1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlay_hint_cache;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use proposed_changes_editor::{
72 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
73};
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79};
80use aho_corasick::AhoCorasick;
81use anyhow::{Context as _, Result, anyhow};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::{AGENT_REPLICA_ID, ReplicaId};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
95use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
96use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
97use futures::{
98 FutureExt, StreamExt as _,
99 future::{self, Shared, join},
100 stream::FuturesUnordered,
101};
102use fuzzy::{StringMatch, StringMatchCandidate};
103use git::blame::{GitBlame, GlobalBlameRenderer};
104use gpui::{
105 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
106 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
107 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
108 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
109 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
110 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
111 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
112 div, point, prelude::*, pulsating_between, px, relative, size,
113};
114use highlight_matching_bracket::refresh_matching_bracket_highlights;
115use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
125 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143 ToOffsetUtf16,
144};
145use parking_lot::Mutex;
146use persistence::DB;
147use project::{
148 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
149 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
150 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
151 ProjectTransaction, TaskSourceKind,
152 debugger::{
153 breakpoint_store::{
154 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
155 BreakpointStore, BreakpointStoreEvent,
156 },
157 session::{Session, SessionEvent},
158 },
159 git_store::{GitStoreEvent, RepositoryEvent},
160 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
161 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
162};
163use rand::seq::SliceRandom;
164use rpc::{ErrorCode, ErrorExt, proto::PeerId};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
166use selections_collection::{
167 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
168};
169use serde::{Deserialize, Serialize};
170use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 iter::{self, Peekable},
179 mem,
180 num::NonZeroU32,
181 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 sync::Arc,
185 time::{Duration, Instant},
186};
187use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::{
208 code_context_menus::CompletionsMenuSource,
209 editor_settings::MultiCursorModifier,
210 hover_links::{find_url, find_url_from_range},
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
213};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
218const MAX_LINE_LEN: usize = 1024;
219const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
220const MAX_SELECTION_HISTORY_LEN: usize = 1024;
221pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
222#[doc(hidden)]
223pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
224pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
225
226pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
227pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
229pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(u32),
283 DebuggerValue(u32),
284 // LSP
285 Hint(u32),
286 Color(u32),
287}
288
289impl InlayId {
290 fn id(&self) -> u32 {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_vertical);
362 workspace.register_action(Editor::new_file_horizontal);
363 workspace.register_action(Editor::cancel_language_server_work);
364 workspace.register_action(Editor::toggle_focus);
365 },
366 )
367 .detach();
368
369 cx.on_action(move |_: &workspace::NewFile, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach();
381 }
382 });
383 cx.on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 if let Some(app_state) = app_state.upgrade() {
386 workspace::open_new(
387 Default::default(),
388 app_state,
389 cx,
390 |workspace, window, cx| {
391 cx.activate(true);
392 Editor::new_file(workspace, &Default::default(), window, cx)
393 },
394 )
395 .detach();
396 }
397 });
398}
399
400pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
401 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
402}
403
404pub trait DiagnosticRenderer {
405 fn render_group(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
408 buffer_id: BufferId,
409 snapshot: EditorSnapshot,
410 editor: WeakEntity<Editor>,
411 cx: &mut App,
412 ) -> Vec<BlockProperties<Anchor>>;
413
414 fn render_hover(
415 &self,
416 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
417 range: Range<Point>,
418 buffer_id: BufferId,
419 cx: &mut App,
420 ) -> Option<Entity<markdown::Markdown>>;
421
422 fn open_link(
423 &self,
424 editor: &mut Editor,
425 link: SharedString,
426 window: &mut Window,
427 cx: &mut Context<Editor>,
428 );
429}
430
431pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
432
433impl GlobalDiagnosticRenderer {
434 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
435 cx.try_global::<Self>().map(|g| g.0.clone())
436 }
437}
438
439impl gpui::Global for GlobalDiagnosticRenderer {}
440pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
441 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
442}
443
444pub struct SearchWithinRange;
445
446trait InvalidationRegion {
447 fn ranges(&self) -> &[Range<Anchor>];
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum SelectPhase {
452 Begin {
453 position: DisplayPoint,
454 add: bool,
455 click_count: usize,
456 },
457 BeginColumnar {
458 position: DisplayPoint,
459 reset: bool,
460 mode: ColumnarMode,
461 goal_column: u32,
462 },
463 Extend {
464 position: DisplayPoint,
465 click_count: usize,
466 },
467 Update {
468 position: DisplayPoint,
469 goal_column: u32,
470 scroll_delta: gpui::Point<f32>,
471 },
472 End,
473}
474
475#[derive(Clone, Debug, PartialEq)]
476pub enum ColumnarMode {
477 FromMouse,
478 FromSelection,
479}
480
481#[derive(Clone, Debug)]
482pub enum SelectMode {
483 Character,
484 Word(Range<Anchor>),
485 Line(Range<Anchor>),
486 All,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// When set to `true`, the editor's height will be determined by its content.
502 sized_by_content: bool,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sized_by_content: false,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, String)>,
637 edit_preview: Option<EditPreview>,
638 display_mode: EditDisplayMode,
639 snapshot: BufferSnapshot,
640 },
641 /// Move to a specific location in the active editor
642 MoveWithin {
643 target: Anchor,
644 snapshot: BufferSnapshot,
645 },
646 /// Move to a specific location in a different editor (not the active one)
647 MoveOutside {
648 target: language::Anchor,
649 snapshot: BufferSnapshot,
650 },
651}
652
653struct EditPredictionState {
654 inlay_ids: Vec<InlayId>,
655 completion: EditPrediction,
656 completion_id: Option<SharedString>,
657 invalidation_range: Option<Range<Anchor>>,
658}
659
660enum EditPredictionSettings {
661 Disabled,
662 Enabled {
663 show_in_menu: bool,
664 preview_requires_modifier: bool,
665 },
666}
667
668enum EditPredictionHighlight {}
669
670#[derive(Debug, Clone)]
671struct InlineDiagnostic {
672 message: SharedString,
673 group_id: usize,
674 is_primary: bool,
675 start: Point,
676 severity: lsp::DiagnosticSeverity,
677}
678
679pub enum MenuEditPredictionsPolicy {
680 Never,
681 ByProvider,
682}
683
684pub enum EditPredictionPreview {
685 /// Modifier is not pressed
686 Inactive { released_too_fast: bool },
687 /// Modifier pressed
688 Active {
689 since: Instant,
690 previous_scroll_position: Option<ScrollAnchor>,
691 },
692}
693
694impl EditPredictionPreview {
695 pub fn released_too_fast(&self) -> bool {
696 match self {
697 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
698 EditPredictionPreview::Active { .. } => false,
699 }
700 }
701
702 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
703 if let EditPredictionPreview::Active {
704 previous_scroll_position,
705 ..
706 } = self
707 {
708 *previous_scroll_position = scroll_position;
709 }
710 }
711}
712
713pub struct ContextMenuOptions {
714 pub min_entries_visible: usize,
715 pub max_entries_visible: usize,
716 pub placement: Option<ContextMenuPlacement>,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum ContextMenuPlacement {
721 Above,
722 Below,
723}
724
725#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
726struct EditorActionId(usize);
727
728impl EditorActionId {
729 pub fn post_inc(&mut self) -> Self {
730 let answer = self.0;
731
732 *self = Self(answer + 1);
733
734 Self(answer)
735 }
736}
737
738// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
739// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
740
741type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
742type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
743
744#[derive(Default)]
745struct ScrollbarMarkerState {
746 scrollbar_size: Size<Pixels>,
747 dirty: bool,
748 markers: Arc<[PaintQuad]>,
749 pending_refresh: Option<Task<Result<()>>>,
750}
751
752impl ScrollbarMarkerState {
753 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
754 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
755 }
756}
757
758#[derive(Clone, Copy, PartialEq, Eq)]
759pub enum MinimapVisibility {
760 Disabled,
761 Enabled {
762 /// The configuration currently present in the users settings.
763 setting_configuration: bool,
764 /// Whether to override the currently set visibility from the users setting.
765 toggle_override: bool,
766 },
767}
768
769impl MinimapVisibility {
770 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
771 if mode.is_full() {
772 Self::Enabled {
773 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
774 toggle_override: false,
775 }
776 } else {
777 Self::Disabled
778 }
779 }
780
781 fn hidden(&self) -> Self {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => Self::Enabled {
787 setting_configuration,
788 toggle_override: setting_configuration,
789 },
790 Self::Disabled => Self::Disabled,
791 }
792 }
793
794 fn disabled(&self) -> bool {
795 matches!(*self, Self::Disabled)
796 }
797
798 fn settings_visibility(&self) -> bool {
799 match *self {
800 Self::Enabled {
801 setting_configuration,
802 ..
803 } => setting_configuration,
804 _ => false,
805 }
806 }
807
808 fn visible(&self) -> bool {
809 match *self {
810 Self::Enabled {
811 setting_configuration,
812 toggle_override,
813 } => setting_configuration ^ toggle_override,
814 _ => false,
815 }
816 }
817
818 fn toggle_visibility(&self) -> Self {
819 match *self {
820 Self::Enabled {
821 toggle_override,
822 setting_configuration,
823 } => Self::Enabled {
824 setting_configuration,
825 toggle_override: !toggle_override,
826 },
827 Self::Disabled => Self::Disabled,
828 }
829 }
830}
831
832#[derive(Clone, Debug)]
833struct RunnableTasks {
834 templates: Vec<(TaskSourceKind, TaskTemplate)>,
835 offset: multi_buffer::Anchor,
836 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
837 column: u32,
838 // Values of all named captures, including those starting with '_'
839 extra_variables: HashMap<String, String>,
840 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
841 context_range: Range<BufferOffset>,
842}
843
844impl RunnableTasks {
845 fn resolve<'a>(
846 &'a self,
847 cx: &'a task::TaskContext,
848 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
849 self.templates.iter().filter_map(|(kind, template)| {
850 template
851 .resolve_task(&kind.to_id_base(), cx)
852 .map(|task| (kind.clone(), task))
853 })
854 }
855}
856
857#[derive(Clone)]
858pub struct ResolvedTasks {
859 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
860 position: Anchor,
861}
862
863#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
864struct BufferOffset(usize);
865
866/// Addons allow storing per-editor state in other crates (e.g. Vim)
867pub trait Addon: 'static {
868 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
869
870 fn render_buffer_header_controls(
871 &self,
872 _: &ExcerptInfo,
873 _: &Window,
874 _: &App,
875 ) -> Option<AnyElement> {
876 None
877 }
878
879 fn to_any(&self) -> &dyn std::any::Any;
880
881 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
882 None
883 }
884}
885
886struct ChangeLocation {
887 current: Option<Vec<Anchor>>,
888 original: Vec<Anchor>,
889}
890impl ChangeLocation {
891 fn locations(&self) -> &[Anchor] {
892 self.current.as_ref().unwrap_or(&self.original)
893 }
894}
895
896/// A set of caret positions, registered when the editor was edited.
897pub struct ChangeList {
898 changes: Vec<ChangeLocation>,
899 /// Currently "selected" change.
900 position: Option<usize>,
901}
902
903impl ChangeList {
904 pub fn new() -> Self {
905 Self {
906 changes: Vec::new(),
907 position: None,
908 }
909 }
910
911 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
912 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
913 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
914 if self.changes.is_empty() {
915 return None;
916 }
917
918 let prev = self.position.unwrap_or(self.changes.len());
919 let next = if direction == Direction::Prev {
920 prev.saturating_sub(count)
921 } else {
922 (prev + count).min(self.changes.len() - 1)
923 };
924 self.position = Some(next);
925 self.changes.get(next).map(|change| change.locations())
926 }
927
928 /// Adds a new change to the list, resetting the change list position.
929 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
930 self.position.take();
931 if let Some(last) = self.changes.last_mut()
932 && group
933 {
934 last.current = Some(new_positions)
935 } else {
936 self.changes.push(ChangeLocation {
937 original: new_positions,
938 current: None,
939 });
940 }
941 }
942
943 pub fn last(&self) -> Option<&[Anchor]> {
944 self.changes.last().map(|change| change.locations())
945 }
946
947 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
948 self.changes.last().map(|change| change.original.as_slice())
949 }
950
951 pub fn invert_last_group(&mut self) {
952 if let Some(last) = self.changes.last_mut()
953 && let Some(current) = last.current.as_mut()
954 {
955 mem::swap(&mut last.original, current);
956 }
957 }
958}
959
960#[derive(Clone)]
961struct InlineBlamePopoverState {
962 scroll_handle: ScrollHandle,
963 commit_message: Option<ParsedCommitMessage>,
964 markdown: Entity<Markdown>,
965}
966
967struct InlineBlamePopover {
968 position: gpui::Point<Pixels>,
969 hide_task: Option<Task<()>>,
970 popover_bounds: Option<Bounds<Pixels>>,
971 popover_state: InlineBlamePopoverState,
972 keyboard_grace: bool,
973}
974
975enum SelectionDragState {
976 /// State when no drag related activity is detected.
977 None,
978 /// State when the mouse is down on a selection that is about to be dragged.
979 ReadyToDrag {
980 selection: Selection<Anchor>,
981 click_position: gpui::Point<Pixels>,
982 mouse_down_time: Instant,
983 },
984 /// State when the mouse is dragging the selection in the editor.
985 Dragging {
986 selection: Selection<Anchor>,
987 drop_cursor: Selection<Anchor>,
988 hide_drop_cursor: bool,
989 },
990}
991
992enum ColumnarSelectionState {
993 FromMouse {
994 selection_tail: Anchor,
995 display_point: Option<DisplayPoint>,
996 },
997 FromSelection {
998 selection_tail: Anchor,
999 },
1000}
1001
1002/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1003/// a breakpoint on them.
1004#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1005struct PhantomBreakpointIndicator {
1006 display_row: DisplayRow,
1007 /// There's a small debounce between hovering over the line and showing the indicator.
1008 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1009 is_active: bool,
1010 collides_with_existing_breakpoint: bool,
1011}
1012
1013/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1014///
1015/// See the [module level documentation](self) for more information.
1016pub struct Editor {
1017 focus_handle: FocusHandle,
1018 last_focused_descendant: Option<WeakFocusHandle>,
1019 /// The text buffer being edited
1020 buffer: Entity<MultiBuffer>,
1021 /// Map of how text in the buffer should be displayed.
1022 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1023 pub display_map: Entity<DisplayMap>,
1024 placeholder_display_map: Option<Entity<DisplayMap>>,
1025 pub selections: SelectionsCollection,
1026 pub scroll_manager: ScrollManager,
1027 /// When inline assist editors are linked, they all render cursors because
1028 /// typing enters text into each of them, even the ones that aren't focused.
1029 pub(crate) show_cursor_when_unfocused: bool,
1030 columnar_selection_state: Option<ColumnarSelectionState>,
1031 add_selections_state: Option<AddSelectionsState>,
1032 select_next_state: Option<SelectNextState>,
1033 select_prev_state: Option<SelectNextState>,
1034 selection_history: SelectionHistory,
1035 defer_selection_effects: bool,
1036 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1037 autoclose_regions: Vec<AutocloseRegion>,
1038 snippet_stack: InvalidationStack<SnippetState>,
1039 select_syntax_node_history: SelectSyntaxNodeHistory,
1040 ime_transaction: Option<TransactionId>,
1041 pub diagnostics_max_severity: DiagnosticSeverity,
1042 active_diagnostics: ActiveDiagnostic,
1043 show_inline_diagnostics: bool,
1044 inline_diagnostics_update: Task<()>,
1045 inline_diagnostics_enabled: bool,
1046 diagnostics_enabled: bool,
1047 word_completions_enabled: bool,
1048 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1049 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1050 hard_wrap: Option<usize>,
1051 project: Option<Entity<Project>>,
1052 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1053 completion_provider: Option<Rc<dyn CompletionProvider>>,
1054 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1055 blink_manager: Entity<BlinkManager>,
1056 show_cursor_names: bool,
1057 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1058 pub show_local_selections: bool,
1059 mode: EditorMode,
1060 show_breadcrumbs: bool,
1061 show_gutter: bool,
1062 show_scrollbars: ScrollbarAxes,
1063 minimap_visibility: MinimapVisibility,
1064 offset_content: bool,
1065 disable_expand_excerpt_buttons: bool,
1066 show_line_numbers: Option<bool>,
1067 use_relative_line_numbers: Option<bool>,
1068 show_git_diff_gutter: Option<bool>,
1069 show_code_actions: Option<bool>,
1070 show_runnables: Option<bool>,
1071 show_breakpoints: Option<bool>,
1072 show_wrap_guides: Option<bool>,
1073 show_indent_guides: Option<bool>,
1074 highlight_order: usize,
1075 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1076 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1077 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1078 scrollbar_marker_state: ScrollbarMarkerState,
1079 active_indent_guides_state: ActiveIndentGuidesState,
1080 nav_history: Option<ItemNavHistory>,
1081 context_menu: RefCell<Option<CodeContextMenu>>,
1082 context_menu_options: Option<ContextMenuOptions>,
1083 mouse_context_menu: Option<MouseContextMenu>,
1084 completion_tasks: Vec<(CompletionId, Task<()>)>,
1085 inline_blame_popover: Option<InlineBlamePopover>,
1086 inline_blame_popover_show_task: Option<Task<()>>,
1087 signature_help_state: SignatureHelpState,
1088 auto_signature_help: Option<bool>,
1089 find_all_references_task_sources: Vec<Anchor>,
1090 next_completion_id: CompletionId,
1091 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1092 code_actions_task: Option<Task<Result<()>>>,
1093 quick_selection_highlight_task: Option<(Range<usize>, Task<()>)>,
1094 debounced_selection_highlight_task: Option<(Range<usize>, Task<()>)>,
1095 document_highlights_task: Option<Task<()>>,
1096 linked_editing_range_task: Option<Task<Option<()>>>,
1097 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1098 pending_rename: Option<RenameState>,
1099 searchable: bool,
1100 cursor_shape: CursorShape,
1101 current_line_highlight: Option<CurrentLineHighlight>,
1102 collapse_matches: bool,
1103 autoindent_mode: Option<AutoindentMode>,
1104 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1105 input_enabled: bool,
1106 use_modal_editing: bool,
1107 read_only: bool,
1108 leader_id: Option<CollaboratorId>,
1109 remote_id: Option<ViewId>,
1110 pub hover_state: HoverState,
1111 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1112 gutter_hovered: bool,
1113 hovered_link_state: Option<HoveredLinkState>,
1114 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1115 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1116 active_edit_prediction: Option<EditPredictionState>,
1117 /// Used to prevent flickering as the user types while the menu is open
1118 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1119 edit_prediction_settings: EditPredictionSettings,
1120 edit_predictions_hidden_for_vim_mode: bool,
1121 show_edit_predictions_override: Option<bool>,
1122 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1123 edit_prediction_preview: EditPredictionPreview,
1124 edit_prediction_indent_conflict: bool,
1125 edit_prediction_requires_modifier_in_indent_conflict: bool,
1126 inlay_hint_cache: InlayHintCache,
1127 next_inlay_id: u32,
1128 next_color_inlay_id: u32,
1129 _subscriptions: Vec<Subscription>,
1130 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1131 gutter_dimensions: GutterDimensions,
1132 style: Option<EditorStyle>,
1133 text_style_refinement: Option<TextStyleRefinement>,
1134 next_editor_action_id: EditorActionId,
1135 editor_actions: Rc<
1136 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1137 >,
1138 use_autoclose: bool,
1139 use_auto_surround: bool,
1140 auto_replace_emoji_shortcode: bool,
1141 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1142 show_git_blame_gutter: bool,
1143 show_git_blame_inline: bool,
1144 show_git_blame_inline_delay_task: Option<Task<()>>,
1145 git_blame_inline_enabled: bool,
1146 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1147 serialize_dirty_buffers: bool,
1148 show_selection_menu: Option<bool>,
1149 blame: Option<Entity<GitBlame>>,
1150 blame_subscription: Option<Subscription>,
1151 custom_context_menu: Option<
1152 Box<
1153 dyn 'static
1154 + Fn(
1155 &mut Self,
1156 DisplayPoint,
1157 &mut Window,
1158 &mut Context<Self>,
1159 ) -> Option<Entity<ui::ContextMenu>>,
1160 >,
1161 >,
1162 last_bounds: Option<Bounds<Pixels>>,
1163 last_position_map: Option<Rc<PositionMap>>,
1164 expect_bounds_change: Option<Bounds<Pixels>>,
1165 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1166 tasks_update_task: Option<Task<()>>,
1167 breakpoint_store: Option<Entity<BreakpointStore>>,
1168 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1169 hovered_diff_hunk_row: Option<DisplayRow>,
1170 pull_diagnostics_task: Task<()>,
1171 in_project_search: bool,
1172 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1173 breadcrumb_header: Option<String>,
1174 focused_block: Option<FocusedBlock>,
1175 next_scroll_position: NextScrollCursorCenterTopBottom,
1176 addons: HashMap<TypeId, Box<dyn Addon>>,
1177 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1178 load_diff_task: Option<Shared<Task<()>>>,
1179 /// Whether we are temporarily displaying a diff other than git's
1180 temporary_diff_override: bool,
1181 selection_mark_mode: bool,
1182 toggle_fold_multiple_buffers: Task<()>,
1183 _scroll_cursor_center_top_bottom_task: Task<()>,
1184 serialize_selections: Task<()>,
1185 serialize_folds: Task<()>,
1186 mouse_cursor_hidden: bool,
1187 minimap: Option<Entity<Self>>,
1188 hide_mouse_mode: HideMouseMode,
1189 pub change_list: ChangeList,
1190 inline_value_cache: InlineValueCache,
1191 selection_drag_state: SelectionDragState,
1192 colors: Option<LspColorData>,
1193 refresh_colors_task: Task<()>,
1194 folding_newlines: Task<()>,
1195 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1196}
1197
1198#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1199enum NextScrollCursorCenterTopBottom {
1200 #[default]
1201 Center,
1202 Top,
1203 Bottom,
1204}
1205
1206impl NextScrollCursorCenterTopBottom {
1207 fn next(&self) -> Self {
1208 match self {
1209 Self::Center => Self::Top,
1210 Self::Top => Self::Bottom,
1211 Self::Bottom => Self::Center,
1212 }
1213 }
1214}
1215
1216#[derive(Clone)]
1217pub struct EditorSnapshot {
1218 pub mode: EditorMode,
1219 show_gutter: bool,
1220 show_line_numbers: Option<bool>,
1221 show_git_diff_gutter: Option<bool>,
1222 show_code_actions: Option<bool>,
1223 show_runnables: Option<bool>,
1224 show_breakpoints: Option<bool>,
1225 git_blame_gutter_max_author_length: Option<usize>,
1226 pub display_snapshot: DisplaySnapshot,
1227 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1228 is_focused: bool,
1229 scroll_anchor: ScrollAnchor,
1230 ongoing_scroll: OngoingScroll,
1231 current_line_highlight: CurrentLineHighlight,
1232 gutter_hovered: bool,
1233}
1234
1235#[derive(Default, Debug, Clone, Copy)]
1236pub struct GutterDimensions {
1237 pub left_padding: Pixels,
1238 pub right_padding: Pixels,
1239 pub width: Pixels,
1240 pub margin: Pixels,
1241 pub git_blame_entries_width: Option<Pixels>,
1242}
1243
1244impl GutterDimensions {
1245 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1246 Self {
1247 margin: Self::default_gutter_margin(font_id, font_size, cx),
1248 ..Default::default()
1249 }
1250 }
1251
1252 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1253 -cx.text_system().descent(font_id, font_size)
1254 }
1255 /// The full width of the space taken up by the gutter.
1256 pub fn full_width(&self) -> Pixels {
1257 self.margin + self.width
1258 }
1259
1260 /// The width of the space reserved for the fold indicators,
1261 /// use alongside 'justify_end' and `gutter_width` to
1262 /// right align content with the line numbers
1263 pub fn fold_area_width(&self) -> Pixels {
1264 self.margin + self.right_padding
1265 }
1266}
1267
1268struct CharacterDimensions {
1269 em_width: Pixels,
1270 em_advance: Pixels,
1271 line_height: Pixels,
1272}
1273
1274#[derive(Debug)]
1275pub struct RemoteSelection {
1276 pub replica_id: ReplicaId,
1277 pub selection: Selection<Anchor>,
1278 pub cursor_shape: CursorShape,
1279 pub collaborator_id: CollaboratorId,
1280 pub line_mode: bool,
1281 pub user_name: Option<SharedString>,
1282 pub color: PlayerColor,
1283}
1284
1285#[derive(Clone, Debug)]
1286struct SelectionHistoryEntry {
1287 selections: Arc<[Selection<Anchor>]>,
1288 select_next_state: Option<SelectNextState>,
1289 select_prev_state: Option<SelectNextState>,
1290 add_selections_state: Option<AddSelectionsState>,
1291}
1292
1293#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1294enum SelectionHistoryMode {
1295 Normal,
1296 Undoing,
1297 Redoing,
1298 Skipping,
1299}
1300
1301#[derive(Clone, PartialEq, Eq, Hash)]
1302struct HoveredCursor {
1303 replica_id: u16,
1304 selection_id: usize,
1305}
1306
1307impl Default for SelectionHistoryMode {
1308 fn default() -> Self {
1309 Self::Normal
1310 }
1311}
1312
1313#[derive(Debug)]
1314/// SelectionEffects controls the side-effects of updating the selection.
1315///
1316/// The default behaviour does "what you mostly want":
1317/// - it pushes to the nav history if the cursor moved by >10 lines
1318/// - it re-triggers completion requests
1319/// - it scrolls to fit
1320///
1321/// You might want to modify these behaviours. For example when doing a "jump"
1322/// like go to definition, we always want to add to nav history; but when scrolling
1323/// in vim mode we never do.
1324///
1325/// Similarly, you might want to disable scrolling if you don't want the viewport to
1326/// move.
1327#[derive(Clone)]
1328pub struct SelectionEffects {
1329 nav_history: Option<bool>,
1330 completions: bool,
1331 scroll: Option<Autoscroll>,
1332}
1333
1334impl Default for SelectionEffects {
1335 fn default() -> Self {
1336 Self {
1337 nav_history: None,
1338 completions: true,
1339 scroll: Some(Autoscroll::fit()),
1340 }
1341 }
1342}
1343impl SelectionEffects {
1344 pub fn scroll(scroll: Autoscroll) -> Self {
1345 Self {
1346 scroll: Some(scroll),
1347 ..Default::default()
1348 }
1349 }
1350
1351 pub fn no_scroll() -> Self {
1352 Self {
1353 scroll: None,
1354 ..Default::default()
1355 }
1356 }
1357
1358 pub fn completions(self, completions: bool) -> Self {
1359 Self {
1360 completions,
1361 ..self
1362 }
1363 }
1364
1365 pub fn nav_history(self, nav_history: bool) -> Self {
1366 Self {
1367 nav_history: Some(nav_history),
1368 ..self
1369 }
1370 }
1371}
1372
1373struct DeferredSelectionEffectsState {
1374 changed: bool,
1375 effects: SelectionEffects,
1376 old_cursor_position: Anchor,
1377 history_entry: SelectionHistoryEntry,
1378}
1379
1380#[derive(Default)]
1381struct SelectionHistory {
1382 #[allow(clippy::type_complexity)]
1383 selections_by_transaction:
1384 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1385 mode: SelectionHistoryMode,
1386 undo_stack: VecDeque<SelectionHistoryEntry>,
1387 redo_stack: VecDeque<SelectionHistoryEntry>,
1388}
1389
1390impl SelectionHistory {
1391 #[track_caller]
1392 fn insert_transaction(
1393 &mut self,
1394 transaction_id: TransactionId,
1395 selections: Arc<[Selection<Anchor>]>,
1396 ) {
1397 if selections.is_empty() {
1398 log::error!(
1399 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1400 std::panic::Location::caller()
1401 );
1402 return;
1403 }
1404 self.selections_by_transaction
1405 .insert(transaction_id, (selections, None));
1406 }
1407
1408 #[allow(clippy::type_complexity)]
1409 fn transaction(
1410 &self,
1411 transaction_id: TransactionId,
1412 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1413 self.selections_by_transaction.get(&transaction_id)
1414 }
1415
1416 #[allow(clippy::type_complexity)]
1417 fn transaction_mut(
1418 &mut self,
1419 transaction_id: TransactionId,
1420 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1421 self.selections_by_transaction.get_mut(&transaction_id)
1422 }
1423
1424 fn push(&mut self, entry: SelectionHistoryEntry) {
1425 if !entry.selections.is_empty() {
1426 match self.mode {
1427 SelectionHistoryMode::Normal => {
1428 self.push_undo(entry);
1429 self.redo_stack.clear();
1430 }
1431 SelectionHistoryMode::Undoing => self.push_redo(entry),
1432 SelectionHistoryMode::Redoing => self.push_undo(entry),
1433 SelectionHistoryMode::Skipping => {}
1434 }
1435 }
1436 }
1437
1438 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1439 if self
1440 .undo_stack
1441 .back()
1442 .is_none_or(|e| e.selections != entry.selections)
1443 {
1444 self.undo_stack.push_back(entry);
1445 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1446 self.undo_stack.pop_front();
1447 }
1448 }
1449 }
1450
1451 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1452 if self
1453 .redo_stack
1454 .back()
1455 .is_none_or(|e| e.selections != entry.selections)
1456 {
1457 self.redo_stack.push_back(entry);
1458 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1459 self.redo_stack.pop_front();
1460 }
1461 }
1462 }
1463}
1464
1465#[derive(Clone, Copy)]
1466pub struct RowHighlightOptions {
1467 pub autoscroll: bool,
1468 pub include_gutter: bool,
1469}
1470
1471impl Default for RowHighlightOptions {
1472 fn default() -> Self {
1473 Self {
1474 autoscroll: Default::default(),
1475 include_gutter: true,
1476 }
1477 }
1478}
1479
1480struct RowHighlight {
1481 index: usize,
1482 range: Range<Anchor>,
1483 color: Hsla,
1484 options: RowHighlightOptions,
1485 type_id: TypeId,
1486}
1487
1488#[derive(Clone, Debug)]
1489struct AddSelectionsState {
1490 groups: Vec<AddSelectionsGroup>,
1491}
1492
1493#[derive(Clone, Debug)]
1494struct AddSelectionsGroup {
1495 above: bool,
1496 stack: Vec<usize>,
1497}
1498
1499#[derive(Clone)]
1500struct SelectNextState {
1501 query: AhoCorasick,
1502 wordwise: bool,
1503 done: bool,
1504}
1505
1506impl std::fmt::Debug for SelectNextState {
1507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1508 f.debug_struct(std::any::type_name::<Self>())
1509 .field("wordwise", &self.wordwise)
1510 .field("done", &self.done)
1511 .finish()
1512 }
1513}
1514
1515#[derive(Debug)]
1516struct AutocloseRegion {
1517 selection_id: usize,
1518 range: Range<Anchor>,
1519 pair: BracketPair,
1520}
1521
1522#[derive(Debug)]
1523struct SnippetState {
1524 ranges: Vec<Vec<Range<Anchor>>>,
1525 active_index: usize,
1526 choices: Vec<Option<Vec<String>>>,
1527}
1528
1529#[doc(hidden)]
1530pub struct RenameState {
1531 pub range: Range<Anchor>,
1532 pub old_name: Arc<str>,
1533 pub editor: Entity<Editor>,
1534 block_id: CustomBlockId,
1535}
1536
1537struct InvalidationStack<T>(Vec<T>);
1538
1539struct RegisteredEditPredictionProvider {
1540 provider: Arc<dyn EditPredictionProviderHandle>,
1541 _subscription: Subscription,
1542}
1543
1544#[derive(Debug, PartialEq, Eq)]
1545pub struct ActiveDiagnosticGroup {
1546 pub active_range: Range<Anchor>,
1547 pub active_message: String,
1548 pub group_id: usize,
1549 pub blocks: HashSet<CustomBlockId>,
1550}
1551
1552#[derive(Debug, PartialEq, Eq)]
1553
1554pub(crate) enum ActiveDiagnostic {
1555 None,
1556 All,
1557 Group(ActiveDiagnosticGroup),
1558}
1559
1560#[derive(Serialize, Deserialize, Clone, Debug)]
1561pub struct ClipboardSelection {
1562 /// The number of bytes in this selection.
1563 pub len: usize,
1564 /// Whether this was a full-line selection.
1565 pub is_entire_line: bool,
1566 /// The indentation of the first line when this content was originally copied.
1567 pub first_line_indent: u32,
1568}
1569
1570// selections, scroll behavior, was newest selection reversed
1571type SelectSyntaxNodeHistoryState = (
1572 Box<[Selection<usize>]>,
1573 SelectSyntaxNodeScrollBehavior,
1574 bool,
1575);
1576
1577#[derive(Default)]
1578struct SelectSyntaxNodeHistory {
1579 stack: Vec<SelectSyntaxNodeHistoryState>,
1580 // disable temporarily to allow changing selections without losing the stack
1581 pub disable_clearing: bool,
1582}
1583
1584impl SelectSyntaxNodeHistory {
1585 pub fn try_clear(&mut self) {
1586 if !self.disable_clearing {
1587 self.stack.clear();
1588 }
1589 }
1590
1591 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1592 self.stack.push(selection);
1593 }
1594
1595 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1596 self.stack.pop()
1597 }
1598}
1599
1600enum SelectSyntaxNodeScrollBehavior {
1601 CursorTop,
1602 FitSelection,
1603 CursorBottom,
1604}
1605
1606#[derive(Debug)]
1607pub(crate) struct NavigationData {
1608 cursor_anchor: Anchor,
1609 cursor_position: Point,
1610 scroll_anchor: ScrollAnchor,
1611 scroll_top_row: u32,
1612}
1613
1614#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1615pub enum GotoDefinitionKind {
1616 Symbol,
1617 Declaration,
1618 Type,
1619 Implementation,
1620}
1621
1622#[derive(Debug, Clone)]
1623enum InlayHintRefreshReason {
1624 ModifiersChanged(bool),
1625 Toggle(bool),
1626 SettingsChange(InlayHintSettings),
1627 NewLinesShown,
1628 BufferEdited(HashSet<Arc<Language>>),
1629 RefreshRequested,
1630 ExcerptsRemoved(Vec<ExcerptId>),
1631}
1632
1633impl InlayHintRefreshReason {
1634 fn description(&self) -> &'static str {
1635 match self {
1636 Self::ModifiersChanged(_) => "modifiers changed",
1637 Self::Toggle(_) => "toggle",
1638 Self::SettingsChange(_) => "settings change",
1639 Self::NewLinesShown => "new lines shown",
1640 Self::BufferEdited(_) => "buffer edited",
1641 Self::RefreshRequested => "refresh requested",
1642 Self::ExcerptsRemoved(_) => "excerpts removed",
1643 }
1644 }
1645}
1646
1647pub enum FormatTarget {
1648 Buffers(HashSet<Entity<Buffer>>),
1649 Ranges(Vec<Range<MultiBufferPoint>>),
1650}
1651
1652pub(crate) struct FocusedBlock {
1653 id: BlockId,
1654 focus_handle: WeakFocusHandle,
1655}
1656
1657#[derive(Clone)]
1658enum JumpData {
1659 MultiBufferRow {
1660 row: MultiBufferRow,
1661 line_offset_from_top: u32,
1662 },
1663 MultiBufferPoint {
1664 excerpt_id: ExcerptId,
1665 position: Point,
1666 anchor: text::Anchor,
1667 line_offset_from_top: u32,
1668 },
1669}
1670
1671pub enum MultibufferSelectionMode {
1672 First,
1673 All,
1674}
1675
1676#[derive(Clone, Copy, Debug, Default)]
1677pub struct RewrapOptions {
1678 pub override_language_settings: bool,
1679 pub preserve_existing_whitespace: bool,
1680}
1681
1682impl Editor {
1683 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1684 let buffer = cx.new(|cx| Buffer::local("", cx));
1685 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1686 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1687 }
1688
1689 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1690 let buffer = cx.new(|cx| Buffer::local("", cx));
1691 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1692 Self::new(EditorMode::full(), buffer, None, window, cx)
1693 }
1694
1695 pub fn auto_height(
1696 min_lines: usize,
1697 max_lines: usize,
1698 window: &mut Window,
1699 cx: &mut Context<Self>,
1700 ) -> Self {
1701 let buffer = cx.new(|cx| Buffer::local("", cx));
1702 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1703 Self::new(
1704 EditorMode::AutoHeight {
1705 min_lines,
1706 max_lines: Some(max_lines),
1707 },
1708 buffer,
1709 None,
1710 window,
1711 cx,
1712 )
1713 }
1714
1715 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1716 /// The editor grows as tall as needed to fit its content.
1717 pub fn auto_height_unbounded(
1718 min_lines: usize,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 let buffer = cx.new(|cx| Buffer::local("", cx));
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(
1725 EditorMode::AutoHeight {
1726 min_lines,
1727 max_lines: None,
1728 },
1729 buffer,
1730 None,
1731 window,
1732 cx,
1733 )
1734 }
1735
1736 pub fn for_buffer(
1737 buffer: Entity<Buffer>,
1738 project: Option<Entity<Project>>,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1743 Self::new(EditorMode::full(), buffer, project, window, cx)
1744 }
1745
1746 pub fn for_multibuffer(
1747 buffer: Entity<MultiBuffer>,
1748 project: Option<Entity<Project>>,
1749 window: &mut Window,
1750 cx: &mut Context<Self>,
1751 ) -> Self {
1752 Self::new(EditorMode::full(), buffer, project, window, cx)
1753 }
1754
1755 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1756 let mut clone = Self::new(
1757 self.mode.clone(),
1758 self.buffer.clone(),
1759 self.project.clone(),
1760 window,
1761 cx,
1762 );
1763 self.display_map.update(cx, |display_map, cx| {
1764 let snapshot = display_map.snapshot(cx);
1765 clone.display_map.update(cx, |display_map, cx| {
1766 display_map.set_state(&snapshot, cx);
1767 });
1768 });
1769 clone.folds_did_change(cx);
1770 clone.selections.clone_state(&self.selections);
1771 clone.scroll_manager.clone_state(&self.scroll_manager);
1772 clone.searchable = self.searchable;
1773 clone.read_only = self.read_only;
1774 clone
1775 }
1776
1777 pub fn new(
1778 mode: EditorMode,
1779 buffer: Entity<MultiBuffer>,
1780 project: Option<Entity<Project>>,
1781 window: &mut Window,
1782 cx: &mut Context<Self>,
1783 ) -> Self {
1784 Editor::new_internal(mode, buffer, project, None, window, cx)
1785 }
1786
1787 fn new_internal(
1788 mode: EditorMode,
1789 buffer: Entity<MultiBuffer>,
1790 project: Option<Entity<Project>>,
1791 display_map: Option<Entity<DisplayMap>>,
1792 window: &mut Window,
1793 cx: &mut Context<Self>,
1794 ) -> Self {
1795 debug_assert!(
1796 display_map.is_none() || mode.is_minimap(),
1797 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1798 );
1799
1800 let full_mode = mode.is_full();
1801 let is_minimap = mode.is_minimap();
1802 let diagnostics_max_severity = if full_mode {
1803 EditorSettings::get_global(cx)
1804 .diagnostics_max_severity
1805 .unwrap_or(DiagnosticSeverity::Hint)
1806 } else {
1807 DiagnosticSeverity::Off
1808 };
1809 let style = window.text_style();
1810 let font_size = style.font_size.to_pixels(window.rem_size());
1811 let editor = cx.entity().downgrade();
1812 let fold_placeholder = FoldPlaceholder {
1813 constrain_width: false,
1814 render: Arc::new(move |fold_id, fold_range, cx| {
1815 let editor = editor.clone();
1816 div()
1817 .id(fold_id)
1818 .bg(cx.theme().colors().ghost_element_background)
1819 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1820 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1821 .rounded_xs()
1822 .size_full()
1823 .cursor_pointer()
1824 .child("⋯")
1825 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1826 .on_click(move |_, _window, cx| {
1827 editor
1828 .update(cx, |editor, cx| {
1829 editor.unfold_ranges(
1830 &[fold_range.start..fold_range.end],
1831 true,
1832 false,
1833 cx,
1834 );
1835 cx.stop_propagation();
1836 })
1837 .ok();
1838 })
1839 .into_any()
1840 }),
1841 merge_adjacent: true,
1842 ..FoldPlaceholder::default()
1843 };
1844 let display_map = display_map.unwrap_or_else(|| {
1845 cx.new(|cx| {
1846 DisplayMap::new(
1847 buffer.clone(),
1848 style.font(),
1849 font_size,
1850 None,
1851 FILE_HEADER_HEIGHT,
1852 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1853 fold_placeholder,
1854 diagnostics_max_severity,
1855 cx,
1856 )
1857 })
1858 });
1859
1860 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1861
1862 let blink_manager = cx.new(|cx| {
1863 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1864 if is_minimap {
1865 blink_manager.disable(cx);
1866 }
1867 blink_manager
1868 });
1869
1870 let soft_wrap_mode_override =
1871 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1872
1873 let mut project_subscriptions = Vec::new();
1874 if full_mode && let Some(project) = project.as_ref() {
1875 project_subscriptions.push(cx.subscribe_in(
1876 project,
1877 window,
1878 |editor, _, event, window, cx| match event {
1879 project::Event::RefreshCodeLens => {
1880 // we always query lens with actions, without storing them, always refreshing them
1881 }
1882 project::Event::RefreshInlayHints => {
1883 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1884 }
1885 project::Event::LanguageServerAdded(..)
1886 | project::Event::LanguageServerRemoved(..) => {
1887 if editor.tasks_update_task.is_none() {
1888 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1889 }
1890 }
1891 project::Event::SnippetEdit(id, snippet_edits) => {
1892 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1893 let focus_handle = editor.focus_handle(cx);
1894 if focus_handle.is_focused(window) {
1895 let snapshot = buffer.read(cx).snapshot();
1896 for (range, snippet) in snippet_edits {
1897 let editor_range =
1898 language::range_from_lsp(*range).to_offset(&snapshot);
1899 editor
1900 .insert_snippet(
1901 &[editor_range],
1902 snippet.clone(),
1903 window,
1904 cx,
1905 )
1906 .ok();
1907 }
1908 }
1909 }
1910 }
1911 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1912 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1913 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1914 }
1915 }
1916
1917 project::Event::EntryRenamed(transaction) => {
1918 let Some(workspace) = editor.workspace() else {
1919 return;
1920 };
1921 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1922 else {
1923 return;
1924 };
1925 if active_editor.entity_id() == cx.entity_id() {
1926 let edited_buffers_already_open = {
1927 let other_editors: Vec<Entity<Editor>> = workspace
1928 .read(cx)
1929 .panes()
1930 .iter()
1931 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1932 .filter(|editor| editor.entity_id() != cx.entity_id())
1933 .collect();
1934
1935 transaction.0.keys().all(|buffer| {
1936 other_editors.iter().any(|editor| {
1937 let multi_buffer = editor.read(cx).buffer();
1938 multi_buffer.read(cx).is_singleton()
1939 && multi_buffer.read(cx).as_singleton().map_or(
1940 false,
1941 |singleton| {
1942 singleton.entity_id() == buffer.entity_id()
1943 },
1944 )
1945 })
1946 })
1947 };
1948
1949 if !edited_buffers_already_open {
1950 let workspace = workspace.downgrade();
1951 let transaction = transaction.clone();
1952 cx.defer_in(window, move |_, window, cx| {
1953 cx.spawn_in(window, async move |editor, cx| {
1954 Self::open_project_transaction(
1955 &editor,
1956 workspace,
1957 transaction,
1958 "Rename".to_string(),
1959 cx,
1960 )
1961 .await
1962 .ok()
1963 })
1964 .detach();
1965 });
1966 }
1967 }
1968 }
1969
1970 _ => {}
1971 },
1972 ));
1973 if let Some(task_inventory) = project
1974 .read(cx)
1975 .task_store()
1976 .read(cx)
1977 .task_inventory()
1978 .cloned()
1979 {
1980 project_subscriptions.push(cx.observe_in(
1981 &task_inventory,
1982 window,
1983 |editor, _, window, cx| {
1984 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1985 },
1986 ));
1987 };
1988
1989 project_subscriptions.push(cx.subscribe_in(
1990 &project.read(cx).breakpoint_store(),
1991 window,
1992 |editor, _, event, window, cx| match event {
1993 BreakpointStoreEvent::ClearDebugLines => {
1994 editor.clear_row_highlights::<ActiveDebugLine>();
1995 editor.refresh_inline_values(cx);
1996 }
1997 BreakpointStoreEvent::SetDebugLine => {
1998 if editor.go_to_active_debug_line(window, cx) {
1999 cx.stop_propagation();
2000 }
2001
2002 editor.refresh_inline_values(cx);
2003 }
2004 _ => {}
2005 },
2006 ));
2007 let git_store = project.read(cx).git_store().clone();
2008 let project = project.clone();
2009 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2010 if let GitStoreEvent::RepositoryUpdated(
2011 _,
2012 RepositoryEvent::Updated {
2013 new_instance: true, ..
2014 },
2015 _,
2016 ) = event
2017 {
2018 this.load_diff_task = Some(
2019 update_uncommitted_diff_for_buffer(
2020 cx.entity(),
2021 &project,
2022 this.buffer.read(cx).all_buffers(),
2023 this.buffer.clone(),
2024 cx,
2025 )
2026 .shared(),
2027 );
2028 }
2029 }));
2030 }
2031
2032 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2033
2034 let inlay_hint_settings =
2035 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2036 let focus_handle = cx.focus_handle();
2037 if !is_minimap {
2038 cx.on_focus(&focus_handle, window, Self::handle_focus)
2039 .detach();
2040 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2041 .detach();
2042 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2043 .detach();
2044 cx.on_blur(&focus_handle, window, Self::handle_blur)
2045 .detach();
2046 cx.observe_pending_input(window, Self::observe_pending_input)
2047 .detach();
2048 }
2049
2050 let show_indent_guides =
2051 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2052 Some(false)
2053 } else {
2054 None
2055 };
2056
2057 let breakpoint_store = match (&mode, project.as_ref()) {
2058 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2059 _ => None,
2060 };
2061
2062 let mut code_action_providers = Vec::new();
2063 let mut load_uncommitted_diff = None;
2064 if let Some(project) = project.clone() {
2065 load_uncommitted_diff = Some(
2066 update_uncommitted_diff_for_buffer(
2067 cx.entity(),
2068 &project,
2069 buffer.read(cx).all_buffers(),
2070 buffer.clone(),
2071 cx,
2072 )
2073 .shared(),
2074 );
2075 code_action_providers.push(Rc::new(project) as Rc<_>);
2076 }
2077
2078 let mut editor = Self {
2079 focus_handle,
2080 show_cursor_when_unfocused: false,
2081 last_focused_descendant: None,
2082 buffer: buffer.clone(),
2083 display_map: display_map.clone(),
2084 placeholder_display_map: None,
2085 selections,
2086 scroll_manager: ScrollManager::new(cx),
2087 columnar_selection_state: None,
2088 add_selections_state: None,
2089 select_next_state: None,
2090 select_prev_state: None,
2091 selection_history: SelectionHistory::default(),
2092 defer_selection_effects: false,
2093 deferred_selection_effects_state: None,
2094 autoclose_regions: Vec::new(),
2095 snippet_stack: InvalidationStack::default(),
2096 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2097 ime_transaction: None,
2098 active_diagnostics: ActiveDiagnostic::None,
2099 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2100 inline_diagnostics_update: Task::ready(()),
2101 inline_diagnostics: Vec::new(),
2102 soft_wrap_mode_override,
2103 diagnostics_max_severity,
2104 hard_wrap: None,
2105 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2106 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2107 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2108 project,
2109 blink_manager: blink_manager.clone(),
2110 show_local_selections: true,
2111 show_scrollbars: ScrollbarAxes {
2112 horizontal: full_mode,
2113 vertical: full_mode,
2114 },
2115 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2116 offset_content: !matches!(mode, EditorMode::SingleLine),
2117 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2118 show_gutter: full_mode,
2119 show_line_numbers: (!full_mode).then_some(false),
2120 use_relative_line_numbers: None,
2121 disable_expand_excerpt_buttons: !full_mode,
2122 show_git_diff_gutter: None,
2123 show_code_actions: None,
2124 show_runnables: None,
2125 show_breakpoints: None,
2126 show_wrap_guides: None,
2127 show_indent_guides,
2128 highlight_order: 0,
2129 highlighted_rows: HashMap::default(),
2130 background_highlights: HashMap::default(),
2131 gutter_highlights: HashMap::default(),
2132 scrollbar_marker_state: ScrollbarMarkerState::default(),
2133 active_indent_guides_state: ActiveIndentGuidesState::default(),
2134 nav_history: None,
2135 context_menu: RefCell::new(None),
2136 context_menu_options: None,
2137 mouse_context_menu: None,
2138 completion_tasks: Vec::new(),
2139 inline_blame_popover: None,
2140 inline_blame_popover_show_task: None,
2141 signature_help_state: SignatureHelpState::default(),
2142 auto_signature_help: None,
2143 find_all_references_task_sources: Vec::new(),
2144 next_completion_id: 0,
2145 next_inlay_id: 0,
2146 code_action_providers,
2147 available_code_actions: None,
2148 code_actions_task: None,
2149 quick_selection_highlight_task: None,
2150 debounced_selection_highlight_task: None,
2151 document_highlights_task: None,
2152 linked_editing_range_task: None,
2153 pending_rename: None,
2154 searchable: !is_minimap,
2155 cursor_shape: EditorSettings::get_global(cx)
2156 .cursor_shape
2157 .unwrap_or_default(),
2158 current_line_highlight: None,
2159 autoindent_mode: Some(AutoindentMode::EachLine),
2160 collapse_matches: false,
2161 workspace: None,
2162 input_enabled: !is_minimap,
2163 use_modal_editing: full_mode,
2164 read_only: is_minimap,
2165 use_autoclose: true,
2166 use_auto_surround: true,
2167 auto_replace_emoji_shortcode: false,
2168 jsx_tag_auto_close_enabled_in_any_buffer: false,
2169 leader_id: None,
2170 remote_id: None,
2171 hover_state: HoverState::default(),
2172 pending_mouse_down: None,
2173 hovered_link_state: None,
2174 edit_prediction_provider: None,
2175 active_edit_prediction: None,
2176 stale_edit_prediction_in_menu: None,
2177 edit_prediction_preview: EditPredictionPreview::Inactive {
2178 released_too_fast: false,
2179 },
2180 inline_diagnostics_enabled: full_mode,
2181 diagnostics_enabled: full_mode,
2182 word_completions_enabled: full_mode,
2183 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2184 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2185 gutter_hovered: false,
2186 pixel_position_of_newest_cursor: None,
2187 last_bounds: None,
2188 last_position_map: None,
2189 expect_bounds_change: None,
2190 gutter_dimensions: GutterDimensions::default(),
2191 style: None,
2192 show_cursor_names: false,
2193 hovered_cursors: HashMap::default(),
2194 next_editor_action_id: EditorActionId::default(),
2195 editor_actions: Rc::default(),
2196 edit_predictions_hidden_for_vim_mode: false,
2197 show_edit_predictions_override: None,
2198 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2199 edit_prediction_settings: EditPredictionSettings::Disabled,
2200 edit_prediction_indent_conflict: false,
2201 edit_prediction_requires_modifier_in_indent_conflict: true,
2202 custom_context_menu: None,
2203 show_git_blame_gutter: false,
2204 show_git_blame_inline: false,
2205 show_selection_menu: None,
2206 show_git_blame_inline_delay_task: None,
2207 git_blame_inline_enabled: full_mode
2208 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2209 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2210 serialize_dirty_buffers: !is_minimap
2211 && ProjectSettings::get_global(cx)
2212 .session
2213 .restore_unsaved_buffers,
2214 blame: None,
2215 blame_subscription: None,
2216 tasks: BTreeMap::default(),
2217
2218 breakpoint_store,
2219 gutter_breakpoint_indicator: (None, None),
2220 hovered_diff_hunk_row: None,
2221 _subscriptions: (!is_minimap)
2222 .then(|| {
2223 vec![
2224 cx.observe(&buffer, Self::on_buffer_changed),
2225 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2226 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2227 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2228 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2229 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2230 cx.observe_window_activation(window, |editor, window, cx| {
2231 let active = window.is_window_active();
2232 editor.blink_manager.update(cx, |blink_manager, cx| {
2233 if active {
2234 blink_manager.enable(cx);
2235 } else {
2236 blink_manager.disable(cx);
2237 }
2238 });
2239 if active {
2240 editor.show_mouse_cursor(cx);
2241 }
2242 }),
2243 ]
2244 })
2245 .unwrap_or_default(),
2246 tasks_update_task: None,
2247 pull_diagnostics_task: Task::ready(()),
2248 colors: None,
2249 refresh_colors_task: Task::ready(()),
2250 next_color_inlay_id: 0,
2251 linked_edit_ranges: Default::default(),
2252 in_project_search: false,
2253 previous_search_ranges: None,
2254 breadcrumb_header: None,
2255 focused_block: None,
2256 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2257 addons: HashMap::default(),
2258 registered_buffers: HashMap::default(),
2259 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2260 selection_mark_mode: false,
2261 toggle_fold_multiple_buffers: Task::ready(()),
2262 serialize_selections: Task::ready(()),
2263 serialize_folds: Task::ready(()),
2264 text_style_refinement: None,
2265 load_diff_task: load_uncommitted_diff,
2266 temporary_diff_override: false,
2267 mouse_cursor_hidden: false,
2268 minimap: None,
2269 hide_mouse_mode: EditorSettings::get_global(cx)
2270 .hide_mouse
2271 .unwrap_or_default(),
2272 change_list: ChangeList::new(),
2273 mode,
2274 selection_drag_state: SelectionDragState::None,
2275 folding_newlines: Task::ready(()),
2276 lookup_key: None,
2277 };
2278
2279 if is_minimap {
2280 return editor;
2281 }
2282
2283 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2284 editor
2285 ._subscriptions
2286 .push(cx.observe(breakpoints, |_, _, cx| {
2287 cx.notify();
2288 }));
2289 }
2290 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2291 editor._subscriptions.extend(project_subscriptions);
2292
2293 editor._subscriptions.push(cx.subscribe_in(
2294 &cx.entity(),
2295 window,
2296 |editor, _, e: &EditorEvent, window, cx| match e {
2297 EditorEvent::ScrollPositionChanged { local, .. } => {
2298 if *local {
2299 let new_anchor = editor.scroll_manager.anchor();
2300 let snapshot = editor.snapshot(window, cx);
2301 editor.update_restoration_data(cx, move |data| {
2302 data.scroll_position = (
2303 new_anchor.top_row(snapshot.buffer_snapshot()),
2304 new_anchor.offset,
2305 );
2306 });
2307 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2308 editor.inline_blame_popover.take();
2309 }
2310 }
2311 EditorEvent::Edited { .. } => {
2312 if !vim_enabled(cx) {
2313 let (map, selections) = editor.selections.all_adjusted_display(cx);
2314 let pop_state = editor
2315 .change_list
2316 .last()
2317 .map(|previous| {
2318 previous.len() == selections.len()
2319 && previous.iter().enumerate().all(|(ix, p)| {
2320 p.to_display_point(&map).row()
2321 == selections[ix].head().row()
2322 })
2323 })
2324 .unwrap_or(false);
2325 let new_positions = selections
2326 .into_iter()
2327 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2328 .collect();
2329 editor
2330 .change_list
2331 .push_to_change_list(pop_state, new_positions);
2332 }
2333 }
2334 _ => (),
2335 },
2336 ));
2337
2338 if let Some(dap_store) = editor
2339 .project
2340 .as_ref()
2341 .map(|project| project.read(cx).dap_store())
2342 {
2343 let weak_editor = cx.weak_entity();
2344
2345 editor
2346 ._subscriptions
2347 .push(
2348 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2349 let session_entity = cx.entity();
2350 weak_editor
2351 .update(cx, |editor, cx| {
2352 editor._subscriptions.push(
2353 cx.subscribe(&session_entity, Self::on_debug_session_event),
2354 );
2355 })
2356 .ok();
2357 }),
2358 );
2359
2360 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2361 editor
2362 ._subscriptions
2363 .push(cx.subscribe(&session, Self::on_debug_session_event));
2364 }
2365 }
2366
2367 // skip adding the initial selection to selection history
2368 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2369 editor.end_selection(window, cx);
2370 editor.selection_history.mode = SelectionHistoryMode::Normal;
2371
2372 editor.scroll_manager.show_scrollbars(window, cx);
2373 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2374
2375 if full_mode {
2376 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2377 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2378
2379 if editor.git_blame_inline_enabled {
2380 editor.start_git_blame_inline(false, window, cx);
2381 }
2382
2383 editor.go_to_active_debug_line(window, cx);
2384
2385 if let Some(buffer) = buffer.read(cx).as_singleton()
2386 && let Some(project) = editor.project()
2387 {
2388 let handle = project.update(cx, |project, cx| {
2389 project.register_buffer_with_language_servers(&buffer, cx)
2390 });
2391 editor
2392 .registered_buffers
2393 .insert(buffer.read(cx).remote_id(), handle);
2394 }
2395
2396 editor.minimap =
2397 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2398 editor.colors = Some(LspColorData::new(cx));
2399 editor.update_lsp_data(false, None, window, cx);
2400 }
2401
2402 if editor.mode.is_full() {
2403 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2404 }
2405
2406 editor
2407 }
2408
2409 pub fn deploy_mouse_context_menu(
2410 &mut self,
2411 position: gpui::Point<Pixels>,
2412 context_menu: Entity<ContextMenu>,
2413 window: &mut Window,
2414 cx: &mut Context<Self>,
2415 ) {
2416 self.mouse_context_menu = Some(MouseContextMenu::new(
2417 self,
2418 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2419 context_menu,
2420 window,
2421 cx,
2422 ));
2423 }
2424
2425 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2426 self.mouse_context_menu
2427 .as_ref()
2428 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2429 }
2430
2431 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2432 if self
2433 .selections
2434 .pending_anchor()
2435 .is_some_and(|pending_selection| {
2436 let snapshot = self.buffer().read(cx).snapshot(cx);
2437 pending_selection.range().includes(range, &snapshot)
2438 })
2439 {
2440 return true;
2441 }
2442
2443 self.selections
2444 .disjoint_in_range::<usize>(range.clone(), cx)
2445 .into_iter()
2446 .any(|selection| {
2447 // This is needed to cover a corner case, if we just check for an existing
2448 // selection in the fold range, having a cursor at the start of the fold
2449 // marks it as selected. Non-empty selections don't cause this.
2450 let length = selection.end - selection.start;
2451 length > 0
2452 })
2453 }
2454
2455 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2456 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2457 }
2458
2459 fn key_context_internal(
2460 &self,
2461 has_active_edit_prediction: bool,
2462 window: &Window,
2463 cx: &App,
2464 ) -> KeyContext {
2465 let mut key_context = KeyContext::new_with_defaults();
2466 key_context.add("Editor");
2467 let mode = match self.mode {
2468 EditorMode::SingleLine => "single_line",
2469 EditorMode::AutoHeight { .. } => "auto_height",
2470 EditorMode::Minimap { .. } => "minimap",
2471 EditorMode::Full { .. } => "full",
2472 };
2473
2474 if EditorSettings::jupyter_enabled(cx) {
2475 key_context.add("jupyter");
2476 }
2477
2478 key_context.set("mode", mode);
2479 if self.pending_rename.is_some() {
2480 key_context.add("renaming");
2481 }
2482
2483 match self.context_menu.borrow().as_ref() {
2484 Some(CodeContextMenu::Completions(menu)) => {
2485 if menu.visible() {
2486 key_context.add("menu");
2487 key_context.add("showing_completions");
2488 }
2489 }
2490 Some(CodeContextMenu::CodeActions(menu)) => {
2491 if menu.visible() {
2492 key_context.add("menu");
2493 key_context.add("showing_code_actions")
2494 }
2495 }
2496 None => {}
2497 }
2498
2499 if self.signature_help_state.has_multiple_signatures() {
2500 key_context.add("showing_signature_help");
2501 }
2502
2503 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2504 if !self.focus_handle(cx).contains_focused(window, cx)
2505 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2506 {
2507 for addon in self.addons.values() {
2508 addon.extend_key_context(&mut key_context, cx)
2509 }
2510 }
2511
2512 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2513 if let Some(extension) = singleton_buffer
2514 .read(cx)
2515 .file()
2516 .and_then(|file| file.path().extension())
2517 {
2518 key_context.set("extension", extension.to_string());
2519 }
2520 } else {
2521 key_context.add("multibuffer");
2522 }
2523
2524 if has_active_edit_prediction {
2525 if self.edit_prediction_in_conflict() {
2526 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2527 } else {
2528 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2529 key_context.add("copilot_suggestion");
2530 }
2531 }
2532
2533 if self.selection_mark_mode {
2534 key_context.add("selection_mode");
2535 }
2536
2537 key_context
2538 }
2539
2540 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2541 self.last_bounds.as_ref()
2542 }
2543
2544 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2545 if self.mouse_cursor_hidden {
2546 self.mouse_cursor_hidden = false;
2547 cx.notify();
2548 }
2549 }
2550
2551 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2552 let hide_mouse_cursor = match origin {
2553 HideMouseCursorOrigin::TypingAction => {
2554 matches!(
2555 self.hide_mouse_mode,
2556 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2557 )
2558 }
2559 HideMouseCursorOrigin::MovementAction => {
2560 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2561 }
2562 };
2563 if self.mouse_cursor_hidden != hide_mouse_cursor {
2564 self.mouse_cursor_hidden = hide_mouse_cursor;
2565 cx.notify();
2566 }
2567 }
2568
2569 pub fn edit_prediction_in_conflict(&self) -> bool {
2570 if !self.show_edit_predictions_in_menu() {
2571 return false;
2572 }
2573
2574 let showing_completions = self
2575 .context_menu
2576 .borrow()
2577 .as_ref()
2578 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2579
2580 showing_completions
2581 || self.edit_prediction_requires_modifier()
2582 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2583 // bindings to insert tab characters.
2584 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2585 }
2586
2587 pub fn accept_edit_prediction_keybind(
2588 &self,
2589 accept_partial: bool,
2590 window: &Window,
2591 cx: &App,
2592 ) -> AcceptEditPredictionBinding {
2593 let key_context = self.key_context_internal(true, window, cx);
2594 let in_conflict = self.edit_prediction_in_conflict();
2595
2596 let bindings = if accept_partial {
2597 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2598 } else {
2599 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2600 };
2601
2602 // TODO: if the binding contains multiple keystrokes, display all of them, not
2603 // just the first one.
2604 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2605 !in_conflict
2606 || binding
2607 .keystrokes()
2608 .first()
2609 .is_some_and(|keystroke| keystroke.modifiers().modified())
2610 }))
2611 }
2612
2613 pub fn new_file(
2614 workspace: &mut Workspace,
2615 _: &workspace::NewFile,
2616 window: &mut Window,
2617 cx: &mut Context<Workspace>,
2618 ) {
2619 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2620 "Failed to create buffer",
2621 window,
2622 cx,
2623 |e, _, _| match e.error_code() {
2624 ErrorCode::RemoteUpgradeRequired => Some(format!(
2625 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2626 e.error_tag("required").unwrap_or("the latest version")
2627 )),
2628 _ => None,
2629 },
2630 );
2631 }
2632
2633 pub fn new_in_workspace(
2634 workspace: &mut Workspace,
2635 window: &mut Window,
2636 cx: &mut Context<Workspace>,
2637 ) -> Task<Result<Entity<Editor>>> {
2638 let project = workspace.project().clone();
2639 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2640
2641 cx.spawn_in(window, async move |workspace, cx| {
2642 let buffer = create.await?;
2643 workspace.update_in(cx, |workspace, window, cx| {
2644 let editor =
2645 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2646 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2647 editor
2648 })
2649 })
2650 }
2651
2652 fn new_file_vertical(
2653 workspace: &mut Workspace,
2654 _: &workspace::NewFileSplitVertical,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2659 }
2660
2661 fn new_file_horizontal(
2662 workspace: &mut Workspace,
2663 _: &workspace::NewFileSplitHorizontal,
2664 window: &mut Window,
2665 cx: &mut Context<Workspace>,
2666 ) {
2667 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2668 }
2669
2670 fn new_file_in_direction(
2671 workspace: &mut Workspace,
2672 direction: SplitDirection,
2673 window: &mut Window,
2674 cx: &mut Context<Workspace>,
2675 ) {
2676 let project = workspace.project().clone();
2677 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2678
2679 cx.spawn_in(window, async move |workspace, cx| {
2680 let buffer = create.await?;
2681 workspace.update_in(cx, move |workspace, window, cx| {
2682 workspace.split_item(
2683 direction,
2684 Box::new(
2685 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2686 ),
2687 window,
2688 cx,
2689 )
2690 })?;
2691 anyhow::Ok(())
2692 })
2693 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2694 match e.error_code() {
2695 ErrorCode::RemoteUpgradeRequired => Some(format!(
2696 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2697 e.error_tag("required").unwrap_or("the latest version")
2698 )),
2699 _ => None,
2700 }
2701 });
2702 }
2703
2704 pub fn leader_id(&self) -> Option<CollaboratorId> {
2705 self.leader_id
2706 }
2707
2708 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2709 &self.buffer
2710 }
2711
2712 pub fn project(&self) -> Option<&Entity<Project>> {
2713 self.project.as_ref()
2714 }
2715
2716 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2717 self.workspace.as_ref()?.0.upgrade()
2718 }
2719
2720 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2721 self.buffer().read(cx).title(cx)
2722 }
2723
2724 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2725 let git_blame_gutter_max_author_length = self
2726 .render_git_blame_gutter(cx)
2727 .then(|| {
2728 if let Some(blame) = self.blame.as_ref() {
2729 let max_author_length =
2730 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2731 Some(max_author_length)
2732 } else {
2733 None
2734 }
2735 })
2736 .flatten();
2737
2738 EditorSnapshot {
2739 mode: self.mode.clone(),
2740 show_gutter: self.show_gutter,
2741 show_line_numbers: self.show_line_numbers,
2742 show_git_diff_gutter: self.show_git_diff_gutter,
2743 show_code_actions: self.show_code_actions,
2744 show_runnables: self.show_runnables,
2745 show_breakpoints: self.show_breakpoints,
2746 git_blame_gutter_max_author_length,
2747 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2748 placeholder_display_snapshot: self
2749 .placeholder_display_map
2750 .as_ref()
2751 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2752 scroll_anchor: self.scroll_manager.anchor(),
2753 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2754 is_focused: self.focus_handle.is_focused(window),
2755 current_line_highlight: self
2756 .current_line_highlight
2757 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2758 gutter_hovered: self.gutter_hovered,
2759 }
2760 }
2761
2762 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2763 self.buffer.read(cx).language_at(point, cx)
2764 }
2765
2766 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2767 self.buffer.read(cx).read(cx).file_at(point).cloned()
2768 }
2769
2770 pub fn active_excerpt(
2771 &self,
2772 cx: &App,
2773 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2774 self.buffer
2775 .read(cx)
2776 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2777 }
2778
2779 pub fn mode(&self) -> &EditorMode {
2780 &self.mode
2781 }
2782
2783 pub fn set_mode(&mut self, mode: EditorMode) {
2784 self.mode = mode;
2785 }
2786
2787 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2788 self.collaboration_hub.as_deref()
2789 }
2790
2791 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2792 self.collaboration_hub = Some(hub);
2793 }
2794
2795 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2796 self.in_project_search = in_project_search;
2797 }
2798
2799 pub fn set_custom_context_menu(
2800 &mut self,
2801 f: impl 'static
2802 + Fn(
2803 &mut Self,
2804 DisplayPoint,
2805 &mut Window,
2806 &mut Context<Self>,
2807 ) -> Option<Entity<ui::ContextMenu>>,
2808 ) {
2809 self.custom_context_menu = Some(Box::new(f))
2810 }
2811
2812 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2813 self.completion_provider = provider;
2814 }
2815
2816 #[cfg(any(test, feature = "test-support"))]
2817 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2818 self.completion_provider.clone()
2819 }
2820
2821 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2822 self.semantics_provider.clone()
2823 }
2824
2825 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2826 self.semantics_provider = provider;
2827 }
2828
2829 pub fn set_edit_prediction_provider<T>(
2830 &mut self,
2831 provider: Option<Entity<T>>,
2832 window: &mut Window,
2833 cx: &mut Context<Self>,
2834 ) where
2835 T: EditPredictionProvider,
2836 {
2837 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2838 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2839 if this.focus_handle.is_focused(window) {
2840 this.update_visible_edit_prediction(window, cx);
2841 }
2842 }),
2843 provider: Arc::new(provider),
2844 });
2845 self.update_edit_prediction_settings(cx);
2846 self.refresh_edit_prediction(false, false, window, cx);
2847 }
2848
2849 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2850 self.placeholder_display_map
2851 .as_ref()
2852 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2853 }
2854
2855 pub fn set_placeholder_text(
2856 &mut self,
2857 placeholder_text: &str,
2858 window: &mut Window,
2859 cx: &mut Context<Self>,
2860 ) {
2861 let multibuffer = cx
2862 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2863
2864 let style = window.text_style();
2865
2866 self.placeholder_display_map = Some(cx.new(|cx| {
2867 DisplayMap::new(
2868 multibuffer,
2869 style.font(),
2870 style.font_size.to_pixels(window.rem_size()),
2871 None,
2872 FILE_HEADER_HEIGHT,
2873 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2874 Default::default(),
2875 DiagnosticSeverity::Off,
2876 cx,
2877 )
2878 }));
2879 cx.notify();
2880 }
2881
2882 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2883 self.cursor_shape = cursor_shape;
2884
2885 // Disrupt blink for immediate user feedback that the cursor shape has changed
2886 self.blink_manager.update(cx, BlinkManager::show_cursor);
2887
2888 cx.notify();
2889 }
2890
2891 pub fn set_current_line_highlight(
2892 &mut self,
2893 current_line_highlight: Option<CurrentLineHighlight>,
2894 ) {
2895 self.current_line_highlight = current_line_highlight;
2896 }
2897
2898 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2899 self.collapse_matches = collapse_matches;
2900 }
2901
2902 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2903 let buffers = self.buffer.read(cx).all_buffers();
2904 let Some(project) = self.project.as_ref() else {
2905 return;
2906 };
2907 project.update(cx, |project, cx| {
2908 for buffer in buffers {
2909 self.registered_buffers
2910 .entry(buffer.read(cx).remote_id())
2911 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2912 }
2913 })
2914 }
2915
2916 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2917 if self.collapse_matches {
2918 return range.start..range.start;
2919 }
2920 range.clone()
2921 }
2922
2923 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2924 if self.display_map.read(cx).clip_at_line_ends != clip {
2925 self.display_map
2926 .update(cx, |map, _| map.clip_at_line_ends = clip);
2927 }
2928 }
2929
2930 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2931 self.input_enabled = input_enabled;
2932 }
2933
2934 pub fn set_edit_predictions_hidden_for_vim_mode(
2935 &mut self,
2936 hidden: bool,
2937 window: &mut Window,
2938 cx: &mut Context<Self>,
2939 ) {
2940 if hidden != self.edit_predictions_hidden_for_vim_mode {
2941 self.edit_predictions_hidden_for_vim_mode = hidden;
2942 if hidden {
2943 self.update_visible_edit_prediction(window, cx);
2944 } else {
2945 self.refresh_edit_prediction(true, false, window, cx);
2946 }
2947 }
2948 }
2949
2950 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2951 self.menu_edit_predictions_policy = value;
2952 }
2953
2954 pub fn set_autoindent(&mut self, autoindent: bool) {
2955 if autoindent {
2956 self.autoindent_mode = Some(AutoindentMode::EachLine);
2957 } else {
2958 self.autoindent_mode = None;
2959 }
2960 }
2961
2962 pub fn read_only(&self, cx: &App) -> bool {
2963 self.read_only || self.buffer.read(cx).read_only()
2964 }
2965
2966 pub fn set_read_only(&mut self, read_only: bool) {
2967 self.read_only = read_only;
2968 }
2969
2970 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2971 self.use_autoclose = autoclose;
2972 }
2973
2974 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2975 self.use_auto_surround = auto_surround;
2976 }
2977
2978 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2979 self.auto_replace_emoji_shortcode = auto_replace;
2980 }
2981
2982 pub fn toggle_edit_predictions(
2983 &mut self,
2984 _: &ToggleEditPrediction,
2985 window: &mut Window,
2986 cx: &mut Context<Self>,
2987 ) {
2988 if self.show_edit_predictions_override.is_some() {
2989 self.set_show_edit_predictions(None, window, cx);
2990 } else {
2991 let show_edit_predictions = !self.edit_predictions_enabled();
2992 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2993 }
2994 }
2995
2996 pub fn set_show_edit_predictions(
2997 &mut self,
2998 show_edit_predictions: Option<bool>,
2999 window: &mut Window,
3000 cx: &mut Context<Self>,
3001 ) {
3002 self.show_edit_predictions_override = show_edit_predictions;
3003 self.update_edit_prediction_settings(cx);
3004
3005 if let Some(false) = show_edit_predictions {
3006 self.discard_edit_prediction(false, cx);
3007 } else {
3008 self.refresh_edit_prediction(false, true, window, cx);
3009 }
3010 }
3011
3012 fn edit_predictions_disabled_in_scope(
3013 &self,
3014 buffer: &Entity<Buffer>,
3015 buffer_position: language::Anchor,
3016 cx: &App,
3017 ) -> bool {
3018 let snapshot = buffer.read(cx).snapshot();
3019 let settings = snapshot.settings_at(buffer_position, cx);
3020
3021 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3022 return false;
3023 };
3024
3025 scope.override_name().is_some_and(|scope_name| {
3026 settings
3027 .edit_predictions_disabled_in
3028 .iter()
3029 .any(|s| s == scope_name)
3030 })
3031 }
3032
3033 pub fn set_use_modal_editing(&mut self, to: bool) {
3034 self.use_modal_editing = to;
3035 }
3036
3037 pub fn use_modal_editing(&self) -> bool {
3038 self.use_modal_editing
3039 }
3040
3041 fn selections_did_change(
3042 &mut self,
3043 local: bool,
3044 old_cursor_position: &Anchor,
3045 effects: SelectionEffects,
3046 window: &mut Window,
3047 cx: &mut Context<Self>,
3048 ) {
3049 window.invalidate_character_coordinates();
3050
3051 // Copy selections to primary selection buffer
3052 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3053 if local {
3054 let selections = self.selections.all::<usize>(cx);
3055 let buffer_handle = self.buffer.read(cx).read(cx);
3056
3057 let mut text = String::new();
3058 for (index, selection) in selections.iter().enumerate() {
3059 let text_for_selection = buffer_handle
3060 .text_for_range(selection.start..selection.end)
3061 .collect::<String>();
3062
3063 text.push_str(&text_for_selection);
3064 if index != selections.len() - 1 {
3065 text.push('\n');
3066 }
3067 }
3068
3069 if !text.is_empty() {
3070 cx.write_to_primary(ClipboardItem::new_string(text));
3071 }
3072 }
3073
3074 let selection_anchors = self.selections.disjoint_anchors_arc();
3075
3076 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3077 self.buffer.update(cx, |buffer, cx| {
3078 buffer.set_active_selections(
3079 &selection_anchors,
3080 self.selections.line_mode(),
3081 self.cursor_shape,
3082 cx,
3083 )
3084 });
3085 }
3086 let display_map = self
3087 .display_map
3088 .update(cx, |display_map, cx| display_map.snapshot(cx));
3089 let buffer = display_map.buffer_snapshot();
3090 if self.selections.count() == 1 {
3091 self.add_selections_state = None;
3092 }
3093 self.select_next_state = None;
3094 self.select_prev_state = None;
3095 self.select_syntax_node_history.try_clear();
3096 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3097 self.snippet_stack.invalidate(&selection_anchors, buffer);
3098 self.take_rename(false, window, cx);
3099
3100 let newest_selection = self.selections.newest_anchor();
3101 let new_cursor_position = newest_selection.head();
3102 let selection_start = newest_selection.start;
3103
3104 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3105 self.push_to_nav_history(
3106 *old_cursor_position,
3107 Some(new_cursor_position.to_point(buffer)),
3108 false,
3109 effects.nav_history == Some(true),
3110 cx,
3111 );
3112 }
3113
3114 if local {
3115 if let Some(buffer_id) = new_cursor_position.buffer_id
3116 && !self.registered_buffers.contains_key(&buffer_id)
3117 && let Some(project) = self.project.as_ref()
3118 {
3119 project.update(cx, |project, cx| {
3120 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3121 return;
3122 };
3123 self.registered_buffers.insert(
3124 buffer_id,
3125 project.register_buffer_with_language_servers(&buffer, cx),
3126 );
3127 })
3128 }
3129
3130 let mut context_menu = self.context_menu.borrow_mut();
3131 let completion_menu = match context_menu.as_ref() {
3132 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3133 Some(CodeContextMenu::CodeActions(_)) => {
3134 *context_menu = None;
3135 None
3136 }
3137 None => None,
3138 };
3139 let completion_position = completion_menu.map(|menu| menu.initial_position);
3140 drop(context_menu);
3141
3142 if effects.completions
3143 && let Some(completion_position) = completion_position
3144 {
3145 let start_offset = selection_start.to_offset(buffer);
3146 let position_matches = start_offset == completion_position.to_offset(buffer);
3147 let continue_showing = if position_matches {
3148 if self.snippet_stack.is_empty() {
3149 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3150 == Some(CharKind::Word)
3151 } else {
3152 // Snippet choices can be shown even when the cursor is in whitespace.
3153 // Dismissing the menu with actions like backspace is handled by
3154 // invalidation regions.
3155 true
3156 }
3157 } else {
3158 false
3159 };
3160
3161 if continue_showing {
3162 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3163 } else {
3164 self.hide_context_menu(window, cx);
3165 }
3166 }
3167
3168 hide_hover(self, cx);
3169
3170 if old_cursor_position.to_display_point(&display_map).row()
3171 != new_cursor_position.to_display_point(&display_map).row()
3172 {
3173 self.available_code_actions.take();
3174 }
3175 self.refresh_code_actions(window, cx);
3176 self.refresh_document_highlights(cx);
3177 self.refresh_selected_text_highlights(false, window, cx);
3178 refresh_matching_bracket_highlights(self, cx);
3179 self.update_visible_edit_prediction(window, cx);
3180 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3181 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3182 self.inline_blame_popover.take();
3183 if self.git_blame_inline_enabled {
3184 self.start_inline_blame_timer(window, cx);
3185 }
3186 }
3187
3188 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3189 cx.emit(EditorEvent::SelectionsChanged { local });
3190
3191 let selections = &self.selections.disjoint_anchors_arc();
3192 if selections.len() == 1 {
3193 cx.emit(SearchEvent::ActiveMatchChanged)
3194 }
3195 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3196 let inmemory_selections = selections
3197 .iter()
3198 .map(|s| {
3199 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3200 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3201 })
3202 .collect();
3203 self.update_restoration_data(cx, |data| {
3204 data.selections = inmemory_selections;
3205 });
3206
3207 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3208 && let Some(workspace_id) =
3209 self.workspace.as_ref().and_then(|workspace| workspace.1)
3210 {
3211 let snapshot = self.buffer().read(cx).snapshot(cx);
3212 let selections = selections.clone();
3213 let background_executor = cx.background_executor().clone();
3214 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3215 self.serialize_selections = cx.background_spawn(async move {
3216 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3217 let db_selections = selections
3218 .iter()
3219 .map(|selection| {
3220 (
3221 selection.start.to_offset(&snapshot),
3222 selection.end.to_offset(&snapshot),
3223 )
3224 })
3225 .collect();
3226
3227 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3228 .await
3229 .with_context(|| {
3230 format!(
3231 "persisting editor selections for editor {editor_id}, \
3232 workspace {workspace_id:?}"
3233 )
3234 })
3235 .log_err();
3236 });
3237 }
3238 }
3239
3240 cx.notify();
3241 }
3242
3243 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3244 use text::ToOffset as _;
3245 use text::ToPoint as _;
3246
3247 if self.mode.is_minimap()
3248 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3249 {
3250 return;
3251 }
3252
3253 if !self.buffer().read(cx).is_singleton() {
3254 return;
3255 }
3256
3257 let display_snapshot = self
3258 .display_map
3259 .update(cx, |display_map, cx| display_map.snapshot(cx));
3260 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3261 return;
3262 };
3263 let inmemory_folds = display_snapshot
3264 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3265 .map(|fold| {
3266 fold.range.start.text_anchor.to_point(&snapshot)
3267 ..fold.range.end.text_anchor.to_point(&snapshot)
3268 })
3269 .collect();
3270 self.update_restoration_data(cx, |data| {
3271 data.folds = inmemory_folds;
3272 });
3273
3274 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3275 return;
3276 };
3277 let background_executor = cx.background_executor().clone();
3278 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3279 let db_folds = display_snapshot
3280 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3281 .map(|fold| {
3282 (
3283 fold.range.start.text_anchor.to_offset(&snapshot),
3284 fold.range.end.text_anchor.to_offset(&snapshot),
3285 )
3286 })
3287 .collect();
3288 self.serialize_folds = cx.background_spawn(async move {
3289 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3290 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3291 .await
3292 .with_context(|| {
3293 format!(
3294 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3295 )
3296 })
3297 .log_err();
3298 });
3299 }
3300
3301 pub fn sync_selections(
3302 &mut self,
3303 other: Entity<Editor>,
3304 cx: &mut Context<Self>,
3305 ) -> gpui::Subscription {
3306 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3307 if !other_selections.is_empty() {
3308 self.selections.change_with(cx, |selections| {
3309 selections.select_anchors(other_selections);
3310 });
3311 }
3312
3313 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3314 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3315 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3316 if other_selections.is_empty() {
3317 return;
3318 }
3319 this.selections.change_with(cx, |selections| {
3320 selections.select_anchors(other_selections);
3321 });
3322 }
3323 });
3324
3325 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3326 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3327 let these_selections = this.selections.disjoint_anchors().to_vec();
3328 if these_selections.is_empty() {
3329 return;
3330 }
3331 other.update(cx, |other_editor, cx| {
3332 other_editor.selections.change_with(cx, |selections| {
3333 selections.select_anchors(these_selections);
3334 })
3335 });
3336 }
3337 });
3338
3339 Subscription::join(other_subscription, this_subscription)
3340 }
3341
3342 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3343 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3344 /// effects of selection change occur at the end of the transaction.
3345 pub fn change_selections<R>(
3346 &mut self,
3347 effects: SelectionEffects,
3348 window: &mut Window,
3349 cx: &mut Context<Self>,
3350 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3351 ) -> R {
3352 if let Some(state) = &mut self.deferred_selection_effects_state {
3353 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3354 state.effects.completions = effects.completions;
3355 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3356 let (changed, result) = self.selections.change_with(cx, change);
3357 state.changed |= changed;
3358 return result;
3359 }
3360 let mut state = DeferredSelectionEffectsState {
3361 changed: false,
3362 effects,
3363 old_cursor_position: self.selections.newest_anchor().head(),
3364 history_entry: SelectionHistoryEntry {
3365 selections: self.selections.disjoint_anchors_arc(),
3366 select_next_state: self.select_next_state.clone(),
3367 select_prev_state: self.select_prev_state.clone(),
3368 add_selections_state: self.add_selections_state.clone(),
3369 },
3370 };
3371 let (changed, result) = self.selections.change_with(cx, change);
3372 state.changed = state.changed || changed;
3373 if self.defer_selection_effects {
3374 self.deferred_selection_effects_state = Some(state);
3375 } else {
3376 self.apply_selection_effects(state, window, cx);
3377 }
3378 result
3379 }
3380
3381 /// Defers the effects of selection change, so that the effects of multiple calls to
3382 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3383 /// to selection history and the state of popovers based on selection position aren't
3384 /// erroneously updated.
3385 pub fn with_selection_effects_deferred<R>(
3386 &mut self,
3387 window: &mut Window,
3388 cx: &mut Context<Self>,
3389 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3390 ) -> R {
3391 let already_deferred = self.defer_selection_effects;
3392 self.defer_selection_effects = true;
3393 let result = update(self, window, cx);
3394 if !already_deferred {
3395 self.defer_selection_effects = false;
3396 if let Some(state) = self.deferred_selection_effects_state.take() {
3397 self.apply_selection_effects(state, window, cx);
3398 }
3399 }
3400 result
3401 }
3402
3403 fn apply_selection_effects(
3404 &mut self,
3405 state: DeferredSelectionEffectsState,
3406 window: &mut Window,
3407 cx: &mut Context<Self>,
3408 ) {
3409 if state.changed {
3410 self.selection_history.push(state.history_entry);
3411
3412 if let Some(autoscroll) = state.effects.scroll {
3413 self.request_autoscroll(autoscroll, cx);
3414 }
3415
3416 let old_cursor_position = &state.old_cursor_position;
3417
3418 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3419
3420 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3421 self.show_signature_help(&ShowSignatureHelp, window, cx);
3422 }
3423 }
3424 }
3425
3426 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3427 where
3428 I: IntoIterator<Item = (Range<S>, T)>,
3429 S: ToOffset,
3430 T: Into<Arc<str>>,
3431 {
3432 if self.read_only(cx) {
3433 return;
3434 }
3435
3436 self.buffer
3437 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3438 }
3439
3440 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3441 where
3442 I: IntoIterator<Item = (Range<S>, T)>,
3443 S: ToOffset,
3444 T: Into<Arc<str>>,
3445 {
3446 if self.read_only(cx) {
3447 return;
3448 }
3449
3450 self.buffer.update(cx, |buffer, cx| {
3451 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3452 });
3453 }
3454
3455 pub fn edit_with_block_indent<I, S, T>(
3456 &mut self,
3457 edits: I,
3458 original_indent_columns: Vec<Option<u32>>,
3459 cx: &mut Context<Self>,
3460 ) where
3461 I: IntoIterator<Item = (Range<S>, T)>,
3462 S: ToOffset,
3463 T: Into<Arc<str>>,
3464 {
3465 if self.read_only(cx) {
3466 return;
3467 }
3468
3469 self.buffer.update(cx, |buffer, cx| {
3470 buffer.edit(
3471 edits,
3472 Some(AutoindentMode::Block {
3473 original_indent_columns,
3474 }),
3475 cx,
3476 )
3477 });
3478 }
3479
3480 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3481 self.hide_context_menu(window, cx);
3482
3483 match phase {
3484 SelectPhase::Begin {
3485 position,
3486 add,
3487 click_count,
3488 } => self.begin_selection(position, add, click_count, window, cx),
3489 SelectPhase::BeginColumnar {
3490 position,
3491 goal_column,
3492 reset,
3493 mode,
3494 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3495 SelectPhase::Extend {
3496 position,
3497 click_count,
3498 } => self.extend_selection(position, click_count, window, cx),
3499 SelectPhase::Update {
3500 position,
3501 goal_column,
3502 scroll_delta,
3503 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3504 SelectPhase::End => self.end_selection(window, cx),
3505 }
3506 }
3507
3508 fn extend_selection(
3509 &mut self,
3510 position: DisplayPoint,
3511 click_count: usize,
3512 window: &mut Window,
3513 cx: &mut Context<Self>,
3514 ) {
3515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3516 let tail = self.selections.newest::<usize>(cx).tail();
3517 let click_count = click_count.max(match self.selections.select_mode() {
3518 SelectMode::Character => 1,
3519 SelectMode::Word(_) => 2,
3520 SelectMode::Line(_) => 3,
3521 SelectMode::All => 4,
3522 });
3523 self.begin_selection(position, false, click_count, window, cx);
3524
3525 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3526
3527 let current_selection = match self.selections.select_mode() {
3528 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3529 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3530 };
3531
3532 let mut pending_selection = self
3533 .selections
3534 .pending_anchor()
3535 .cloned()
3536 .expect("extend_selection not called with pending selection");
3537
3538 if pending_selection
3539 .start
3540 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3541 == Ordering::Greater
3542 {
3543 pending_selection.start = current_selection.start;
3544 }
3545 if pending_selection
3546 .end
3547 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3548 == Ordering::Less
3549 {
3550 pending_selection.end = current_selection.end;
3551 pending_selection.reversed = true;
3552 }
3553
3554 let mut pending_mode = self.selections.pending_mode().unwrap();
3555 match &mut pending_mode {
3556 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3557 _ => {}
3558 }
3559
3560 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3561 SelectionEffects::scroll(Autoscroll::fit())
3562 } else {
3563 SelectionEffects::no_scroll()
3564 };
3565
3566 self.change_selections(effects, window, cx, |s| {
3567 s.set_pending(pending_selection.clone(), pending_mode);
3568 s.set_is_extending(true);
3569 });
3570 }
3571
3572 fn begin_selection(
3573 &mut self,
3574 position: DisplayPoint,
3575 add: bool,
3576 click_count: usize,
3577 window: &mut Window,
3578 cx: &mut Context<Self>,
3579 ) {
3580 if !self.focus_handle.is_focused(window) {
3581 self.last_focused_descendant = None;
3582 window.focus(&self.focus_handle);
3583 }
3584
3585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3586 let buffer = display_map.buffer_snapshot();
3587 let position = display_map.clip_point(position, Bias::Left);
3588
3589 let start;
3590 let end;
3591 let mode;
3592 let mut auto_scroll;
3593 match click_count {
3594 1 => {
3595 start = buffer.anchor_before(position.to_point(&display_map));
3596 end = start;
3597 mode = SelectMode::Character;
3598 auto_scroll = true;
3599 }
3600 2 => {
3601 let position = display_map
3602 .clip_point(position, Bias::Left)
3603 .to_offset(&display_map, Bias::Left);
3604 let (range, _) = buffer.surrounding_word(position, None);
3605 start = buffer.anchor_before(range.start);
3606 end = buffer.anchor_before(range.end);
3607 mode = SelectMode::Word(start..end);
3608 auto_scroll = true;
3609 }
3610 3 => {
3611 let position = display_map
3612 .clip_point(position, Bias::Left)
3613 .to_point(&display_map);
3614 let line_start = display_map.prev_line_boundary(position).0;
3615 let next_line_start = buffer.clip_point(
3616 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3617 Bias::Left,
3618 );
3619 start = buffer.anchor_before(line_start);
3620 end = buffer.anchor_before(next_line_start);
3621 mode = SelectMode::Line(start..end);
3622 auto_scroll = true;
3623 }
3624 _ => {
3625 start = buffer.anchor_before(0);
3626 end = buffer.anchor_before(buffer.len());
3627 mode = SelectMode::All;
3628 auto_scroll = false;
3629 }
3630 }
3631 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3632
3633 let point_to_delete: Option<usize> = {
3634 let selected_points: Vec<Selection<Point>> =
3635 self.selections.disjoint_in_range(start..end, cx);
3636
3637 if !add || click_count > 1 {
3638 None
3639 } else if !selected_points.is_empty() {
3640 Some(selected_points[0].id)
3641 } else {
3642 let clicked_point_already_selected =
3643 self.selections.disjoint_anchors().iter().find(|selection| {
3644 selection.start.to_point(buffer) == start.to_point(buffer)
3645 || selection.end.to_point(buffer) == end.to_point(buffer)
3646 });
3647
3648 clicked_point_already_selected.map(|selection| selection.id)
3649 }
3650 };
3651
3652 let selections_count = self.selections.count();
3653 let effects = if auto_scroll {
3654 SelectionEffects::default()
3655 } else {
3656 SelectionEffects::no_scroll()
3657 };
3658
3659 self.change_selections(effects, window, cx, |s| {
3660 if let Some(point_to_delete) = point_to_delete {
3661 s.delete(point_to_delete);
3662
3663 if selections_count == 1 {
3664 s.set_pending_anchor_range(start..end, mode);
3665 }
3666 } else {
3667 if !add {
3668 s.clear_disjoint();
3669 }
3670
3671 s.set_pending_anchor_range(start..end, mode);
3672 }
3673 });
3674 }
3675
3676 fn begin_columnar_selection(
3677 &mut self,
3678 position: DisplayPoint,
3679 goal_column: u32,
3680 reset: bool,
3681 mode: ColumnarMode,
3682 window: &mut Window,
3683 cx: &mut Context<Self>,
3684 ) {
3685 if !self.focus_handle.is_focused(window) {
3686 self.last_focused_descendant = None;
3687 window.focus(&self.focus_handle);
3688 }
3689
3690 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3691
3692 if reset {
3693 let pointer_position = display_map
3694 .buffer_snapshot()
3695 .anchor_before(position.to_point(&display_map));
3696
3697 self.change_selections(
3698 SelectionEffects::scroll(Autoscroll::newest()),
3699 window,
3700 cx,
3701 |s| {
3702 s.clear_disjoint();
3703 s.set_pending_anchor_range(
3704 pointer_position..pointer_position,
3705 SelectMode::Character,
3706 );
3707 },
3708 );
3709 };
3710
3711 let tail = self.selections.newest::<Point>(cx).tail();
3712 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3713 self.columnar_selection_state = match mode {
3714 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3715 selection_tail: selection_anchor,
3716 display_point: if reset {
3717 if position.column() != goal_column {
3718 Some(DisplayPoint::new(position.row(), goal_column))
3719 } else {
3720 None
3721 }
3722 } else {
3723 None
3724 },
3725 }),
3726 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3727 selection_tail: selection_anchor,
3728 }),
3729 };
3730
3731 if !reset {
3732 self.select_columns(position, goal_column, &display_map, window, cx);
3733 }
3734 }
3735
3736 fn update_selection(
3737 &mut self,
3738 position: DisplayPoint,
3739 goal_column: u32,
3740 scroll_delta: gpui::Point<f32>,
3741 window: &mut Window,
3742 cx: &mut Context<Self>,
3743 ) {
3744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3745
3746 if self.columnar_selection_state.is_some() {
3747 self.select_columns(position, goal_column, &display_map, window, cx);
3748 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3749 let buffer = display_map.buffer_snapshot();
3750 let head;
3751 let tail;
3752 let mode = self.selections.pending_mode().unwrap();
3753 match &mode {
3754 SelectMode::Character => {
3755 head = position.to_point(&display_map);
3756 tail = pending.tail().to_point(buffer);
3757 }
3758 SelectMode::Word(original_range) => {
3759 let offset = display_map
3760 .clip_point(position, Bias::Left)
3761 .to_offset(&display_map, Bias::Left);
3762 let original_range = original_range.to_offset(buffer);
3763
3764 let head_offset = if buffer.is_inside_word(offset, None)
3765 || original_range.contains(&offset)
3766 {
3767 let (word_range, _) = buffer.surrounding_word(offset, None);
3768 if word_range.start < original_range.start {
3769 word_range.start
3770 } else {
3771 word_range.end
3772 }
3773 } else {
3774 offset
3775 };
3776
3777 head = head_offset.to_point(buffer);
3778 if head_offset <= original_range.start {
3779 tail = original_range.end.to_point(buffer);
3780 } else {
3781 tail = original_range.start.to_point(buffer);
3782 }
3783 }
3784 SelectMode::Line(original_range) => {
3785 let original_range = original_range.to_point(display_map.buffer_snapshot());
3786
3787 let position = display_map
3788 .clip_point(position, Bias::Left)
3789 .to_point(&display_map);
3790 let line_start = display_map.prev_line_boundary(position).0;
3791 let next_line_start = buffer.clip_point(
3792 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3793 Bias::Left,
3794 );
3795
3796 if line_start < original_range.start {
3797 head = line_start
3798 } else {
3799 head = next_line_start
3800 }
3801
3802 if head <= original_range.start {
3803 tail = original_range.end;
3804 } else {
3805 tail = original_range.start;
3806 }
3807 }
3808 SelectMode::All => {
3809 return;
3810 }
3811 };
3812
3813 if head < tail {
3814 pending.start = buffer.anchor_before(head);
3815 pending.end = buffer.anchor_before(tail);
3816 pending.reversed = true;
3817 } else {
3818 pending.start = buffer.anchor_before(tail);
3819 pending.end = buffer.anchor_before(head);
3820 pending.reversed = false;
3821 }
3822
3823 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3824 s.set_pending(pending.clone(), mode);
3825 });
3826 } else {
3827 log::error!("update_selection dispatched with no pending selection");
3828 return;
3829 }
3830
3831 self.apply_scroll_delta(scroll_delta, window, cx);
3832 cx.notify();
3833 }
3834
3835 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3836 self.columnar_selection_state.take();
3837 if let Some(pending_mode) = self.selections.pending_mode() {
3838 let selections = self.selections.all::<usize>(cx);
3839 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3840 s.select(selections);
3841 s.clear_pending();
3842 if s.is_extending() {
3843 s.set_is_extending(false);
3844 } else {
3845 s.set_select_mode(pending_mode);
3846 }
3847 });
3848 }
3849 }
3850
3851 fn select_columns(
3852 &mut self,
3853 head: DisplayPoint,
3854 goal_column: u32,
3855 display_map: &DisplaySnapshot,
3856 window: &mut Window,
3857 cx: &mut Context<Self>,
3858 ) {
3859 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3860 return;
3861 };
3862
3863 let tail = match columnar_state {
3864 ColumnarSelectionState::FromMouse {
3865 selection_tail,
3866 display_point,
3867 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3868 ColumnarSelectionState::FromSelection { selection_tail } => {
3869 selection_tail.to_display_point(display_map)
3870 }
3871 };
3872
3873 let start_row = cmp::min(tail.row(), head.row());
3874 let end_row = cmp::max(tail.row(), head.row());
3875 let start_column = cmp::min(tail.column(), goal_column);
3876 let end_column = cmp::max(tail.column(), goal_column);
3877 let reversed = start_column < tail.column();
3878
3879 let selection_ranges = (start_row.0..=end_row.0)
3880 .map(DisplayRow)
3881 .filter_map(|row| {
3882 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3883 || start_column <= display_map.line_len(row))
3884 && !display_map.is_block_line(row)
3885 {
3886 let start = display_map
3887 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3888 .to_point(display_map);
3889 let end = display_map
3890 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3891 .to_point(display_map);
3892 if reversed {
3893 Some(end..start)
3894 } else {
3895 Some(start..end)
3896 }
3897 } else {
3898 None
3899 }
3900 })
3901 .collect::<Vec<_>>();
3902
3903 let ranges = match columnar_state {
3904 ColumnarSelectionState::FromMouse { .. } => {
3905 let mut non_empty_ranges = selection_ranges
3906 .iter()
3907 .filter(|selection_range| selection_range.start != selection_range.end)
3908 .peekable();
3909 if non_empty_ranges.peek().is_some() {
3910 non_empty_ranges.cloned().collect()
3911 } else {
3912 selection_ranges
3913 }
3914 }
3915 _ => selection_ranges,
3916 };
3917
3918 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3919 s.select_ranges(ranges);
3920 });
3921 cx.notify();
3922 }
3923
3924 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3925 self.selections
3926 .all_adjusted(cx)
3927 .iter()
3928 .any(|selection| !selection.is_empty())
3929 }
3930
3931 pub fn has_pending_nonempty_selection(&self) -> bool {
3932 let pending_nonempty_selection = match self.selections.pending_anchor() {
3933 Some(Selection { start, end, .. }) => start != end,
3934 None => false,
3935 };
3936
3937 pending_nonempty_selection
3938 || (self.columnar_selection_state.is_some()
3939 && self.selections.disjoint_anchors().len() > 1)
3940 }
3941
3942 pub fn has_pending_selection(&self) -> bool {
3943 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3944 }
3945
3946 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3947 self.selection_mark_mode = false;
3948 self.selection_drag_state = SelectionDragState::None;
3949
3950 if self.clear_expanded_diff_hunks(cx) {
3951 cx.notify();
3952 return;
3953 }
3954 if self.dismiss_menus_and_popups(true, window, cx) {
3955 return;
3956 }
3957
3958 if self.mode.is_full()
3959 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3960 {
3961 return;
3962 }
3963
3964 cx.propagate();
3965 }
3966
3967 pub fn dismiss_menus_and_popups(
3968 &mut self,
3969 is_user_requested: bool,
3970 window: &mut Window,
3971 cx: &mut Context<Self>,
3972 ) -> bool {
3973 if self.take_rename(false, window, cx).is_some() {
3974 return true;
3975 }
3976
3977 if hide_hover(self, cx) {
3978 return true;
3979 }
3980
3981 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3982 return true;
3983 }
3984
3985 if self.hide_context_menu(window, cx).is_some() {
3986 return true;
3987 }
3988
3989 if self.mouse_context_menu.take().is_some() {
3990 return true;
3991 }
3992
3993 if is_user_requested && self.discard_edit_prediction(true, cx) {
3994 return true;
3995 }
3996
3997 if self.snippet_stack.pop().is_some() {
3998 return true;
3999 }
4000
4001 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4002 self.dismiss_diagnostics(cx);
4003 return true;
4004 }
4005
4006 false
4007 }
4008
4009 fn linked_editing_ranges_for(
4010 &self,
4011 selection: Range<text::Anchor>,
4012 cx: &App,
4013 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4014 if self.linked_edit_ranges.is_empty() {
4015 return None;
4016 }
4017 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4018 selection.end.buffer_id.and_then(|end_buffer_id| {
4019 if selection.start.buffer_id != Some(end_buffer_id) {
4020 return None;
4021 }
4022 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4023 let snapshot = buffer.read(cx).snapshot();
4024 self.linked_edit_ranges
4025 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4026 .map(|ranges| (ranges, snapshot, buffer))
4027 })?;
4028 use text::ToOffset as TO;
4029 // find offset from the start of current range to current cursor position
4030 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4031
4032 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4033 let start_difference = start_offset - start_byte_offset;
4034 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4035 let end_difference = end_offset - start_byte_offset;
4036 // Current range has associated linked ranges.
4037 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4038 for range in linked_ranges.iter() {
4039 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4040 let end_offset = start_offset + end_difference;
4041 let start_offset = start_offset + start_difference;
4042 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4043 continue;
4044 }
4045 if self.selections.disjoint_anchor_ranges().any(|s| {
4046 if s.start.buffer_id != selection.start.buffer_id
4047 || s.end.buffer_id != selection.end.buffer_id
4048 {
4049 return false;
4050 }
4051 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4052 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4053 }) {
4054 continue;
4055 }
4056 let start = buffer_snapshot.anchor_after(start_offset);
4057 let end = buffer_snapshot.anchor_after(end_offset);
4058 linked_edits
4059 .entry(buffer.clone())
4060 .or_default()
4061 .push(start..end);
4062 }
4063 Some(linked_edits)
4064 }
4065
4066 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4067 let text: Arc<str> = text.into();
4068
4069 if self.read_only(cx) {
4070 return;
4071 }
4072
4073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4074
4075 let selections = self.selections.all_adjusted(cx);
4076 let mut bracket_inserted = false;
4077 let mut edits = Vec::new();
4078 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4079 let mut new_selections = Vec::with_capacity(selections.len());
4080 let mut new_autoclose_regions = Vec::new();
4081 let snapshot = self.buffer.read(cx).read(cx);
4082 let mut clear_linked_edit_ranges = false;
4083
4084 for (selection, autoclose_region) in
4085 self.selections_with_autoclose_regions(selections, &snapshot)
4086 {
4087 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4088 // Determine if the inserted text matches the opening or closing
4089 // bracket of any of this language's bracket pairs.
4090 let mut bracket_pair = None;
4091 let mut is_bracket_pair_start = false;
4092 let mut is_bracket_pair_end = false;
4093 if !text.is_empty() {
4094 let mut bracket_pair_matching_end = None;
4095 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4096 // and they are removing the character that triggered IME popup.
4097 for (pair, enabled) in scope.brackets() {
4098 if !pair.close && !pair.surround {
4099 continue;
4100 }
4101
4102 if enabled && pair.start.ends_with(text.as_ref()) {
4103 let prefix_len = pair.start.len() - text.len();
4104 let preceding_text_matches_prefix = prefix_len == 0
4105 || (selection.start.column >= (prefix_len as u32)
4106 && snapshot.contains_str_at(
4107 Point::new(
4108 selection.start.row,
4109 selection.start.column - (prefix_len as u32),
4110 ),
4111 &pair.start[..prefix_len],
4112 ));
4113 if preceding_text_matches_prefix {
4114 bracket_pair = Some(pair.clone());
4115 is_bracket_pair_start = true;
4116 break;
4117 }
4118 }
4119 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4120 {
4121 // take first bracket pair matching end, but don't break in case a later bracket
4122 // pair matches start
4123 bracket_pair_matching_end = Some(pair.clone());
4124 }
4125 }
4126 if let Some(end) = bracket_pair_matching_end
4127 && bracket_pair.is_none()
4128 {
4129 bracket_pair = Some(end);
4130 is_bracket_pair_end = true;
4131 }
4132 }
4133
4134 if let Some(bracket_pair) = bracket_pair {
4135 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4136 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4137 let auto_surround =
4138 self.use_auto_surround && snapshot_settings.use_auto_surround;
4139 if selection.is_empty() {
4140 if is_bracket_pair_start {
4141 // If the inserted text is a suffix of an opening bracket and the
4142 // selection is preceded by the rest of the opening bracket, then
4143 // insert the closing bracket.
4144 let following_text_allows_autoclose = snapshot
4145 .chars_at(selection.start)
4146 .next()
4147 .is_none_or(|c| scope.should_autoclose_before(c));
4148
4149 let preceding_text_allows_autoclose = selection.start.column == 0
4150 || snapshot
4151 .reversed_chars_at(selection.start)
4152 .next()
4153 .is_none_or(|c| {
4154 bracket_pair.start != bracket_pair.end
4155 || !snapshot
4156 .char_classifier_at(selection.start)
4157 .is_word(c)
4158 });
4159
4160 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4161 && bracket_pair.start.len() == 1
4162 {
4163 let target = bracket_pair.start.chars().next().unwrap();
4164 let current_line_count = snapshot
4165 .reversed_chars_at(selection.start)
4166 .take_while(|&c| c != '\n')
4167 .filter(|&c| c == target)
4168 .count();
4169 current_line_count % 2 == 1
4170 } else {
4171 false
4172 };
4173
4174 if autoclose
4175 && bracket_pair.close
4176 && following_text_allows_autoclose
4177 && preceding_text_allows_autoclose
4178 && !is_closing_quote
4179 {
4180 let anchor = snapshot.anchor_before(selection.end);
4181 new_selections.push((selection.map(|_| anchor), text.len()));
4182 new_autoclose_regions.push((
4183 anchor,
4184 text.len(),
4185 selection.id,
4186 bracket_pair.clone(),
4187 ));
4188 edits.push((
4189 selection.range(),
4190 format!("{}{}", text, bracket_pair.end).into(),
4191 ));
4192 bracket_inserted = true;
4193 continue;
4194 }
4195 }
4196
4197 if let Some(region) = autoclose_region {
4198 // If the selection is followed by an auto-inserted closing bracket,
4199 // then don't insert that closing bracket again; just move the selection
4200 // past the closing bracket.
4201 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4202 && text.as_ref() == region.pair.end.as_str()
4203 && snapshot.contains_str_at(region.range.end, text.as_ref());
4204 if should_skip {
4205 let anchor = snapshot.anchor_after(selection.end);
4206 new_selections
4207 .push((selection.map(|_| anchor), region.pair.end.len()));
4208 continue;
4209 }
4210 }
4211
4212 let always_treat_brackets_as_autoclosed = snapshot
4213 .language_settings_at(selection.start, cx)
4214 .always_treat_brackets_as_autoclosed;
4215 if always_treat_brackets_as_autoclosed
4216 && is_bracket_pair_end
4217 && snapshot.contains_str_at(selection.end, text.as_ref())
4218 {
4219 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4220 // and the inserted text is a closing bracket and the selection is followed
4221 // by the closing bracket then move the selection past the closing bracket.
4222 let anchor = snapshot.anchor_after(selection.end);
4223 new_selections.push((selection.map(|_| anchor), text.len()));
4224 continue;
4225 }
4226 }
4227 // If an opening bracket is 1 character long and is typed while
4228 // text is selected, then surround that text with the bracket pair.
4229 else if auto_surround
4230 && bracket_pair.surround
4231 && is_bracket_pair_start
4232 && bracket_pair.start.chars().count() == 1
4233 {
4234 edits.push((selection.start..selection.start, text.clone()));
4235 edits.push((
4236 selection.end..selection.end,
4237 bracket_pair.end.as_str().into(),
4238 ));
4239 bracket_inserted = true;
4240 new_selections.push((
4241 Selection {
4242 id: selection.id,
4243 start: snapshot.anchor_after(selection.start),
4244 end: snapshot.anchor_before(selection.end),
4245 reversed: selection.reversed,
4246 goal: selection.goal,
4247 },
4248 0,
4249 ));
4250 continue;
4251 }
4252 }
4253 }
4254
4255 if self.auto_replace_emoji_shortcode
4256 && selection.is_empty()
4257 && text.as_ref().ends_with(':')
4258 && let Some(possible_emoji_short_code) =
4259 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4260 && !possible_emoji_short_code.is_empty()
4261 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4262 {
4263 let emoji_shortcode_start = Point::new(
4264 selection.start.row,
4265 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4266 );
4267
4268 // Remove shortcode from buffer
4269 edits.push((
4270 emoji_shortcode_start..selection.start,
4271 "".to_string().into(),
4272 ));
4273 new_selections.push((
4274 Selection {
4275 id: selection.id,
4276 start: snapshot.anchor_after(emoji_shortcode_start),
4277 end: snapshot.anchor_before(selection.start),
4278 reversed: selection.reversed,
4279 goal: selection.goal,
4280 },
4281 0,
4282 ));
4283
4284 // Insert emoji
4285 let selection_start_anchor = snapshot.anchor_after(selection.start);
4286 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4287 edits.push((selection.start..selection.end, emoji.to_string().into()));
4288
4289 continue;
4290 }
4291
4292 // If not handling any auto-close operation, then just replace the selected
4293 // text with the given input and move the selection to the end of the
4294 // newly inserted text.
4295 let anchor = snapshot.anchor_after(selection.end);
4296 if !self.linked_edit_ranges.is_empty() {
4297 let start_anchor = snapshot.anchor_before(selection.start);
4298
4299 let is_word_char = text.chars().next().is_none_or(|char| {
4300 let classifier = snapshot
4301 .char_classifier_at(start_anchor.to_offset(&snapshot))
4302 .scope_context(Some(CharScopeContext::LinkedEdit));
4303 classifier.is_word(char)
4304 });
4305
4306 if is_word_char {
4307 if let Some(ranges) = self
4308 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4309 {
4310 for (buffer, edits) in ranges {
4311 linked_edits
4312 .entry(buffer.clone())
4313 .or_default()
4314 .extend(edits.into_iter().map(|range| (range, text.clone())));
4315 }
4316 }
4317 } else {
4318 clear_linked_edit_ranges = true;
4319 }
4320 }
4321
4322 new_selections.push((selection.map(|_| anchor), 0));
4323 edits.push((selection.start..selection.end, text.clone()));
4324 }
4325
4326 drop(snapshot);
4327
4328 self.transact(window, cx, |this, window, cx| {
4329 if clear_linked_edit_ranges {
4330 this.linked_edit_ranges.clear();
4331 }
4332 let initial_buffer_versions =
4333 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4334
4335 this.buffer.update(cx, |buffer, cx| {
4336 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4337 });
4338 for (buffer, edits) in linked_edits {
4339 buffer.update(cx, |buffer, cx| {
4340 let snapshot = buffer.snapshot();
4341 let edits = edits
4342 .into_iter()
4343 .map(|(range, text)| {
4344 use text::ToPoint as TP;
4345 let end_point = TP::to_point(&range.end, &snapshot);
4346 let start_point = TP::to_point(&range.start, &snapshot);
4347 (start_point..end_point, text)
4348 })
4349 .sorted_by_key(|(range, _)| range.start);
4350 buffer.edit(edits, None, cx);
4351 })
4352 }
4353 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4354 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4355 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4356 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4357 .zip(new_selection_deltas)
4358 .map(|(selection, delta)| Selection {
4359 id: selection.id,
4360 start: selection.start + delta,
4361 end: selection.end + delta,
4362 reversed: selection.reversed,
4363 goal: SelectionGoal::None,
4364 })
4365 .collect::<Vec<_>>();
4366
4367 let mut i = 0;
4368 for (position, delta, selection_id, pair) in new_autoclose_regions {
4369 let position = position.to_offset(map.buffer_snapshot()) + delta;
4370 let start = map.buffer_snapshot().anchor_before(position);
4371 let end = map.buffer_snapshot().anchor_after(position);
4372 while let Some(existing_state) = this.autoclose_regions.get(i) {
4373 match existing_state
4374 .range
4375 .start
4376 .cmp(&start, map.buffer_snapshot())
4377 {
4378 Ordering::Less => i += 1,
4379 Ordering::Greater => break,
4380 Ordering::Equal => {
4381 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4382 Ordering::Less => i += 1,
4383 Ordering::Equal => break,
4384 Ordering::Greater => break,
4385 }
4386 }
4387 }
4388 }
4389 this.autoclose_regions.insert(
4390 i,
4391 AutocloseRegion {
4392 selection_id,
4393 range: start..end,
4394 pair,
4395 },
4396 );
4397 }
4398
4399 let had_active_edit_prediction = this.has_active_edit_prediction();
4400 this.change_selections(
4401 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4402 window,
4403 cx,
4404 |s| s.select(new_selections),
4405 );
4406
4407 if !bracket_inserted
4408 && let Some(on_type_format_task) =
4409 this.trigger_on_type_formatting(text.to_string(), window, cx)
4410 {
4411 on_type_format_task.detach_and_log_err(cx);
4412 }
4413
4414 let editor_settings = EditorSettings::get_global(cx);
4415 if bracket_inserted
4416 && (editor_settings.auto_signature_help
4417 || editor_settings.show_signature_help_after_edits)
4418 {
4419 this.show_signature_help(&ShowSignatureHelp, window, cx);
4420 }
4421
4422 let trigger_in_words =
4423 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4424 if this.hard_wrap.is_some() {
4425 let latest: Range<Point> = this.selections.newest(cx).range();
4426 if latest.is_empty()
4427 && this
4428 .buffer()
4429 .read(cx)
4430 .snapshot(cx)
4431 .line_len(MultiBufferRow(latest.start.row))
4432 == latest.start.column
4433 {
4434 this.rewrap_impl(
4435 RewrapOptions {
4436 override_language_settings: true,
4437 preserve_existing_whitespace: true,
4438 },
4439 cx,
4440 )
4441 }
4442 }
4443 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4444 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4445 this.refresh_edit_prediction(true, false, window, cx);
4446 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4447 });
4448 }
4449
4450 fn find_possible_emoji_shortcode_at_position(
4451 snapshot: &MultiBufferSnapshot,
4452 position: Point,
4453 ) -> Option<String> {
4454 let mut chars = Vec::new();
4455 let mut found_colon = false;
4456 for char in snapshot.reversed_chars_at(position).take(100) {
4457 // Found a possible emoji shortcode in the middle of the buffer
4458 if found_colon {
4459 if char.is_whitespace() {
4460 chars.reverse();
4461 return Some(chars.iter().collect());
4462 }
4463 // If the previous character is not a whitespace, we are in the middle of a word
4464 // and we only want to complete the shortcode if the word is made up of other emojis
4465 let mut containing_word = String::new();
4466 for ch in snapshot
4467 .reversed_chars_at(position)
4468 .skip(chars.len() + 1)
4469 .take(100)
4470 {
4471 if ch.is_whitespace() {
4472 break;
4473 }
4474 containing_word.push(ch);
4475 }
4476 let containing_word = containing_word.chars().rev().collect::<String>();
4477 if util::word_consists_of_emojis(containing_word.as_str()) {
4478 chars.reverse();
4479 return Some(chars.iter().collect());
4480 }
4481 }
4482
4483 if char.is_whitespace() || !char.is_ascii() {
4484 return None;
4485 }
4486 if char == ':' {
4487 found_colon = true;
4488 } else {
4489 chars.push(char);
4490 }
4491 }
4492 // Found a possible emoji shortcode at the beginning of the buffer
4493 chars.reverse();
4494 Some(chars.iter().collect())
4495 }
4496
4497 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4498 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4499 self.transact(window, cx, |this, window, cx| {
4500 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4501 let selections = this.selections.all::<usize>(cx);
4502 let multi_buffer = this.buffer.read(cx);
4503 let buffer = multi_buffer.snapshot(cx);
4504 selections
4505 .iter()
4506 .map(|selection| {
4507 let start_point = selection.start.to_point(&buffer);
4508 let mut existing_indent =
4509 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4510 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4511 let start = selection.start;
4512 let end = selection.end;
4513 let selection_is_empty = start == end;
4514 let language_scope = buffer.language_scope_at(start);
4515 let (
4516 comment_delimiter,
4517 doc_delimiter,
4518 insert_extra_newline,
4519 indent_on_newline,
4520 indent_on_extra_newline,
4521 ) = if let Some(language) = &language_scope {
4522 let mut insert_extra_newline =
4523 insert_extra_newline_brackets(&buffer, start..end, language)
4524 || insert_extra_newline_tree_sitter(&buffer, start..end);
4525
4526 // Comment extension on newline is allowed only for cursor selections
4527 let comment_delimiter = maybe!({
4528 if !selection_is_empty {
4529 return None;
4530 }
4531
4532 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4533 return None;
4534 }
4535
4536 let delimiters = language.line_comment_prefixes();
4537 let max_len_of_delimiter =
4538 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4539 let (snapshot, range) =
4540 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4541
4542 let num_of_whitespaces = snapshot
4543 .chars_for_range(range.clone())
4544 .take_while(|c| c.is_whitespace())
4545 .count();
4546 let comment_candidate = snapshot
4547 .chars_for_range(range.clone())
4548 .skip(num_of_whitespaces)
4549 .take(max_len_of_delimiter)
4550 .collect::<String>();
4551 let (delimiter, trimmed_len) = delimiters
4552 .iter()
4553 .filter_map(|delimiter| {
4554 let prefix = delimiter.trim_end();
4555 if comment_candidate.starts_with(prefix) {
4556 Some((delimiter, prefix.len()))
4557 } else {
4558 None
4559 }
4560 })
4561 .max_by_key(|(_, len)| *len)?;
4562
4563 if let Some(BlockCommentConfig {
4564 start: block_start, ..
4565 }) = language.block_comment()
4566 {
4567 let block_start_trimmed = block_start.trim_end();
4568 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4569 let line_content = snapshot
4570 .chars_for_range(range)
4571 .skip(num_of_whitespaces)
4572 .take(block_start_trimmed.len())
4573 .collect::<String>();
4574
4575 if line_content.starts_with(block_start_trimmed) {
4576 return None;
4577 }
4578 }
4579 }
4580
4581 let cursor_is_placed_after_comment_marker =
4582 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4583 if cursor_is_placed_after_comment_marker {
4584 Some(delimiter.clone())
4585 } else {
4586 None
4587 }
4588 });
4589
4590 let mut indent_on_newline = IndentSize::spaces(0);
4591 let mut indent_on_extra_newline = IndentSize::spaces(0);
4592
4593 let doc_delimiter = maybe!({
4594 if !selection_is_empty {
4595 return None;
4596 }
4597
4598 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4599 return None;
4600 }
4601
4602 let BlockCommentConfig {
4603 start: start_tag,
4604 end: end_tag,
4605 prefix: delimiter,
4606 tab_size: len,
4607 } = language.documentation_comment()?;
4608 let is_within_block_comment = buffer
4609 .language_scope_at(start_point)
4610 .is_some_and(|scope| scope.override_name() == Some("comment"));
4611 if !is_within_block_comment {
4612 return None;
4613 }
4614
4615 let (snapshot, range) =
4616 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4617
4618 let num_of_whitespaces = snapshot
4619 .chars_for_range(range.clone())
4620 .take_while(|c| c.is_whitespace())
4621 .count();
4622
4623 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4624 let column = start_point.column;
4625 let cursor_is_after_start_tag = {
4626 let start_tag_len = start_tag.len();
4627 let start_tag_line = snapshot
4628 .chars_for_range(range.clone())
4629 .skip(num_of_whitespaces)
4630 .take(start_tag_len)
4631 .collect::<String>();
4632 if start_tag_line.starts_with(start_tag.as_ref()) {
4633 num_of_whitespaces + start_tag_len <= column as usize
4634 } else {
4635 false
4636 }
4637 };
4638
4639 let cursor_is_after_delimiter = {
4640 let delimiter_trim = delimiter.trim_end();
4641 let delimiter_line = snapshot
4642 .chars_for_range(range.clone())
4643 .skip(num_of_whitespaces)
4644 .take(delimiter_trim.len())
4645 .collect::<String>();
4646 if delimiter_line.starts_with(delimiter_trim) {
4647 num_of_whitespaces + delimiter_trim.len() <= column as usize
4648 } else {
4649 false
4650 }
4651 };
4652
4653 let cursor_is_before_end_tag_if_exists = {
4654 let mut char_position = 0u32;
4655 let mut end_tag_offset = None;
4656
4657 'outer: for chunk in snapshot.text_for_range(range) {
4658 if let Some(byte_pos) = chunk.find(&**end_tag) {
4659 let chars_before_match =
4660 chunk[..byte_pos].chars().count() as u32;
4661 end_tag_offset =
4662 Some(char_position + chars_before_match);
4663 break 'outer;
4664 }
4665 char_position += chunk.chars().count() as u32;
4666 }
4667
4668 if let Some(end_tag_offset) = end_tag_offset {
4669 let cursor_is_before_end_tag = column <= end_tag_offset;
4670 if cursor_is_after_start_tag {
4671 if cursor_is_before_end_tag {
4672 insert_extra_newline = true;
4673 }
4674 let cursor_is_at_start_of_end_tag =
4675 column == end_tag_offset;
4676 if cursor_is_at_start_of_end_tag {
4677 indent_on_extra_newline.len = *len;
4678 }
4679 }
4680 cursor_is_before_end_tag
4681 } else {
4682 true
4683 }
4684 };
4685
4686 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4687 && cursor_is_before_end_tag_if_exists
4688 {
4689 if cursor_is_after_start_tag {
4690 indent_on_newline.len = *len;
4691 }
4692 Some(delimiter.clone())
4693 } else {
4694 None
4695 }
4696 });
4697
4698 (
4699 comment_delimiter,
4700 doc_delimiter,
4701 insert_extra_newline,
4702 indent_on_newline,
4703 indent_on_extra_newline,
4704 )
4705 } else {
4706 (
4707 None,
4708 None,
4709 false,
4710 IndentSize::default(),
4711 IndentSize::default(),
4712 )
4713 };
4714
4715 let prevent_auto_indent = doc_delimiter.is_some();
4716 let delimiter = comment_delimiter.or(doc_delimiter);
4717
4718 let capacity_for_delimiter =
4719 delimiter.as_deref().map(str::len).unwrap_or_default();
4720 let mut new_text = String::with_capacity(
4721 1 + capacity_for_delimiter
4722 + existing_indent.len as usize
4723 + indent_on_newline.len as usize
4724 + indent_on_extra_newline.len as usize,
4725 );
4726 new_text.push('\n');
4727 new_text.extend(existing_indent.chars());
4728 new_text.extend(indent_on_newline.chars());
4729
4730 if let Some(delimiter) = &delimiter {
4731 new_text.push_str(delimiter);
4732 }
4733
4734 if insert_extra_newline {
4735 new_text.push('\n');
4736 new_text.extend(existing_indent.chars());
4737 new_text.extend(indent_on_extra_newline.chars());
4738 }
4739
4740 let anchor = buffer.anchor_after(end);
4741 let new_selection = selection.map(|_| anchor);
4742 (
4743 ((start..end, new_text), prevent_auto_indent),
4744 (insert_extra_newline, new_selection),
4745 )
4746 })
4747 .unzip()
4748 };
4749
4750 let mut auto_indent_edits = Vec::new();
4751 let mut edits = Vec::new();
4752 for (edit, prevent_auto_indent) in edits_with_flags {
4753 if prevent_auto_indent {
4754 edits.push(edit);
4755 } else {
4756 auto_indent_edits.push(edit);
4757 }
4758 }
4759 if !edits.is_empty() {
4760 this.edit(edits, cx);
4761 }
4762 if !auto_indent_edits.is_empty() {
4763 this.edit_with_autoindent(auto_indent_edits, cx);
4764 }
4765
4766 let buffer = this.buffer.read(cx).snapshot(cx);
4767 let new_selections = selection_info
4768 .into_iter()
4769 .map(|(extra_newline_inserted, new_selection)| {
4770 let mut cursor = new_selection.end.to_point(&buffer);
4771 if extra_newline_inserted {
4772 cursor.row -= 1;
4773 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4774 }
4775 new_selection.map(|_| cursor)
4776 })
4777 .collect();
4778
4779 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4780 this.refresh_edit_prediction(true, false, window, cx);
4781 });
4782 }
4783
4784 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4786
4787 let buffer = self.buffer.read(cx);
4788 let snapshot = buffer.snapshot(cx);
4789
4790 let mut edits = Vec::new();
4791 let mut rows = Vec::new();
4792
4793 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4794 let cursor = selection.head();
4795 let row = cursor.row;
4796
4797 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4798
4799 let newline = "\n".to_string();
4800 edits.push((start_of_line..start_of_line, newline));
4801
4802 rows.push(row + rows_inserted as u32);
4803 }
4804
4805 self.transact(window, cx, |editor, window, cx| {
4806 editor.edit(edits, cx);
4807
4808 editor.change_selections(Default::default(), window, cx, |s| {
4809 let mut index = 0;
4810 s.move_cursors_with(|map, _, _| {
4811 let row = rows[index];
4812 index += 1;
4813
4814 let point = Point::new(row, 0);
4815 let boundary = map.next_line_boundary(point).1;
4816 let clipped = map.clip_point(boundary, Bias::Left);
4817
4818 (clipped, SelectionGoal::None)
4819 });
4820 });
4821
4822 let mut indent_edits = Vec::new();
4823 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4824 for row in rows {
4825 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4826 for (row, indent) in indents {
4827 if indent.len == 0 {
4828 continue;
4829 }
4830
4831 let text = match indent.kind {
4832 IndentKind::Space => " ".repeat(indent.len as usize),
4833 IndentKind::Tab => "\t".repeat(indent.len as usize),
4834 };
4835 let point = Point::new(row.0, 0);
4836 indent_edits.push((point..point, text));
4837 }
4838 }
4839 editor.edit(indent_edits, cx);
4840 });
4841 }
4842
4843 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4845
4846 let buffer = self.buffer.read(cx);
4847 let snapshot = buffer.snapshot(cx);
4848
4849 let mut edits = Vec::new();
4850 let mut rows = Vec::new();
4851 let mut rows_inserted = 0;
4852
4853 for selection in self.selections.all_adjusted(cx) {
4854 let cursor = selection.head();
4855 let row = cursor.row;
4856
4857 let point = Point::new(row + 1, 0);
4858 let start_of_line = snapshot.clip_point(point, Bias::Left);
4859
4860 let newline = "\n".to_string();
4861 edits.push((start_of_line..start_of_line, newline));
4862
4863 rows_inserted += 1;
4864 rows.push(row + rows_inserted);
4865 }
4866
4867 self.transact(window, cx, |editor, window, cx| {
4868 editor.edit(edits, cx);
4869
4870 editor.change_selections(Default::default(), window, cx, |s| {
4871 let mut index = 0;
4872 s.move_cursors_with(|map, _, _| {
4873 let row = rows[index];
4874 index += 1;
4875
4876 let point = Point::new(row, 0);
4877 let boundary = map.next_line_boundary(point).1;
4878 let clipped = map.clip_point(boundary, Bias::Left);
4879
4880 (clipped, SelectionGoal::None)
4881 });
4882 });
4883
4884 let mut indent_edits = Vec::new();
4885 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4886 for row in rows {
4887 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4888 for (row, indent) in indents {
4889 if indent.len == 0 {
4890 continue;
4891 }
4892
4893 let text = match indent.kind {
4894 IndentKind::Space => " ".repeat(indent.len as usize),
4895 IndentKind::Tab => "\t".repeat(indent.len as usize),
4896 };
4897 let point = Point::new(row.0, 0);
4898 indent_edits.push((point..point, text));
4899 }
4900 }
4901 editor.edit(indent_edits, cx);
4902 });
4903 }
4904
4905 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4906 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4907 original_indent_columns: Vec::new(),
4908 });
4909 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4910 }
4911
4912 fn insert_with_autoindent_mode(
4913 &mut self,
4914 text: &str,
4915 autoindent_mode: Option<AutoindentMode>,
4916 window: &mut Window,
4917 cx: &mut Context<Self>,
4918 ) {
4919 if self.read_only(cx) {
4920 return;
4921 }
4922
4923 let text: Arc<str> = text.into();
4924 self.transact(window, cx, |this, window, cx| {
4925 let old_selections = this.selections.all_adjusted(cx);
4926 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4927 let anchors = {
4928 let snapshot = buffer.read(cx);
4929 old_selections
4930 .iter()
4931 .map(|s| {
4932 let anchor = snapshot.anchor_after(s.head());
4933 s.map(|_| anchor)
4934 })
4935 .collect::<Vec<_>>()
4936 };
4937 buffer.edit(
4938 old_selections
4939 .iter()
4940 .map(|s| (s.start..s.end, text.clone())),
4941 autoindent_mode,
4942 cx,
4943 );
4944 anchors
4945 });
4946
4947 this.change_selections(Default::default(), window, cx, |s| {
4948 s.select_anchors(selection_anchors);
4949 });
4950
4951 cx.notify();
4952 });
4953 }
4954
4955 fn trigger_completion_on_input(
4956 &mut self,
4957 text: &str,
4958 trigger_in_words: bool,
4959 window: &mut Window,
4960 cx: &mut Context<Self>,
4961 ) {
4962 let completions_source = self
4963 .context_menu
4964 .borrow()
4965 .as_ref()
4966 .and_then(|menu| match menu {
4967 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4968 CodeContextMenu::CodeActions(_) => None,
4969 });
4970
4971 match completions_source {
4972 Some(CompletionsMenuSource::Words { .. }) => {
4973 self.open_or_update_completions_menu(
4974 Some(CompletionsMenuSource::Words {
4975 ignore_threshold: false,
4976 }),
4977 None,
4978 window,
4979 cx,
4980 );
4981 }
4982 Some(CompletionsMenuSource::Normal)
4983 | Some(CompletionsMenuSource::SnippetChoices)
4984 | None
4985 if self.is_completion_trigger(
4986 text,
4987 trigger_in_words,
4988 completions_source.is_some(),
4989 cx,
4990 ) =>
4991 {
4992 self.show_completions(
4993 &ShowCompletions {
4994 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4995 },
4996 window,
4997 cx,
4998 )
4999 }
5000 _ => {
5001 self.hide_context_menu(window, cx);
5002 }
5003 }
5004 }
5005
5006 fn is_completion_trigger(
5007 &self,
5008 text: &str,
5009 trigger_in_words: bool,
5010 menu_is_open: bool,
5011 cx: &mut Context<Self>,
5012 ) -> bool {
5013 let position = self.selections.newest_anchor().head();
5014 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5015 return false;
5016 };
5017
5018 if let Some(completion_provider) = &self.completion_provider {
5019 completion_provider.is_completion_trigger(
5020 &buffer,
5021 position.text_anchor,
5022 text,
5023 trigger_in_words,
5024 menu_is_open,
5025 cx,
5026 )
5027 } else {
5028 false
5029 }
5030 }
5031
5032 /// If any empty selections is touching the start of its innermost containing autoclose
5033 /// region, expand it to select the brackets.
5034 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5035 let selections = self.selections.all::<usize>(cx);
5036 let buffer = self.buffer.read(cx).read(cx);
5037 let new_selections = self
5038 .selections_with_autoclose_regions(selections, &buffer)
5039 .map(|(mut selection, region)| {
5040 if !selection.is_empty() {
5041 return selection;
5042 }
5043
5044 if let Some(region) = region {
5045 let mut range = region.range.to_offset(&buffer);
5046 if selection.start == range.start && range.start >= region.pair.start.len() {
5047 range.start -= region.pair.start.len();
5048 if buffer.contains_str_at(range.start, ®ion.pair.start)
5049 && buffer.contains_str_at(range.end, ®ion.pair.end)
5050 {
5051 range.end += region.pair.end.len();
5052 selection.start = range.start;
5053 selection.end = range.end;
5054
5055 return selection;
5056 }
5057 }
5058 }
5059
5060 let always_treat_brackets_as_autoclosed = buffer
5061 .language_settings_at(selection.start, cx)
5062 .always_treat_brackets_as_autoclosed;
5063
5064 if !always_treat_brackets_as_autoclosed {
5065 return selection;
5066 }
5067
5068 if let Some(scope) = buffer.language_scope_at(selection.start) {
5069 for (pair, enabled) in scope.brackets() {
5070 if !enabled || !pair.close {
5071 continue;
5072 }
5073
5074 if buffer.contains_str_at(selection.start, &pair.end) {
5075 let pair_start_len = pair.start.len();
5076 if buffer.contains_str_at(
5077 selection.start.saturating_sub(pair_start_len),
5078 &pair.start,
5079 ) {
5080 selection.start -= pair_start_len;
5081 selection.end += pair.end.len();
5082
5083 return selection;
5084 }
5085 }
5086 }
5087 }
5088
5089 selection
5090 })
5091 .collect();
5092
5093 drop(buffer);
5094 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5095 selections.select(new_selections)
5096 });
5097 }
5098
5099 /// Iterate the given selections, and for each one, find the smallest surrounding
5100 /// autoclose region. This uses the ordering of the selections and the autoclose
5101 /// regions to avoid repeated comparisons.
5102 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5103 &'a self,
5104 selections: impl IntoIterator<Item = Selection<D>>,
5105 buffer: &'a MultiBufferSnapshot,
5106 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5107 let mut i = 0;
5108 let mut regions = self.autoclose_regions.as_slice();
5109 selections.into_iter().map(move |selection| {
5110 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5111
5112 let mut enclosing = None;
5113 while let Some(pair_state) = regions.get(i) {
5114 if pair_state.range.end.to_offset(buffer) < range.start {
5115 regions = ®ions[i + 1..];
5116 i = 0;
5117 } else if pair_state.range.start.to_offset(buffer) > range.end {
5118 break;
5119 } else {
5120 if pair_state.selection_id == selection.id {
5121 enclosing = Some(pair_state);
5122 }
5123 i += 1;
5124 }
5125 }
5126
5127 (selection, enclosing)
5128 })
5129 }
5130
5131 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5132 fn invalidate_autoclose_regions(
5133 &mut self,
5134 mut selections: &[Selection<Anchor>],
5135 buffer: &MultiBufferSnapshot,
5136 ) {
5137 self.autoclose_regions.retain(|state| {
5138 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5139 return false;
5140 }
5141
5142 let mut i = 0;
5143 while let Some(selection) = selections.get(i) {
5144 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5145 selections = &selections[1..];
5146 continue;
5147 }
5148 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5149 break;
5150 }
5151 if selection.id == state.selection_id {
5152 return true;
5153 } else {
5154 i += 1;
5155 }
5156 }
5157 false
5158 });
5159 }
5160
5161 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5162 let offset = position.to_offset(buffer);
5163 let (word_range, kind) =
5164 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5165 if offset > word_range.start && kind == Some(CharKind::Word) {
5166 Some(
5167 buffer
5168 .text_for_range(word_range.start..offset)
5169 .collect::<String>(),
5170 )
5171 } else {
5172 None
5173 }
5174 }
5175
5176 pub fn toggle_inline_values(
5177 &mut self,
5178 _: &ToggleInlineValues,
5179 _: &mut Window,
5180 cx: &mut Context<Self>,
5181 ) {
5182 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5183
5184 self.refresh_inline_values(cx);
5185 }
5186
5187 pub fn toggle_inlay_hints(
5188 &mut self,
5189 _: &ToggleInlayHints,
5190 _: &mut Window,
5191 cx: &mut Context<Self>,
5192 ) {
5193 self.refresh_inlay_hints(
5194 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5195 cx,
5196 );
5197 }
5198
5199 pub fn inlay_hints_enabled(&self) -> bool {
5200 self.inlay_hint_cache.enabled
5201 }
5202
5203 pub fn inline_values_enabled(&self) -> bool {
5204 self.inline_value_cache.enabled
5205 }
5206
5207 #[cfg(any(test, feature = "test-support"))]
5208 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5209 self.display_map
5210 .read(cx)
5211 .current_inlays()
5212 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5213 .cloned()
5214 .collect()
5215 }
5216
5217 #[cfg(any(test, feature = "test-support"))]
5218 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5219 self.display_map
5220 .read(cx)
5221 .current_inlays()
5222 .cloned()
5223 .collect()
5224 }
5225
5226 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5227 if self.semantics_provider.is_none() || !self.mode.is_full() {
5228 return;
5229 }
5230
5231 let reason_description = reason.description();
5232 let ignore_debounce = matches!(
5233 reason,
5234 InlayHintRefreshReason::SettingsChange(_)
5235 | InlayHintRefreshReason::Toggle(_)
5236 | InlayHintRefreshReason::ExcerptsRemoved(_)
5237 | InlayHintRefreshReason::ModifiersChanged(_)
5238 );
5239 let (invalidate_cache, required_languages) = match reason {
5240 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5241 match self.inlay_hint_cache.modifiers_override(enabled) {
5242 Some(enabled) => {
5243 if enabled {
5244 (InvalidationStrategy::RefreshRequested, None)
5245 } else {
5246 self.splice_inlays(
5247 &self
5248 .visible_inlay_hints(cx)
5249 .iter()
5250 .map(|inlay| inlay.id)
5251 .collect::<Vec<InlayId>>(),
5252 Vec::new(),
5253 cx,
5254 );
5255 return;
5256 }
5257 }
5258 None => return,
5259 }
5260 }
5261 InlayHintRefreshReason::Toggle(enabled) => {
5262 if self.inlay_hint_cache.toggle(enabled) {
5263 if enabled {
5264 (InvalidationStrategy::RefreshRequested, None)
5265 } else {
5266 self.splice_inlays(
5267 &self
5268 .visible_inlay_hints(cx)
5269 .iter()
5270 .map(|inlay| inlay.id)
5271 .collect::<Vec<InlayId>>(),
5272 Vec::new(),
5273 cx,
5274 );
5275 return;
5276 }
5277 } else {
5278 return;
5279 }
5280 }
5281 InlayHintRefreshReason::SettingsChange(new_settings) => {
5282 match self.inlay_hint_cache.update_settings(
5283 &self.buffer,
5284 new_settings,
5285 self.visible_inlay_hints(cx),
5286 cx,
5287 ) {
5288 ControlFlow::Break(Some(InlaySplice {
5289 to_remove,
5290 to_insert,
5291 })) => {
5292 self.splice_inlays(&to_remove, to_insert, cx);
5293 return;
5294 }
5295 ControlFlow::Break(None) => return,
5296 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5297 }
5298 }
5299 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5300 if let Some(InlaySplice {
5301 to_remove,
5302 to_insert,
5303 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5304 {
5305 self.splice_inlays(&to_remove, to_insert, cx);
5306 }
5307 self.display_map.update(cx, |display_map, _| {
5308 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5309 });
5310 return;
5311 }
5312 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5313 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5314 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5315 }
5316 InlayHintRefreshReason::RefreshRequested => {
5317 (InvalidationStrategy::RefreshRequested, None)
5318 }
5319 };
5320
5321 if let Some(InlaySplice {
5322 to_remove,
5323 to_insert,
5324 }) = self.inlay_hint_cache.spawn_hint_refresh(
5325 reason_description,
5326 self.visible_excerpts(required_languages.as_ref(), cx),
5327 invalidate_cache,
5328 ignore_debounce,
5329 cx,
5330 ) {
5331 self.splice_inlays(&to_remove, to_insert, cx);
5332 }
5333 }
5334
5335 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5336 self.display_map
5337 .read(cx)
5338 .current_inlays()
5339 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5340 .cloned()
5341 .collect()
5342 }
5343
5344 pub fn visible_excerpts(
5345 &self,
5346 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5347 cx: &mut Context<Editor>,
5348 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5349 let Some(project) = self.project() else {
5350 return HashMap::default();
5351 };
5352 let project = project.read(cx);
5353 let multi_buffer = self.buffer().read(cx);
5354 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5355 let multi_buffer_visible_start = self
5356 .scroll_manager
5357 .anchor()
5358 .anchor
5359 .to_point(&multi_buffer_snapshot);
5360 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5361 multi_buffer_visible_start
5362 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5363 Bias::Left,
5364 );
5365 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5366 multi_buffer_snapshot
5367 .range_to_buffer_ranges(multi_buffer_visible_range)
5368 .into_iter()
5369 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5370 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5371 let buffer_file = project::File::from_dyn(buffer.file())?;
5372 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5373 let worktree_entry = buffer_worktree
5374 .read(cx)
5375 .entry_for_id(buffer_file.project_entry_id()?)?;
5376 if worktree_entry.is_ignored {
5377 return None;
5378 }
5379
5380 let language = buffer.language()?;
5381 if let Some(restrict_to_languages) = restrict_to_languages
5382 && !restrict_to_languages.contains(language)
5383 {
5384 return None;
5385 }
5386 Some((
5387 excerpt_id,
5388 (
5389 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5390 buffer.version().clone(),
5391 excerpt_visible_range,
5392 ),
5393 ))
5394 })
5395 .collect()
5396 }
5397
5398 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5399 TextLayoutDetails {
5400 text_system: window.text_system().clone(),
5401 editor_style: self.style.clone().unwrap(),
5402 rem_size: window.rem_size(),
5403 scroll_anchor: self.scroll_manager.anchor(),
5404 visible_rows: self.visible_line_count(),
5405 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5406 }
5407 }
5408
5409 pub fn splice_inlays(
5410 &self,
5411 to_remove: &[InlayId],
5412 to_insert: Vec<Inlay>,
5413 cx: &mut Context<Self>,
5414 ) {
5415 self.display_map.update(cx, |display_map, cx| {
5416 display_map.splice_inlays(to_remove, to_insert, cx)
5417 });
5418 cx.notify();
5419 }
5420
5421 fn trigger_on_type_formatting(
5422 &self,
5423 input: String,
5424 window: &mut Window,
5425 cx: &mut Context<Self>,
5426 ) -> Option<Task<Result<()>>> {
5427 if input.len() != 1 {
5428 return None;
5429 }
5430
5431 let project = self.project()?;
5432 let position = self.selections.newest_anchor().head();
5433 let (buffer, buffer_position) = self
5434 .buffer
5435 .read(cx)
5436 .text_anchor_for_position(position, cx)?;
5437
5438 let settings = language_settings::language_settings(
5439 buffer
5440 .read(cx)
5441 .language_at(buffer_position)
5442 .map(|l| l.name()),
5443 buffer.read(cx).file(),
5444 cx,
5445 );
5446 if !settings.use_on_type_format {
5447 return None;
5448 }
5449
5450 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5451 // hence we do LSP request & edit on host side only — add formats to host's history.
5452 let push_to_lsp_host_history = true;
5453 // If this is not the host, append its history with new edits.
5454 let push_to_client_history = project.read(cx).is_via_collab();
5455
5456 let on_type_formatting = project.update(cx, |project, cx| {
5457 project.on_type_format(
5458 buffer.clone(),
5459 buffer_position,
5460 input,
5461 push_to_lsp_host_history,
5462 cx,
5463 )
5464 });
5465 Some(cx.spawn_in(window, async move |editor, cx| {
5466 if let Some(transaction) = on_type_formatting.await? {
5467 if push_to_client_history {
5468 buffer
5469 .update(cx, |buffer, _| {
5470 buffer.push_transaction(transaction, Instant::now());
5471 buffer.finalize_last_transaction();
5472 })
5473 .ok();
5474 }
5475 editor.update(cx, |editor, cx| {
5476 editor.refresh_document_highlights(cx);
5477 })?;
5478 }
5479 Ok(())
5480 }))
5481 }
5482
5483 pub fn show_word_completions(
5484 &mut self,
5485 _: &ShowWordCompletions,
5486 window: &mut Window,
5487 cx: &mut Context<Self>,
5488 ) {
5489 self.open_or_update_completions_menu(
5490 Some(CompletionsMenuSource::Words {
5491 ignore_threshold: true,
5492 }),
5493 None,
5494 window,
5495 cx,
5496 );
5497 }
5498
5499 pub fn show_completions(
5500 &mut self,
5501 options: &ShowCompletions,
5502 window: &mut Window,
5503 cx: &mut Context<Self>,
5504 ) {
5505 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5506 }
5507
5508 fn open_or_update_completions_menu(
5509 &mut self,
5510 requested_source: Option<CompletionsMenuSource>,
5511 trigger: Option<&str>,
5512 window: &mut Window,
5513 cx: &mut Context<Self>,
5514 ) {
5515 if self.pending_rename.is_some() {
5516 return;
5517 }
5518
5519 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5520
5521 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5522 // inserted and selected. To handle that case, the start of the selection is used so that
5523 // the menu starts with all choices.
5524 let position = self
5525 .selections
5526 .newest_anchor()
5527 .start
5528 .bias_right(&multibuffer_snapshot);
5529 if position.diff_base_anchor.is_some() {
5530 return;
5531 }
5532 let buffer_position = multibuffer_snapshot.anchor_before(position);
5533 let Some(buffer) = buffer_position
5534 .buffer_id
5535 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5536 else {
5537 return;
5538 };
5539 let buffer_snapshot = buffer.read(cx).snapshot();
5540
5541 let query: Option<Arc<String>> =
5542 Self::completion_query(&multibuffer_snapshot, buffer_position)
5543 .map(|query| query.into());
5544
5545 drop(multibuffer_snapshot);
5546
5547 // Hide the current completions menu when query is empty. Without this, cached
5548 // completions from before the trigger char may be reused (#32774).
5549 if query.is_none() {
5550 let menu_is_open = matches!(
5551 self.context_menu.borrow().as_ref(),
5552 Some(CodeContextMenu::Completions(_))
5553 );
5554 if menu_is_open {
5555 self.hide_context_menu(window, cx);
5556 }
5557 }
5558
5559 let mut ignore_word_threshold = false;
5560 let provider = match requested_source {
5561 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5562 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5563 ignore_word_threshold = ignore_threshold;
5564 None
5565 }
5566 Some(CompletionsMenuSource::SnippetChoices) => {
5567 log::error!("bug: SnippetChoices requested_source is not handled");
5568 None
5569 }
5570 };
5571
5572 let sort_completions = provider
5573 .as_ref()
5574 .is_some_and(|provider| provider.sort_completions());
5575
5576 let filter_completions = provider
5577 .as_ref()
5578 .is_none_or(|provider| provider.filter_completions());
5579
5580 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5581 if filter_completions {
5582 menu.filter(query.clone(), provider.clone(), window, cx);
5583 }
5584 // When `is_incomplete` is false, no need to re-query completions when the current query
5585 // is a suffix of the initial query.
5586 if !menu.is_incomplete {
5587 // If the new query is a suffix of the old query (typing more characters) and
5588 // the previous result was complete, the existing completions can be filtered.
5589 //
5590 // Note that this is always true for snippet completions.
5591 let query_matches = match (&menu.initial_query, &query) {
5592 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5593 (None, _) => true,
5594 _ => false,
5595 };
5596 if query_matches {
5597 let position_matches = if menu.initial_position == position {
5598 true
5599 } else {
5600 let snapshot = self.buffer.read(cx).read(cx);
5601 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5602 };
5603 if position_matches {
5604 return;
5605 }
5606 }
5607 }
5608 };
5609
5610 let trigger_kind = match trigger {
5611 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5612 CompletionTriggerKind::TRIGGER_CHARACTER
5613 }
5614 _ => CompletionTriggerKind::INVOKED,
5615 };
5616 let completion_context = CompletionContext {
5617 trigger_character: trigger.and_then(|trigger| {
5618 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5619 Some(String::from(trigger))
5620 } else {
5621 None
5622 }
5623 }),
5624 trigger_kind,
5625 };
5626
5627 let Anchor {
5628 excerpt_id: buffer_excerpt_id,
5629 text_anchor: buffer_position,
5630 ..
5631 } = buffer_position;
5632
5633 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5634 buffer_snapshot.surrounding_word(buffer_position, None)
5635 {
5636 let word_to_exclude = buffer_snapshot
5637 .text_for_range(word_range.clone())
5638 .collect::<String>();
5639 (
5640 buffer_snapshot.anchor_before(word_range.start)
5641 ..buffer_snapshot.anchor_after(buffer_position),
5642 Some(word_to_exclude),
5643 )
5644 } else {
5645 (buffer_position..buffer_position, None)
5646 };
5647
5648 let language = buffer_snapshot
5649 .language_at(buffer_position)
5650 .map(|language| language.name());
5651
5652 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5653 .completions
5654 .clone();
5655
5656 let show_completion_documentation = buffer_snapshot
5657 .settings_at(buffer_position, cx)
5658 .show_completion_documentation;
5659
5660 // The document can be large, so stay in reasonable bounds when searching for words,
5661 // otherwise completion pop-up might be slow to appear.
5662 const WORD_LOOKUP_ROWS: u32 = 5_000;
5663 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5664 let min_word_search = buffer_snapshot.clip_point(
5665 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5666 Bias::Left,
5667 );
5668 let max_word_search = buffer_snapshot.clip_point(
5669 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5670 Bias::Right,
5671 );
5672 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5673 ..buffer_snapshot.point_to_offset(max_word_search);
5674
5675 let skip_digits = query
5676 .as_ref()
5677 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5678
5679 let omit_word_completions = !self.word_completions_enabled
5680 || (!ignore_word_threshold
5681 && match &query {
5682 Some(query) => query.chars().count() < completion_settings.words_min_length,
5683 None => completion_settings.words_min_length != 0,
5684 });
5685
5686 let (mut words, provider_responses) = match &provider {
5687 Some(provider) => {
5688 let provider_responses = provider.completions(
5689 buffer_excerpt_id,
5690 &buffer,
5691 buffer_position,
5692 completion_context,
5693 window,
5694 cx,
5695 );
5696
5697 let words = match (omit_word_completions, completion_settings.words) {
5698 (true, _) | (_, WordsCompletionMode::Disabled) => {
5699 Task::ready(BTreeMap::default())
5700 }
5701 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5702 .background_spawn(async move {
5703 buffer_snapshot.words_in_range(WordsQuery {
5704 fuzzy_contents: None,
5705 range: word_search_range,
5706 skip_digits,
5707 })
5708 }),
5709 };
5710
5711 (words, provider_responses)
5712 }
5713 None => {
5714 let words = if omit_word_completions {
5715 Task::ready(BTreeMap::default())
5716 } else {
5717 cx.background_spawn(async move {
5718 buffer_snapshot.words_in_range(WordsQuery {
5719 fuzzy_contents: None,
5720 range: word_search_range,
5721 skip_digits,
5722 })
5723 })
5724 };
5725 (words, Task::ready(Ok(Vec::new())))
5726 }
5727 };
5728
5729 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5730
5731 let id = post_inc(&mut self.next_completion_id);
5732 let task = cx.spawn_in(window, async move |editor, cx| {
5733 let Ok(()) = editor.update(cx, |this, _| {
5734 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5735 }) else {
5736 return;
5737 };
5738
5739 // TODO: Ideally completions from different sources would be selectively re-queried, so
5740 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5741 let mut completions = Vec::new();
5742 let mut is_incomplete = false;
5743 let mut display_options: Option<CompletionDisplayOptions> = None;
5744 if let Some(provider_responses) = provider_responses.await.log_err()
5745 && !provider_responses.is_empty()
5746 {
5747 for response in provider_responses {
5748 completions.extend(response.completions);
5749 is_incomplete = is_incomplete || response.is_incomplete;
5750 match display_options.as_mut() {
5751 None => {
5752 display_options = Some(response.display_options);
5753 }
5754 Some(options) => options.merge(&response.display_options),
5755 }
5756 }
5757 if completion_settings.words == WordsCompletionMode::Fallback {
5758 words = Task::ready(BTreeMap::default());
5759 }
5760 }
5761 let display_options = display_options.unwrap_or_default();
5762
5763 let mut words = words.await;
5764 if let Some(word_to_exclude) = &word_to_exclude {
5765 words.remove(word_to_exclude);
5766 }
5767 for lsp_completion in &completions {
5768 words.remove(&lsp_completion.new_text);
5769 }
5770 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5771 replace_range: word_replace_range.clone(),
5772 new_text: word.clone(),
5773 label: CodeLabel::plain(word, None),
5774 icon_path: None,
5775 documentation: None,
5776 source: CompletionSource::BufferWord {
5777 word_range,
5778 resolved: false,
5779 },
5780 insert_text_mode: Some(InsertTextMode::AS_IS),
5781 confirm: None,
5782 }));
5783
5784 let menu = if completions.is_empty() {
5785 None
5786 } else {
5787 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5788 let languages = editor
5789 .workspace
5790 .as_ref()
5791 .and_then(|(workspace, _)| workspace.upgrade())
5792 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5793 let menu = CompletionsMenu::new(
5794 id,
5795 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5796 sort_completions,
5797 show_completion_documentation,
5798 position,
5799 query.clone(),
5800 is_incomplete,
5801 buffer.clone(),
5802 completions.into(),
5803 display_options,
5804 snippet_sort_order,
5805 languages,
5806 language,
5807 cx,
5808 );
5809
5810 let query = if filter_completions { query } else { None };
5811 let matches_task = if let Some(query) = query {
5812 menu.do_async_filtering(query, cx)
5813 } else {
5814 Task::ready(menu.unfiltered_matches())
5815 };
5816 (menu, matches_task)
5817 }) else {
5818 return;
5819 };
5820
5821 let matches = matches_task.await;
5822
5823 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5824 // Newer menu already set, so exit.
5825 if let Some(CodeContextMenu::Completions(prev_menu)) =
5826 editor.context_menu.borrow().as_ref()
5827 && prev_menu.id > id
5828 {
5829 return;
5830 };
5831
5832 // Only valid to take prev_menu because it the new menu is immediately set
5833 // below, or the menu is hidden.
5834 if let Some(CodeContextMenu::Completions(prev_menu)) =
5835 editor.context_menu.borrow_mut().take()
5836 {
5837 let position_matches =
5838 if prev_menu.initial_position == menu.initial_position {
5839 true
5840 } else {
5841 let snapshot = editor.buffer.read(cx).read(cx);
5842 prev_menu.initial_position.to_offset(&snapshot)
5843 == menu.initial_position.to_offset(&snapshot)
5844 };
5845 if position_matches {
5846 // Preserve markdown cache before `set_filter_results` because it will
5847 // try to populate the documentation cache.
5848 menu.preserve_markdown_cache(prev_menu);
5849 }
5850 };
5851
5852 menu.set_filter_results(matches, provider, window, cx);
5853 }) else {
5854 return;
5855 };
5856
5857 menu.visible().then_some(menu)
5858 };
5859
5860 editor
5861 .update_in(cx, |editor, window, cx| {
5862 if editor.focus_handle.is_focused(window)
5863 && let Some(menu) = menu
5864 {
5865 *editor.context_menu.borrow_mut() =
5866 Some(CodeContextMenu::Completions(menu));
5867
5868 crate::hover_popover::hide_hover(editor, cx);
5869 if editor.show_edit_predictions_in_menu() {
5870 editor.update_visible_edit_prediction(window, cx);
5871 } else {
5872 editor.discard_edit_prediction(false, cx);
5873 }
5874
5875 cx.notify();
5876 return;
5877 }
5878
5879 if editor.completion_tasks.len() <= 1 {
5880 // If there are no more completion tasks and the last menu was empty, we should hide it.
5881 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5882 // If it was already hidden and we don't show edit predictions in the menu,
5883 // we should also show the edit prediction when available.
5884 if was_hidden && editor.show_edit_predictions_in_menu() {
5885 editor.update_visible_edit_prediction(window, cx);
5886 }
5887 }
5888 })
5889 .ok();
5890 });
5891
5892 self.completion_tasks.push((id, task));
5893 }
5894
5895 #[cfg(feature = "test-support")]
5896 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5897 let menu = self.context_menu.borrow();
5898 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5899 let completions = menu.completions.borrow();
5900 Some(completions.to_vec())
5901 } else {
5902 None
5903 }
5904 }
5905
5906 pub fn with_completions_menu_matching_id<R>(
5907 &self,
5908 id: CompletionId,
5909 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5910 ) -> R {
5911 let mut context_menu = self.context_menu.borrow_mut();
5912 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5913 return f(None);
5914 };
5915 if completions_menu.id != id {
5916 return f(None);
5917 }
5918 f(Some(completions_menu))
5919 }
5920
5921 pub fn confirm_completion(
5922 &mut self,
5923 action: &ConfirmCompletion,
5924 window: &mut Window,
5925 cx: &mut Context<Self>,
5926 ) -> Option<Task<Result<()>>> {
5927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5928 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5929 }
5930
5931 pub fn confirm_completion_insert(
5932 &mut self,
5933 _: &ConfirmCompletionInsert,
5934 window: &mut Window,
5935 cx: &mut Context<Self>,
5936 ) -> Option<Task<Result<()>>> {
5937 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5938 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5939 }
5940
5941 pub fn confirm_completion_replace(
5942 &mut self,
5943 _: &ConfirmCompletionReplace,
5944 window: &mut Window,
5945 cx: &mut Context<Self>,
5946 ) -> Option<Task<Result<()>>> {
5947 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5948 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5949 }
5950
5951 pub fn compose_completion(
5952 &mut self,
5953 action: &ComposeCompletion,
5954 window: &mut Window,
5955 cx: &mut Context<Self>,
5956 ) -> Option<Task<Result<()>>> {
5957 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5958 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5959 }
5960
5961 fn do_completion(
5962 &mut self,
5963 item_ix: Option<usize>,
5964 intent: CompletionIntent,
5965 window: &mut Window,
5966 cx: &mut Context<Editor>,
5967 ) -> Option<Task<Result<()>>> {
5968 use language::ToOffset as _;
5969
5970 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5971 else {
5972 return None;
5973 };
5974
5975 let candidate_id = {
5976 let entries = completions_menu.entries.borrow();
5977 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5978 if self.show_edit_predictions_in_menu() {
5979 self.discard_edit_prediction(true, cx);
5980 }
5981 mat.candidate_id
5982 };
5983
5984 let completion = completions_menu
5985 .completions
5986 .borrow()
5987 .get(candidate_id)?
5988 .clone();
5989 cx.stop_propagation();
5990
5991 let buffer_handle = completions_menu.buffer.clone();
5992
5993 let CompletionEdit {
5994 new_text,
5995 snippet,
5996 replace_range,
5997 } = process_completion_for_edit(
5998 &completion,
5999 intent,
6000 &buffer_handle,
6001 &completions_menu.initial_position.text_anchor,
6002 cx,
6003 );
6004
6005 let buffer = buffer_handle.read(cx);
6006 let snapshot = self.buffer.read(cx).snapshot(cx);
6007 let newest_anchor = self.selections.newest_anchor();
6008 let replace_range_multibuffer = {
6009 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6010 let multibuffer_anchor = snapshot
6011 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
6012 .unwrap()
6013 ..snapshot
6014 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
6015 .unwrap();
6016 multibuffer_anchor.start.to_offset(&snapshot)
6017 ..multibuffer_anchor.end.to_offset(&snapshot)
6018 };
6019 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6020 return None;
6021 }
6022
6023 let old_text = buffer
6024 .text_for_range(replace_range.clone())
6025 .collect::<String>();
6026 let lookbehind = newest_anchor
6027 .start
6028 .text_anchor
6029 .to_offset(buffer)
6030 .saturating_sub(replace_range.start);
6031 let lookahead = replace_range
6032 .end
6033 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6034 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6035 let suffix = &old_text[lookbehind.min(old_text.len())..];
6036
6037 let selections = self.selections.all::<usize>(cx);
6038 let mut ranges = Vec::new();
6039 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6040
6041 for selection in &selections {
6042 let range = if selection.id == newest_anchor.id {
6043 replace_range_multibuffer.clone()
6044 } else {
6045 let mut range = selection.range();
6046
6047 // if prefix is present, don't duplicate it
6048 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6049 range.start = range.start.saturating_sub(lookbehind);
6050
6051 // if suffix is also present, mimic the newest cursor and replace it
6052 if selection.id != newest_anchor.id
6053 && snapshot.contains_str_at(range.end, suffix)
6054 {
6055 range.end += lookahead;
6056 }
6057 }
6058 range
6059 };
6060
6061 ranges.push(range.clone());
6062
6063 if !self.linked_edit_ranges.is_empty() {
6064 let start_anchor = snapshot.anchor_before(range.start);
6065 let end_anchor = snapshot.anchor_after(range.end);
6066 if let Some(ranges) = self
6067 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6068 {
6069 for (buffer, edits) in ranges {
6070 linked_edits
6071 .entry(buffer.clone())
6072 .or_default()
6073 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6074 }
6075 }
6076 }
6077 }
6078
6079 let common_prefix_len = old_text
6080 .chars()
6081 .zip(new_text.chars())
6082 .take_while(|(a, b)| a == b)
6083 .map(|(a, _)| a.len_utf8())
6084 .sum::<usize>();
6085
6086 cx.emit(EditorEvent::InputHandled {
6087 utf16_range_to_replace: None,
6088 text: new_text[common_prefix_len..].into(),
6089 });
6090
6091 self.transact(window, cx, |editor, window, cx| {
6092 if let Some(mut snippet) = snippet {
6093 snippet.text = new_text.to_string();
6094 editor
6095 .insert_snippet(&ranges, snippet, window, cx)
6096 .log_err();
6097 } else {
6098 editor.buffer.update(cx, |multi_buffer, cx| {
6099 let auto_indent = match completion.insert_text_mode {
6100 Some(InsertTextMode::AS_IS) => None,
6101 _ => editor.autoindent_mode.clone(),
6102 };
6103 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6104 multi_buffer.edit(edits, auto_indent, cx);
6105 });
6106 }
6107 for (buffer, edits) in linked_edits {
6108 buffer.update(cx, |buffer, cx| {
6109 let snapshot = buffer.snapshot();
6110 let edits = edits
6111 .into_iter()
6112 .map(|(range, text)| {
6113 use text::ToPoint as TP;
6114 let end_point = TP::to_point(&range.end, &snapshot);
6115 let start_point = TP::to_point(&range.start, &snapshot);
6116 (start_point..end_point, text)
6117 })
6118 .sorted_by_key(|(range, _)| range.start);
6119 buffer.edit(edits, None, cx);
6120 })
6121 }
6122
6123 editor.refresh_edit_prediction(true, false, window, cx);
6124 });
6125 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6126
6127 let show_new_completions_on_confirm = completion
6128 .confirm
6129 .as_ref()
6130 .is_some_and(|confirm| confirm(intent, window, cx));
6131 if show_new_completions_on_confirm {
6132 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6133 }
6134
6135 let provider = self.completion_provider.as_ref()?;
6136 drop(completion);
6137 let apply_edits = provider.apply_additional_edits_for_completion(
6138 buffer_handle,
6139 completions_menu.completions.clone(),
6140 candidate_id,
6141 true,
6142 cx,
6143 );
6144
6145 let editor_settings = EditorSettings::get_global(cx);
6146 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6147 // After the code completion is finished, users often want to know what signatures are needed.
6148 // so we should automatically call signature_help
6149 self.show_signature_help(&ShowSignatureHelp, window, cx);
6150 }
6151
6152 Some(cx.foreground_executor().spawn(async move {
6153 apply_edits.await?;
6154 Ok(())
6155 }))
6156 }
6157
6158 pub fn toggle_code_actions(
6159 &mut self,
6160 action: &ToggleCodeActions,
6161 window: &mut Window,
6162 cx: &mut Context<Self>,
6163 ) {
6164 let quick_launch = action.quick_launch;
6165 let mut context_menu = self.context_menu.borrow_mut();
6166 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6167 if code_actions.deployed_from == action.deployed_from {
6168 // Toggle if we're selecting the same one
6169 *context_menu = None;
6170 cx.notify();
6171 return;
6172 } else {
6173 // Otherwise, clear it and start a new one
6174 *context_menu = None;
6175 cx.notify();
6176 }
6177 }
6178 drop(context_menu);
6179 let snapshot = self.snapshot(window, cx);
6180 let deployed_from = action.deployed_from.clone();
6181 let action = action.clone();
6182 self.completion_tasks.clear();
6183 self.discard_edit_prediction(false, cx);
6184
6185 let multibuffer_point = match &action.deployed_from {
6186 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6187 DisplayPoint::new(*row, 0).to_point(&snapshot)
6188 }
6189 _ => self.selections.newest::<Point>(cx).head(),
6190 };
6191 let Some((buffer, buffer_row)) = snapshot
6192 .buffer_snapshot()
6193 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6194 .and_then(|(buffer_snapshot, range)| {
6195 self.buffer()
6196 .read(cx)
6197 .buffer(buffer_snapshot.remote_id())
6198 .map(|buffer| (buffer, range.start.row))
6199 })
6200 else {
6201 return;
6202 };
6203 let buffer_id = buffer.read(cx).remote_id();
6204 let tasks = self
6205 .tasks
6206 .get(&(buffer_id, buffer_row))
6207 .map(|t| Arc::new(t.to_owned()));
6208
6209 if !self.focus_handle.is_focused(window) {
6210 return;
6211 }
6212 let project = self.project.clone();
6213
6214 let code_actions_task = match deployed_from {
6215 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6216 _ => self.code_actions(buffer_row, window, cx),
6217 };
6218
6219 let runnable_task = match deployed_from {
6220 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6221 _ => {
6222 let mut task_context_task = Task::ready(None);
6223 if let Some(tasks) = &tasks
6224 && let Some(project) = project
6225 {
6226 task_context_task =
6227 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6228 }
6229
6230 cx.spawn_in(window, {
6231 let buffer = buffer.clone();
6232 async move |editor, cx| {
6233 let task_context = task_context_task.await;
6234
6235 let resolved_tasks =
6236 tasks
6237 .zip(task_context.clone())
6238 .map(|(tasks, task_context)| ResolvedTasks {
6239 templates: tasks.resolve(&task_context).collect(),
6240 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6241 multibuffer_point.row,
6242 tasks.column,
6243 )),
6244 });
6245 let debug_scenarios = editor
6246 .update(cx, |editor, cx| {
6247 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6248 })?
6249 .await;
6250 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6251 }
6252 })
6253 }
6254 };
6255
6256 cx.spawn_in(window, async move |editor, cx| {
6257 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6258 let code_actions = code_actions_task.await;
6259 let spawn_straight_away = quick_launch
6260 && resolved_tasks
6261 .as_ref()
6262 .is_some_and(|tasks| tasks.templates.len() == 1)
6263 && code_actions
6264 .as_ref()
6265 .is_none_or(|actions| actions.is_empty())
6266 && debug_scenarios.is_empty();
6267
6268 editor.update_in(cx, |editor, window, cx| {
6269 crate::hover_popover::hide_hover(editor, cx);
6270 let actions = CodeActionContents::new(
6271 resolved_tasks,
6272 code_actions,
6273 debug_scenarios,
6274 task_context.unwrap_or_default(),
6275 );
6276
6277 // Don't show the menu if there are no actions available
6278 if actions.is_empty() {
6279 cx.notify();
6280 return Task::ready(Ok(()));
6281 }
6282
6283 *editor.context_menu.borrow_mut() =
6284 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6285 buffer,
6286 actions,
6287 selected_item: Default::default(),
6288 scroll_handle: UniformListScrollHandle::default(),
6289 deployed_from,
6290 }));
6291 cx.notify();
6292 if spawn_straight_away
6293 && let Some(task) = editor.confirm_code_action(
6294 &ConfirmCodeAction { item_ix: Some(0) },
6295 window,
6296 cx,
6297 )
6298 {
6299 return task;
6300 }
6301
6302 Task::ready(Ok(()))
6303 })
6304 })
6305 .detach_and_log_err(cx);
6306 }
6307
6308 fn debug_scenarios(
6309 &mut self,
6310 resolved_tasks: &Option<ResolvedTasks>,
6311 buffer: &Entity<Buffer>,
6312 cx: &mut App,
6313 ) -> Task<Vec<task::DebugScenario>> {
6314 maybe!({
6315 let project = self.project()?;
6316 let dap_store = project.read(cx).dap_store();
6317 let mut scenarios = vec![];
6318 let resolved_tasks = resolved_tasks.as_ref()?;
6319 let buffer = buffer.read(cx);
6320 let language = buffer.language()?;
6321 let file = buffer.file();
6322 let debug_adapter = language_settings(language.name().into(), file, cx)
6323 .debuggers
6324 .first()
6325 .map(SharedString::from)
6326 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6327
6328 dap_store.update(cx, |dap_store, cx| {
6329 for (_, task) in &resolved_tasks.templates {
6330 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6331 task.original_task().clone(),
6332 debug_adapter.clone().into(),
6333 task.display_label().to_owned().into(),
6334 cx,
6335 );
6336 scenarios.push(maybe_scenario);
6337 }
6338 });
6339 Some(cx.background_spawn(async move {
6340 futures::future::join_all(scenarios)
6341 .await
6342 .into_iter()
6343 .flatten()
6344 .collect::<Vec<_>>()
6345 }))
6346 })
6347 .unwrap_or_else(|| Task::ready(vec![]))
6348 }
6349
6350 fn code_actions(
6351 &mut self,
6352 buffer_row: u32,
6353 window: &mut Window,
6354 cx: &mut Context<Self>,
6355 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6356 let mut task = self.code_actions_task.take();
6357 cx.spawn_in(window, async move |editor, cx| {
6358 while let Some(prev_task) = task {
6359 prev_task.await.log_err();
6360 task = editor
6361 .update(cx, |this, _| this.code_actions_task.take())
6362 .ok()?;
6363 }
6364
6365 editor
6366 .update(cx, |editor, cx| {
6367 editor
6368 .available_code_actions
6369 .clone()
6370 .and_then(|(location, code_actions)| {
6371 let snapshot = location.buffer.read(cx).snapshot();
6372 let point_range = location.range.to_point(&snapshot);
6373 let point_range = point_range.start.row..=point_range.end.row;
6374 if point_range.contains(&buffer_row) {
6375 Some(code_actions)
6376 } else {
6377 None
6378 }
6379 })
6380 })
6381 .ok()
6382 .flatten()
6383 })
6384 }
6385
6386 pub fn confirm_code_action(
6387 &mut self,
6388 action: &ConfirmCodeAction,
6389 window: &mut Window,
6390 cx: &mut Context<Self>,
6391 ) -> Option<Task<Result<()>>> {
6392 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6393
6394 let actions_menu =
6395 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6396 menu
6397 } else {
6398 return None;
6399 };
6400
6401 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6402 let action = actions_menu.actions.get(action_ix)?;
6403 let title = action.label();
6404 let buffer = actions_menu.buffer;
6405 let workspace = self.workspace()?;
6406
6407 match action {
6408 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6409 workspace.update(cx, |workspace, cx| {
6410 workspace.schedule_resolved_task(
6411 task_source_kind,
6412 resolved_task,
6413 false,
6414 window,
6415 cx,
6416 );
6417
6418 Some(Task::ready(Ok(())))
6419 })
6420 }
6421 CodeActionsItem::CodeAction {
6422 excerpt_id,
6423 action,
6424 provider,
6425 } => {
6426 let apply_code_action =
6427 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6428 let workspace = workspace.downgrade();
6429 Some(cx.spawn_in(window, async move |editor, cx| {
6430 let project_transaction = apply_code_action.await?;
6431 Self::open_project_transaction(
6432 &editor,
6433 workspace,
6434 project_transaction,
6435 title,
6436 cx,
6437 )
6438 .await
6439 }))
6440 }
6441 CodeActionsItem::DebugScenario(scenario) => {
6442 let context = actions_menu.actions.context;
6443
6444 workspace.update(cx, |workspace, cx| {
6445 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6446 workspace.start_debug_session(
6447 scenario,
6448 context,
6449 Some(buffer),
6450 None,
6451 window,
6452 cx,
6453 );
6454 });
6455 Some(Task::ready(Ok(())))
6456 }
6457 }
6458 }
6459
6460 pub async fn open_project_transaction(
6461 editor: &WeakEntity<Editor>,
6462 workspace: WeakEntity<Workspace>,
6463 transaction: ProjectTransaction,
6464 title: String,
6465 cx: &mut AsyncWindowContext,
6466 ) -> Result<()> {
6467 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6468 cx.update(|_, cx| {
6469 entries.sort_unstable_by_key(|(buffer, _)| {
6470 buffer.read(cx).file().map(|f| f.path().clone())
6471 });
6472 })?;
6473 if entries.is_empty() {
6474 return Ok(());
6475 }
6476
6477 // If the project transaction's edits are all contained within this editor, then
6478 // avoid opening a new editor to display them.
6479
6480 if let [(buffer, transaction)] = &*entries {
6481 let excerpt = editor.update(cx, |editor, cx| {
6482 editor
6483 .buffer()
6484 .read(cx)
6485 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6486 })?;
6487 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6488 && excerpted_buffer == *buffer
6489 {
6490 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6491 let excerpt_range = excerpt_range.to_offset(buffer);
6492 buffer
6493 .edited_ranges_for_transaction::<usize>(transaction)
6494 .all(|range| {
6495 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6496 })
6497 })?;
6498
6499 if all_edits_within_excerpt {
6500 return Ok(());
6501 }
6502 }
6503 }
6504
6505 let mut ranges_to_highlight = Vec::new();
6506 let excerpt_buffer = cx.new(|cx| {
6507 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6508 for (buffer_handle, transaction) in &entries {
6509 let edited_ranges = buffer_handle
6510 .read(cx)
6511 .edited_ranges_for_transaction::<Point>(transaction)
6512 .collect::<Vec<_>>();
6513 let (ranges, _) = multibuffer.set_excerpts_for_path(
6514 PathKey::for_buffer(buffer_handle, cx),
6515 buffer_handle.clone(),
6516 edited_ranges,
6517 multibuffer_context_lines(cx),
6518 cx,
6519 );
6520
6521 ranges_to_highlight.extend(ranges);
6522 }
6523 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6524 multibuffer
6525 })?;
6526
6527 workspace.update_in(cx, |workspace, window, cx| {
6528 let project = workspace.project().clone();
6529 let editor =
6530 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6531 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6532 editor.update(cx, |editor, cx| {
6533 editor.highlight_background::<Self>(
6534 &ranges_to_highlight,
6535 |theme| theme.colors().editor_highlighted_line_background,
6536 cx,
6537 );
6538 });
6539 })?;
6540
6541 Ok(())
6542 }
6543
6544 pub fn clear_code_action_providers(&mut self) {
6545 self.code_action_providers.clear();
6546 self.available_code_actions.take();
6547 }
6548
6549 pub fn add_code_action_provider(
6550 &mut self,
6551 provider: Rc<dyn CodeActionProvider>,
6552 window: &mut Window,
6553 cx: &mut Context<Self>,
6554 ) {
6555 if self
6556 .code_action_providers
6557 .iter()
6558 .any(|existing_provider| existing_provider.id() == provider.id())
6559 {
6560 return;
6561 }
6562
6563 self.code_action_providers.push(provider);
6564 self.refresh_code_actions(window, cx);
6565 }
6566
6567 pub fn remove_code_action_provider(
6568 &mut self,
6569 id: Arc<str>,
6570 window: &mut Window,
6571 cx: &mut Context<Self>,
6572 ) {
6573 self.code_action_providers
6574 .retain(|provider| provider.id() != id);
6575 self.refresh_code_actions(window, cx);
6576 }
6577
6578 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6579 !self.code_action_providers.is_empty()
6580 && EditorSettings::get_global(cx).toolbar.code_actions
6581 }
6582
6583 pub fn has_available_code_actions(&self) -> bool {
6584 self.available_code_actions
6585 .as_ref()
6586 .is_some_and(|(_, actions)| !actions.is_empty())
6587 }
6588
6589 fn render_inline_code_actions(
6590 &self,
6591 icon_size: ui::IconSize,
6592 display_row: DisplayRow,
6593 is_active: bool,
6594 cx: &mut Context<Self>,
6595 ) -> AnyElement {
6596 let show_tooltip = !self.context_menu_visible();
6597 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6598 .icon_size(icon_size)
6599 .shape(ui::IconButtonShape::Square)
6600 .icon_color(ui::Color::Hidden)
6601 .toggle_state(is_active)
6602 .when(show_tooltip, |this| {
6603 this.tooltip({
6604 let focus_handle = self.focus_handle.clone();
6605 move |window, cx| {
6606 Tooltip::for_action_in(
6607 "Toggle Code Actions",
6608 &ToggleCodeActions {
6609 deployed_from: None,
6610 quick_launch: false,
6611 },
6612 &focus_handle,
6613 window,
6614 cx,
6615 )
6616 }
6617 })
6618 })
6619 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6620 window.focus(&editor.focus_handle(cx));
6621 editor.toggle_code_actions(
6622 &crate::actions::ToggleCodeActions {
6623 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6624 display_row,
6625 )),
6626 quick_launch: false,
6627 },
6628 window,
6629 cx,
6630 );
6631 }))
6632 .into_any_element()
6633 }
6634
6635 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6636 &self.context_menu
6637 }
6638
6639 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6640 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6641 cx.background_executor()
6642 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6643 .await;
6644
6645 let (start_buffer, start, _, end, newest_selection) = this
6646 .update(cx, |this, cx| {
6647 let newest_selection = this.selections.newest_anchor().clone();
6648 if newest_selection.head().diff_base_anchor.is_some() {
6649 return None;
6650 }
6651 let newest_selection_adjusted = this.selections.newest_adjusted(cx);
6652 let buffer = this.buffer.read(cx);
6653
6654 let (start_buffer, start) =
6655 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6656 let (end_buffer, end) =
6657 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6658
6659 Some((start_buffer, start, end_buffer, end, newest_selection))
6660 })?
6661 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6662 .context(
6663 "Expected selection to lie in a single buffer when refreshing code actions",
6664 )?;
6665 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6666 let providers = this.code_action_providers.clone();
6667 let tasks = this
6668 .code_action_providers
6669 .iter()
6670 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6671 .collect::<Vec<_>>();
6672 (providers, tasks)
6673 })?;
6674
6675 let mut actions = Vec::new();
6676 for (provider, provider_actions) in
6677 providers.into_iter().zip(future::join_all(tasks).await)
6678 {
6679 if let Some(provider_actions) = provider_actions.log_err() {
6680 actions.extend(provider_actions.into_iter().map(|action| {
6681 AvailableCodeAction {
6682 excerpt_id: newest_selection.start.excerpt_id,
6683 action,
6684 provider: provider.clone(),
6685 }
6686 }));
6687 }
6688 }
6689
6690 this.update(cx, |this, cx| {
6691 this.available_code_actions = if actions.is_empty() {
6692 None
6693 } else {
6694 Some((
6695 Location {
6696 buffer: start_buffer,
6697 range: start..end,
6698 },
6699 actions.into(),
6700 ))
6701 };
6702 cx.notify();
6703 })
6704 }));
6705 }
6706
6707 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6708 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6709 self.show_git_blame_inline = false;
6710
6711 self.show_git_blame_inline_delay_task =
6712 Some(cx.spawn_in(window, async move |this, cx| {
6713 cx.background_executor().timer(delay).await;
6714
6715 this.update(cx, |this, cx| {
6716 this.show_git_blame_inline = true;
6717 cx.notify();
6718 })
6719 .log_err();
6720 }));
6721 }
6722 }
6723
6724 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6725 let snapshot = self.snapshot(window, cx);
6726 let cursor = self.selections.newest::<Point>(cx).head();
6727 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6728 else {
6729 return;
6730 };
6731
6732 let Some(blame) = self.blame.as_ref() else {
6733 return;
6734 };
6735
6736 let row_info = RowInfo {
6737 buffer_id: Some(buffer.remote_id()),
6738 buffer_row: Some(point.row),
6739 ..Default::default()
6740 };
6741 let Some((buffer, blame_entry)) = blame
6742 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6743 .flatten()
6744 else {
6745 return;
6746 };
6747
6748 let anchor = self.selections.newest_anchor().head();
6749 let position = self.to_pixel_point(anchor, &snapshot, window);
6750 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6751 self.show_blame_popover(
6752 buffer,
6753 &blame_entry,
6754 position + last_bounds.origin,
6755 true,
6756 cx,
6757 );
6758 };
6759 }
6760
6761 fn show_blame_popover(
6762 &mut self,
6763 buffer: BufferId,
6764 blame_entry: &BlameEntry,
6765 position: gpui::Point<Pixels>,
6766 ignore_timeout: bool,
6767 cx: &mut Context<Self>,
6768 ) {
6769 if let Some(state) = &mut self.inline_blame_popover {
6770 state.hide_task.take();
6771 } else {
6772 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6773 let blame_entry = blame_entry.clone();
6774 let show_task = cx.spawn(async move |editor, cx| {
6775 if !ignore_timeout {
6776 cx.background_executor()
6777 .timer(std::time::Duration::from_millis(blame_popover_delay))
6778 .await;
6779 }
6780 editor
6781 .update(cx, |editor, cx| {
6782 editor.inline_blame_popover_show_task.take();
6783 let Some(blame) = editor.blame.as_ref() else {
6784 return;
6785 };
6786 let blame = blame.read(cx);
6787 let details = blame.details_for_entry(buffer, &blame_entry);
6788 let markdown = cx.new(|cx| {
6789 Markdown::new(
6790 details
6791 .as_ref()
6792 .map(|message| message.message.clone())
6793 .unwrap_or_default(),
6794 None,
6795 None,
6796 cx,
6797 )
6798 });
6799 editor.inline_blame_popover = Some(InlineBlamePopover {
6800 position,
6801 hide_task: None,
6802 popover_bounds: None,
6803 popover_state: InlineBlamePopoverState {
6804 scroll_handle: ScrollHandle::new(),
6805 commit_message: details,
6806 markdown,
6807 },
6808 keyboard_grace: ignore_timeout,
6809 });
6810 cx.notify();
6811 })
6812 .ok();
6813 });
6814 self.inline_blame_popover_show_task = Some(show_task);
6815 }
6816 }
6817
6818 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6819 self.inline_blame_popover_show_task.take();
6820 if let Some(state) = &mut self.inline_blame_popover {
6821 let hide_task = cx.spawn(async move |editor, cx| {
6822 cx.background_executor()
6823 .timer(std::time::Duration::from_millis(100))
6824 .await;
6825 editor
6826 .update(cx, |editor, cx| {
6827 editor.inline_blame_popover.take();
6828 cx.notify();
6829 })
6830 .ok();
6831 });
6832 state.hide_task = Some(hide_task);
6833 }
6834 }
6835
6836 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6837 if self.pending_rename.is_some() {
6838 return None;
6839 }
6840
6841 let provider = self.semantics_provider.clone()?;
6842 let buffer = self.buffer.read(cx);
6843 let newest_selection = self.selections.newest_anchor().clone();
6844 let cursor_position = newest_selection.head();
6845 let (cursor_buffer, cursor_buffer_position) =
6846 buffer.text_anchor_for_position(cursor_position, cx)?;
6847 let (tail_buffer, tail_buffer_position) =
6848 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6849 if cursor_buffer != tail_buffer {
6850 return None;
6851 }
6852
6853 let snapshot = cursor_buffer.read(cx).snapshot();
6854 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6855 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6856 if start_word_range != end_word_range {
6857 self.document_highlights_task.take();
6858 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6859 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6860 return None;
6861 }
6862
6863 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6864 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6865 cx.background_executor()
6866 .timer(Duration::from_millis(debounce))
6867 .await;
6868
6869 let highlights = if let Some(highlights) = cx
6870 .update(|cx| {
6871 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6872 })
6873 .ok()
6874 .flatten()
6875 {
6876 highlights.await.log_err()
6877 } else {
6878 None
6879 };
6880
6881 if let Some(highlights) = highlights {
6882 this.update(cx, |this, cx| {
6883 if this.pending_rename.is_some() {
6884 return;
6885 }
6886
6887 let buffer = this.buffer.read(cx);
6888 if buffer
6889 .text_anchor_for_position(cursor_position, cx)
6890 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6891 {
6892 return;
6893 }
6894
6895 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6896 let mut write_ranges = Vec::new();
6897 let mut read_ranges = Vec::new();
6898 for highlight in highlights {
6899 let buffer_id = cursor_buffer.read(cx).remote_id();
6900 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6901 {
6902 let start = highlight
6903 .range
6904 .start
6905 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6906 let end = highlight
6907 .range
6908 .end
6909 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6910 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6911 continue;
6912 }
6913
6914 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6915 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6916 write_ranges.push(range);
6917 } else {
6918 read_ranges.push(range);
6919 }
6920 }
6921 }
6922
6923 this.highlight_background::<DocumentHighlightRead>(
6924 &read_ranges,
6925 |theme| theme.colors().editor_document_highlight_read_background,
6926 cx,
6927 );
6928 this.highlight_background::<DocumentHighlightWrite>(
6929 &write_ranges,
6930 |theme| theme.colors().editor_document_highlight_write_background,
6931 cx,
6932 );
6933 cx.notify();
6934 })
6935 .log_err();
6936 }
6937 }));
6938 None
6939 }
6940
6941 fn prepare_highlight_query_from_selection(
6942 &mut self,
6943 cx: &mut Context<Editor>,
6944 ) -> Option<(String, Range<Anchor>)> {
6945 if matches!(self.mode, EditorMode::SingleLine) {
6946 return None;
6947 }
6948 if !EditorSettings::get_global(cx).selection_highlight {
6949 return None;
6950 }
6951 if self.selections.count() != 1 || self.selections.line_mode() {
6952 return None;
6953 }
6954 let selection = self.selections.newest_anchor();
6955 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6956 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6957 ..selection.end.to_point(&multi_buffer_snapshot);
6958 // If the selection spans multiple rows OR it is empty
6959 if selection_point_range.start.row != selection_point_range.end.row
6960 || selection_point_range.start.column == selection_point_range.end.column
6961 {
6962 return None;
6963 }
6964
6965 let query = multi_buffer_snapshot
6966 .text_for_range(selection.range())
6967 .collect::<String>();
6968 if query.trim().is_empty() {
6969 return None;
6970 }
6971 Some((query, selection.range()))
6972 }
6973
6974 fn update_selection_occurrence_highlights(
6975 &mut self,
6976 query_text: String,
6977 query_range: Range<Anchor>,
6978 multi_buffer_range_to_query: Range<Point>,
6979 use_debounce: bool,
6980 window: &mut Window,
6981 cx: &mut Context<Editor>,
6982 ) -> Task<()> {
6983 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6984 cx.spawn_in(window, async move |editor, cx| {
6985 if use_debounce {
6986 cx.background_executor()
6987 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6988 .await;
6989 }
6990 let match_task = cx.background_spawn(async move {
6991 let buffer_ranges = multi_buffer_snapshot
6992 .range_to_buffer_ranges(multi_buffer_range_to_query)
6993 .into_iter()
6994 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6995 let mut match_ranges = Vec::new();
6996 let Ok(regex) = project::search::SearchQuery::text(
6997 query_text.clone(),
6998 false,
6999 false,
7000 false,
7001 Default::default(),
7002 Default::default(),
7003 false,
7004 None,
7005 ) else {
7006 return Vec::default();
7007 };
7008 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7009 match_ranges.extend(
7010 regex
7011 .search(buffer_snapshot, Some(search_range.clone()))
7012 .await
7013 .into_iter()
7014 .filter_map(|match_range| {
7015 let match_start = buffer_snapshot
7016 .anchor_after(search_range.start + match_range.start);
7017 let match_end = buffer_snapshot
7018 .anchor_before(search_range.start + match_range.end);
7019 let match_anchor_range = Anchor::range_in_buffer(
7020 excerpt_id,
7021 buffer_snapshot.remote_id(),
7022 match_start..match_end,
7023 );
7024 (match_anchor_range != query_range).then_some(match_anchor_range)
7025 }),
7026 );
7027 }
7028 match_ranges
7029 });
7030 let match_ranges = match_task.await;
7031 editor
7032 .update_in(cx, |editor, _, cx| {
7033 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7034 if !match_ranges.is_empty() {
7035 editor.highlight_background::<SelectedTextHighlight>(
7036 &match_ranges,
7037 |theme| theme.colors().editor_document_highlight_bracket_background,
7038 cx,
7039 )
7040 }
7041 })
7042 .log_err();
7043 })
7044 }
7045
7046 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7047 struct NewlineFold;
7048 let type_id = std::any::TypeId::of::<NewlineFold>();
7049 if !self.mode.is_single_line() {
7050 return;
7051 }
7052 let snapshot = self.snapshot(window, cx);
7053 if snapshot.buffer_snapshot().max_point().row == 0 {
7054 return;
7055 }
7056 let task = cx.background_spawn(async move {
7057 let new_newlines = snapshot
7058 .buffer_chars_at(0)
7059 .filter_map(|(c, i)| {
7060 if c == '\n' {
7061 Some(
7062 snapshot.buffer_snapshot().anchor_after(i)
7063 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7064 )
7065 } else {
7066 None
7067 }
7068 })
7069 .collect::<Vec<_>>();
7070 let existing_newlines = snapshot
7071 .folds_in_range(0..snapshot.buffer_snapshot().len())
7072 .filter_map(|fold| {
7073 if fold.placeholder.type_tag == Some(type_id) {
7074 Some(fold.range.start..fold.range.end)
7075 } else {
7076 None
7077 }
7078 })
7079 .collect::<Vec<_>>();
7080
7081 (new_newlines, existing_newlines)
7082 });
7083 self.folding_newlines = cx.spawn(async move |this, cx| {
7084 let (new_newlines, existing_newlines) = task.await;
7085 if new_newlines == existing_newlines {
7086 return;
7087 }
7088 let placeholder = FoldPlaceholder {
7089 render: Arc::new(move |_, _, cx| {
7090 div()
7091 .bg(cx.theme().status().hint_background)
7092 .border_b_1()
7093 .size_full()
7094 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7095 .border_color(cx.theme().status().hint)
7096 .child("\\n")
7097 .into_any()
7098 }),
7099 constrain_width: false,
7100 merge_adjacent: false,
7101 type_tag: Some(type_id),
7102 };
7103 let creases = new_newlines
7104 .into_iter()
7105 .map(|range| Crease::simple(range, placeholder.clone()))
7106 .collect();
7107 this.update(cx, |this, cx| {
7108 this.display_map.update(cx, |display_map, cx| {
7109 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7110 display_map.fold(creases, cx);
7111 });
7112 })
7113 .ok();
7114 });
7115 }
7116
7117 fn refresh_selected_text_highlights(
7118 &mut self,
7119 on_buffer_edit: bool,
7120 window: &mut Window,
7121 cx: &mut Context<Editor>,
7122 ) {
7123 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7124 else {
7125 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7126 self.quick_selection_highlight_task.take();
7127 self.debounced_selection_highlight_task.take();
7128 return;
7129 };
7130 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7131 let query_offset = query_range.to_offset(&multi_buffer_snapshot);
7132 if on_buffer_edit
7133 || self
7134 .quick_selection_highlight_task
7135 .as_ref()
7136 .is_none_or(|(prev_query_offset, _)| prev_query_offset != &query_offset)
7137 {
7138 let multi_buffer_visible_start = self
7139 .scroll_manager
7140 .anchor()
7141 .anchor
7142 .to_point(&multi_buffer_snapshot);
7143 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7144 multi_buffer_visible_start
7145 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7146 Bias::Left,
7147 );
7148 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7149 self.quick_selection_highlight_task = Some((
7150 query_offset.clone(),
7151 self.update_selection_occurrence_highlights(
7152 query_text.clone(),
7153 query_range.clone(),
7154 multi_buffer_visible_range,
7155 false,
7156 window,
7157 cx,
7158 ),
7159 ));
7160 }
7161 if on_buffer_edit
7162 || self
7163 .debounced_selection_highlight_task
7164 .as_ref()
7165 .is_none_or(|(prev_query_offset, _)| prev_query_offset != &query_offset)
7166 {
7167 let multi_buffer_start = multi_buffer_snapshot
7168 .anchor_before(0)
7169 .to_point(&multi_buffer_snapshot);
7170 let multi_buffer_end = multi_buffer_snapshot
7171 .anchor_after(multi_buffer_snapshot.len())
7172 .to_point(&multi_buffer_snapshot);
7173 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7174 self.debounced_selection_highlight_task = Some((
7175 query_offset,
7176 self.update_selection_occurrence_highlights(
7177 query_text,
7178 query_range,
7179 multi_buffer_full_range,
7180 true,
7181 window,
7182 cx,
7183 ),
7184 ));
7185 }
7186 }
7187
7188 pub fn refresh_edit_prediction(
7189 &mut self,
7190 debounce: bool,
7191 user_requested: bool,
7192 window: &mut Window,
7193 cx: &mut Context<Self>,
7194 ) -> Option<()> {
7195 if DisableAiSettings::get_global(cx).disable_ai {
7196 return None;
7197 }
7198
7199 let provider = self.edit_prediction_provider()?;
7200 let cursor = self.selections.newest_anchor().head();
7201 let (buffer, cursor_buffer_position) =
7202 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7203
7204 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7205 self.discard_edit_prediction(false, cx);
7206 return None;
7207 }
7208
7209 self.update_visible_edit_prediction(window, cx);
7210
7211 if !user_requested
7212 && (!self.should_show_edit_predictions()
7213 || !self.is_focused(window)
7214 || buffer.read(cx).is_empty())
7215 {
7216 self.discard_edit_prediction(false, cx);
7217 return None;
7218 }
7219
7220 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7221 Some(())
7222 }
7223
7224 fn show_edit_predictions_in_menu(&self) -> bool {
7225 match self.edit_prediction_settings {
7226 EditPredictionSettings::Disabled => false,
7227 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7228 }
7229 }
7230
7231 pub fn edit_predictions_enabled(&self) -> bool {
7232 match self.edit_prediction_settings {
7233 EditPredictionSettings::Disabled => false,
7234 EditPredictionSettings::Enabled { .. } => true,
7235 }
7236 }
7237
7238 fn edit_prediction_requires_modifier(&self) -> bool {
7239 match self.edit_prediction_settings {
7240 EditPredictionSettings::Disabled => false,
7241 EditPredictionSettings::Enabled {
7242 preview_requires_modifier,
7243 ..
7244 } => preview_requires_modifier,
7245 }
7246 }
7247
7248 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7249 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7250 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7251 self.discard_edit_prediction(false, cx);
7252 } else {
7253 let selection = self.selections.newest_anchor();
7254 let cursor = selection.head();
7255
7256 if let Some((buffer, cursor_buffer_position)) =
7257 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7258 {
7259 self.edit_prediction_settings =
7260 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7261 }
7262 }
7263 }
7264
7265 fn edit_prediction_settings_at_position(
7266 &self,
7267 buffer: &Entity<Buffer>,
7268 buffer_position: language::Anchor,
7269 cx: &App,
7270 ) -> EditPredictionSettings {
7271 if !self.mode.is_full()
7272 || !self.show_edit_predictions_override.unwrap_or(true)
7273 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7274 {
7275 return EditPredictionSettings::Disabled;
7276 }
7277
7278 let buffer = buffer.read(cx);
7279
7280 let file = buffer.file();
7281
7282 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7283 return EditPredictionSettings::Disabled;
7284 };
7285
7286 let by_provider = matches!(
7287 self.menu_edit_predictions_policy,
7288 MenuEditPredictionsPolicy::ByProvider
7289 );
7290
7291 let show_in_menu = by_provider
7292 && self
7293 .edit_prediction_provider
7294 .as_ref()
7295 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7296
7297 let preview_requires_modifier =
7298 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7299
7300 EditPredictionSettings::Enabled {
7301 show_in_menu,
7302 preview_requires_modifier,
7303 }
7304 }
7305
7306 fn should_show_edit_predictions(&self) -> bool {
7307 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7308 }
7309
7310 pub fn edit_prediction_preview_is_active(&self) -> bool {
7311 matches!(
7312 self.edit_prediction_preview,
7313 EditPredictionPreview::Active { .. }
7314 )
7315 }
7316
7317 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7318 let cursor = self.selections.newest_anchor().head();
7319 if let Some((buffer, cursor_position)) =
7320 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7321 {
7322 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7323 } else {
7324 false
7325 }
7326 }
7327
7328 pub fn supports_minimap(&self, cx: &App) -> bool {
7329 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7330 }
7331
7332 fn edit_predictions_enabled_in_buffer(
7333 &self,
7334 buffer: &Entity<Buffer>,
7335 buffer_position: language::Anchor,
7336 cx: &App,
7337 ) -> bool {
7338 maybe!({
7339 if self.read_only(cx) {
7340 return Some(false);
7341 }
7342 let provider = self.edit_prediction_provider()?;
7343 if !provider.is_enabled(buffer, buffer_position, cx) {
7344 return Some(false);
7345 }
7346 let buffer = buffer.read(cx);
7347 let Some(file) = buffer.file() else {
7348 return Some(true);
7349 };
7350 let settings = all_language_settings(Some(file), cx);
7351 Some(settings.edit_predictions_enabled_for_file(file, cx))
7352 })
7353 .unwrap_or(false)
7354 }
7355
7356 fn cycle_edit_prediction(
7357 &mut self,
7358 direction: Direction,
7359 window: &mut Window,
7360 cx: &mut Context<Self>,
7361 ) -> Option<()> {
7362 let provider = self.edit_prediction_provider()?;
7363 let cursor = self.selections.newest_anchor().head();
7364 let (buffer, cursor_buffer_position) =
7365 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7366 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7367 return None;
7368 }
7369
7370 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7371 self.update_visible_edit_prediction(window, cx);
7372
7373 Some(())
7374 }
7375
7376 pub fn show_edit_prediction(
7377 &mut self,
7378 _: &ShowEditPrediction,
7379 window: &mut Window,
7380 cx: &mut Context<Self>,
7381 ) {
7382 if !self.has_active_edit_prediction() {
7383 self.refresh_edit_prediction(false, true, window, cx);
7384 return;
7385 }
7386
7387 self.update_visible_edit_prediction(window, cx);
7388 }
7389
7390 pub fn display_cursor_names(
7391 &mut self,
7392 _: &DisplayCursorNames,
7393 window: &mut Window,
7394 cx: &mut Context<Self>,
7395 ) {
7396 self.show_cursor_names(window, cx);
7397 }
7398
7399 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7400 self.show_cursor_names = true;
7401 cx.notify();
7402 cx.spawn_in(window, async move |this, cx| {
7403 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7404 this.update(cx, |this, cx| {
7405 this.show_cursor_names = false;
7406 cx.notify()
7407 })
7408 .ok()
7409 })
7410 .detach();
7411 }
7412
7413 pub fn next_edit_prediction(
7414 &mut self,
7415 _: &NextEditPrediction,
7416 window: &mut Window,
7417 cx: &mut Context<Self>,
7418 ) {
7419 if self.has_active_edit_prediction() {
7420 self.cycle_edit_prediction(Direction::Next, window, cx);
7421 } else {
7422 let is_copilot_disabled = self
7423 .refresh_edit_prediction(false, true, window, cx)
7424 .is_none();
7425 if is_copilot_disabled {
7426 cx.propagate();
7427 }
7428 }
7429 }
7430
7431 pub fn previous_edit_prediction(
7432 &mut self,
7433 _: &PreviousEditPrediction,
7434 window: &mut Window,
7435 cx: &mut Context<Self>,
7436 ) {
7437 if self.has_active_edit_prediction() {
7438 self.cycle_edit_prediction(Direction::Prev, window, cx);
7439 } else {
7440 let is_copilot_disabled = self
7441 .refresh_edit_prediction(false, true, window, cx)
7442 .is_none();
7443 if is_copilot_disabled {
7444 cx.propagate();
7445 }
7446 }
7447 }
7448
7449 pub fn accept_edit_prediction(
7450 &mut self,
7451 _: &AcceptEditPrediction,
7452 window: &mut Window,
7453 cx: &mut Context<Self>,
7454 ) {
7455 if self.show_edit_predictions_in_menu() {
7456 self.hide_context_menu(window, cx);
7457 }
7458
7459 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7460 return;
7461 };
7462
7463 match &active_edit_prediction.completion {
7464 EditPrediction::MoveWithin { target, .. } => {
7465 let target = *target;
7466
7467 if let Some(position_map) = &self.last_position_map {
7468 if position_map
7469 .visible_row_range
7470 .contains(&target.to_display_point(&position_map.snapshot).row())
7471 || !self.edit_prediction_requires_modifier()
7472 {
7473 self.unfold_ranges(&[target..target], true, false, cx);
7474 // Note that this is also done in vim's handler of the Tab action.
7475 self.change_selections(
7476 SelectionEffects::scroll(Autoscroll::newest()),
7477 window,
7478 cx,
7479 |selections| {
7480 selections.select_anchor_ranges([target..target]);
7481 },
7482 );
7483 self.clear_row_highlights::<EditPredictionPreview>();
7484
7485 self.edit_prediction_preview
7486 .set_previous_scroll_position(None);
7487 } else {
7488 self.edit_prediction_preview
7489 .set_previous_scroll_position(Some(
7490 position_map.snapshot.scroll_anchor,
7491 ));
7492
7493 self.highlight_rows::<EditPredictionPreview>(
7494 target..target,
7495 cx.theme().colors().editor_highlighted_line_background,
7496 RowHighlightOptions {
7497 autoscroll: true,
7498 ..Default::default()
7499 },
7500 cx,
7501 );
7502 self.request_autoscroll(Autoscroll::fit(), cx);
7503 }
7504 }
7505 }
7506 EditPrediction::MoveOutside { snapshot, target } => {
7507 if let Some(workspace) = self.workspace() {
7508 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7509 .detach_and_log_err(cx);
7510 }
7511 }
7512 EditPrediction::Edit { edits, .. } => {
7513 self.report_edit_prediction_event(
7514 active_edit_prediction.completion_id.clone(),
7515 true,
7516 cx,
7517 );
7518
7519 if let Some(provider) = self.edit_prediction_provider() {
7520 provider.accept(cx);
7521 }
7522
7523 // Store the transaction ID and selections before applying the edit
7524 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7525
7526 let snapshot = self.buffer.read(cx).snapshot(cx);
7527 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7528
7529 self.buffer.update(cx, |buffer, cx| {
7530 buffer.edit(edits.iter().cloned(), None, cx)
7531 });
7532
7533 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7534 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7535 });
7536
7537 let selections = self.selections.disjoint_anchors_arc();
7538 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7539 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7540 if has_new_transaction {
7541 self.selection_history
7542 .insert_transaction(transaction_id_now, selections);
7543 }
7544 }
7545
7546 self.update_visible_edit_prediction(window, cx);
7547 if self.active_edit_prediction.is_none() {
7548 self.refresh_edit_prediction(true, true, window, cx);
7549 }
7550
7551 cx.notify();
7552 }
7553 }
7554
7555 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7556 }
7557
7558 pub fn accept_partial_edit_prediction(
7559 &mut self,
7560 _: &AcceptPartialEditPrediction,
7561 window: &mut Window,
7562 cx: &mut Context<Self>,
7563 ) {
7564 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7565 return;
7566 };
7567 if self.selections.count() != 1 {
7568 return;
7569 }
7570
7571 match &active_edit_prediction.completion {
7572 EditPrediction::MoveWithin { target, .. } => {
7573 let target = *target;
7574 self.change_selections(
7575 SelectionEffects::scroll(Autoscroll::newest()),
7576 window,
7577 cx,
7578 |selections| {
7579 selections.select_anchor_ranges([target..target]);
7580 },
7581 );
7582 }
7583 EditPrediction::MoveOutside { snapshot, target } => {
7584 if let Some(workspace) = self.workspace() {
7585 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7586 .detach_and_log_err(cx);
7587 }
7588 }
7589 EditPrediction::Edit { edits, .. } => {
7590 self.report_edit_prediction_event(
7591 active_edit_prediction.completion_id.clone(),
7592 true,
7593 cx,
7594 );
7595
7596 // Find an insertion that starts at the cursor position.
7597 let snapshot = self.buffer.read(cx).snapshot(cx);
7598 let cursor_offset = self.selections.newest::<usize>(cx).head();
7599 let insertion = edits.iter().find_map(|(range, text)| {
7600 let range = range.to_offset(&snapshot);
7601 if range.is_empty() && range.start == cursor_offset {
7602 Some(text)
7603 } else {
7604 None
7605 }
7606 });
7607
7608 if let Some(text) = insertion {
7609 let mut partial_completion = text
7610 .chars()
7611 .by_ref()
7612 .take_while(|c| c.is_alphabetic())
7613 .collect::<String>();
7614 if partial_completion.is_empty() {
7615 partial_completion = text
7616 .chars()
7617 .by_ref()
7618 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7619 .collect::<String>();
7620 }
7621
7622 cx.emit(EditorEvent::InputHandled {
7623 utf16_range_to_replace: None,
7624 text: partial_completion.clone().into(),
7625 });
7626
7627 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7628
7629 self.refresh_edit_prediction(true, true, window, cx);
7630 cx.notify();
7631 } else {
7632 self.accept_edit_prediction(&Default::default(), window, cx);
7633 }
7634 }
7635 }
7636 }
7637
7638 fn discard_edit_prediction(
7639 &mut self,
7640 should_report_edit_prediction_event: bool,
7641 cx: &mut Context<Self>,
7642 ) -> bool {
7643 if should_report_edit_prediction_event {
7644 let completion_id = self
7645 .active_edit_prediction
7646 .as_ref()
7647 .and_then(|active_completion| active_completion.completion_id.clone());
7648
7649 self.report_edit_prediction_event(completion_id, false, cx);
7650 }
7651
7652 if let Some(provider) = self.edit_prediction_provider() {
7653 provider.discard(cx);
7654 }
7655
7656 self.take_active_edit_prediction(cx)
7657 }
7658
7659 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7660 let Some(provider) = self.edit_prediction_provider() else {
7661 return;
7662 };
7663
7664 let Some((_, buffer, _)) = self
7665 .buffer
7666 .read(cx)
7667 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7668 else {
7669 return;
7670 };
7671
7672 let extension = buffer
7673 .read(cx)
7674 .file()
7675 .and_then(|file| Some(file.path().extension()?.to_string()));
7676
7677 let event_type = match accepted {
7678 true => "Edit Prediction Accepted",
7679 false => "Edit Prediction Discarded",
7680 };
7681 telemetry::event!(
7682 event_type,
7683 provider = provider.name(),
7684 prediction_id = id,
7685 suggestion_accepted = accepted,
7686 file_extension = extension,
7687 );
7688 }
7689
7690 fn open_editor_at_anchor(
7691 snapshot: &language::BufferSnapshot,
7692 target: language::Anchor,
7693 workspace: &Entity<Workspace>,
7694 window: &mut Window,
7695 cx: &mut App,
7696 ) -> Task<Result<()>> {
7697 workspace.update(cx, |workspace, cx| {
7698 let path = snapshot.file().map(|file| file.full_path(cx));
7699 let Some(path) =
7700 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7701 else {
7702 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7703 };
7704 let target = text::ToPoint::to_point(&target, snapshot);
7705 let item = workspace.open_path(path, None, true, window, cx);
7706 window.spawn(cx, async move |cx| {
7707 let Some(editor) = item.await?.downcast::<Editor>() else {
7708 return Ok(());
7709 };
7710 editor
7711 .update_in(cx, |editor, window, cx| {
7712 editor.go_to_singleton_buffer_point(target, window, cx);
7713 })
7714 .ok();
7715 anyhow::Ok(())
7716 })
7717 })
7718 }
7719
7720 pub fn has_active_edit_prediction(&self) -> bool {
7721 self.active_edit_prediction.is_some()
7722 }
7723
7724 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7725 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7726 return false;
7727 };
7728
7729 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7730 self.clear_highlights::<EditPredictionHighlight>(cx);
7731 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7732 true
7733 }
7734
7735 /// Returns true when we're displaying the edit prediction popover below the cursor
7736 /// like we are not previewing and the LSP autocomplete menu is visible
7737 /// or we are in `when_holding_modifier` mode.
7738 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7739 if self.edit_prediction_preview_is_active()
7740 || !self.show_edit_predictions_in_menu()
7741 || !self.edit_predictions_enabled()
7742 {
7743 return false;
7744 }
7745
7746 if self.has_visible_completions_menu() {
7747 return true;
7748 }
7749
7750 has_completion && self.edit_prediction_requires_modifier()
7751 }
7752
7753 fn handle_modifiers_changed(
7754 &mut self,
7755 modifiers: Modifiers,
7756 position_map: &PositionMap,
7757 window: &mut Window,
7758 cx: &mut Context<Self>,
7759 ) {
7760 if self.show_edit_predictions_in_menu() {
7761 self.update_edit_prediction_preview(&modifiers, window, cx);
7762 }
7763
7764 self.update_selection_mode(&modifiers, position_map, window, cx);
7765
7766 let mouse_position = window.mouse_position();
7767 if !position_map.text_hitbox.is_hovered(window) {
7768 return;
7769 }
7770
7771 self.update_hovered_link(
7772 position_map.point_for_position(mouse_position),
7773 &position_map.snapshot,
7774 modifiers,
7775 window,
7776 cx,
7777 )
7778 }
7779
7780 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7781 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7782 if invert {
7783 match multi_cursor_setting {
7784 MultiCursorModifier::Alt => modifiers.alt,
7785 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7786 }
7787 } else {
7788 match multi_cursor_setting {
7789 MultiCursorModifier::Alt => modifiers.secondary(),
7790 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7791 }
7792 }
7793 }
7794
7795 fn columnar_selection_mode(
7796 modifiers: &Modifiers,
7797 cx: &mut Context<Self>,
7798 ) -> Option<ColumnarMode> {
7799 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7800 if Self::multi_cursor_modifier(false, modifiers, cx) {
7801 Some(ColumnarMode::FromMouse)
7802 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7803 Some(ColumnarMode::FromSelection)
7804 } else {
7805 None
7806 }
7807 } else {
7808 None
7809 }
7810 }
7811
7812 fn update_selection_mode(
7813 &mut self,
7814 modifiers: &Modifiers,
7815 position_map: &PositionMap,
7816 window: &mut Window,
7817 cx: &mut Context<Self>,
7818 ) {
7819 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7820 return;
7821 };
7822 if self.selections.pending_anchor().is_none() {
7823 return;
7824 }
7825
7826 let mouse_position = window.mouse_position();
7827 let point_for_position = position_map.point_for_position(mouse_position);
7828 let position = point_for_position.previous_valid;
7829
7830 self.select(
7831 SelectPhase::BeginColumnar {
7832 position,
7833 reset: false,
7834 mode,
7835 goal_column: point_for_position.exact_unclipped.column(),
7836 },
7837 window,
7838 cx,
7839 );
7840 }
7841
7842 fn update_edit_prediction_preview(
7843 &mut self,
7844 modifiers: &Modifiers,
7845 window: &mut Window,
7846 cx: &mut Context<Self>,
7847 ) {
7848 let mut modifiers_held = false;
7849 if let Some(accept_keystroke) = self
7850 .accept_edit_prediction_keybind(false, window, cx)
7851 .keystroke()
7852 {
7853 modifiers_held = modifiers_held
7854 || (accept_keystroke.modifiers() == modifiers
7855 && accept_keystroke.modifiers().modified());
7856 };
7857 if let Some(accept_partial_keystroke) = self
7858 .accept_edit_prediction_keybind(true, window, cx)
7859 .keystroke()
7860 {
7861 modifiers_held = modifiers_held
7862 || (accept_partial_keystroke.modifiers() == modifiers
7863 && accept_partial_keystroke.modifiers().modified());
7864 }
7865
7866 if modifiers_held {
7867 if matches!(
7868 self.edit_prediction_preview,
7869 EditPredictionPreview::Inactive { .. }
7870 ) {
7871 self.edit_prediction_preview = EditPredictionPreview::Active {
7872 previous_scroll_position: None,
7873 since: Instant::now(),
7874 };
7875
7876 self.update_visible_edit_prediction(window, cx);
7877 cx.notify();
7878 }
7879 } else if let EditPredictionPreview::Active {
7880 previous_scroll_position,
7881 since,
7882 } = self.edit_prediction_preview
7883 {
7884 if let (Some(previous_scroll_position), Some(position_map)) =
7885 (previous_scroll_position, self.last_position_map.as_ref())
7886 {
7887 self.set_scroll_position(
7888 previous_scroll_position
7889 .scroll_position(&position_map.snapshot.display_snapshot),
7890 window,
7891 cx,
7892 );
7893 }
7894
7895 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7896 released_too_fast: since.elapsed() < Duration::from_millis(200),
7897 };
7898 self.clear_row_highlights::<EditPredictionPreview>();
7899 self.update_visible_edit_prediction(window, cx);
7900 cx.notify();
7901 }
7902 }
7903
7904 fn update_visible_edit_prediction(
7905 &mut self,
7906 _window: &mut Window,
7907 cx: &mut Context<Self>,
7908 ) -> Option<()> {
7909 if DisableAiSettings::get_global(cx).disable_ai {
7910 return None;
7911 }
7912
7913 if self.ime_transaction.is_some() {
7914 self.discard_edit_prediction(false, cx);
7915 return None;
7916 }
7917
7918 let selection = self.selections.newest_anchor();
7919 let cursor = selection.head();
7920 let multibuffer = self.buffer.read(cx).snapshot(cx);
7921 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7922 let excerpt_id = cursor.excerpt_id;
7923
7924 let show_in_menu = self.show_edit_predictions_in_menu();
7925 let completions_menu_has_precedence = !show_in_menu
7926 && (self.context_menu.borrow().is_some()
7927 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7928
7929 if completions_menu_has_precedence
7930 || !offset_selection.is_empty()
7931 || self
7932 .active_edit_prediction
7933 .as_ref()
7934 .is_some_and(|completion| {
7935 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7936 return false;
7937 };
7938 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7939 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7940 !invalidation_range.contains(&offset_selection.head())
7941 })
7942 {
7943 self.discard_edit_prediction(false, cx);
7944 return None;
7945 }
7946
7947 self.take_active_edit_prediction(cx);
7948 let Some(provider) = self.edit_prediction_provider() else {
7949 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7950 return None;
7951 };
7952
7953 let (buffer, cursor_buffer_position) =
7954 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7955
7956 self.edit_prediction_settings =
7957 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7958
7959 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7960
7961 if self.edit_prediction_indent_conflict {
7962 let cursor_point = cursor.to_point(&multibuffer);
7963
7964 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7965
7966 if let Some((_, indent)) = indents.iter().next()
7967 && indent.len == cursor_point.column
7968 {
7969 self.edit_prediction_indent_conflict = false;
7970 }
7971 }
7972
7973 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7974
7975 let (completion_id, edits, edit_preview) = match edit_prediction {
7976 edit_prediction::EditPrediction::Local {
7977 id,
7978 edits,
7979 edit_preview,
7980 } => (id, edits, edit_preview),
7981 edit_prediction::EditPrediction::Jump {
7982 id,
7983 snapshot,
7984 target,
7985 } => {
7986 self.stale_edit_prediction_in_menu = None;
7987 self.active_edit_prediction = Some(EditPredictionState {
7988 inlay_ids: vec![],
7989 completion: EditPrediction::MoveOutside { snapshot, target },
7990 completion_id: id,
7991 invalidation_range: None,
7992 });
7993 cx.notify();
7994 return Some(());
7995 }
7996 };
7997
7998 let edits = edits
7999 .into_iter()
8000 .flat_map(|(range, new_text)| {
8001 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
8002 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
8003 Some((start..end, new_text))
8004 })
8005 .collect::<Vec<_>>();
8006 if edits.is_empty() {
8007 return None;
8008 }
8009
8010 let first_edit_start = edits.first().unwrap().0.start;
8011 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8012 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8013
8014 let last_edit_end = edits.last().unwrap().0.end;
8015 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8016 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8017
8018 let cursor_row = cursor.to_point(&multibuffer).row;
8019
8020 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8021
8022 let mut inlay_ids = Vec::new();
8023 let invalidation_row_range;
8024 let move_invalidation_row_range = if cursor_row < edit_start_row {
8025 Some(cursor_row..edit_end_row)
8026 } else if cursor_row > edit_end_row {
8027 Some(edit_start_row..cursor_row)
8028 } else {
8029 None
8030 };
8031 let supports_jump = self
8032 .edit_prediction_provider
8033 .as_ref()
8034 .map(|provider| provider.provider.supports_jump_to_edit())
8035 .unwrap_or(true);
8036
8037 let is_move = supports_jump
8038 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8039 let completion = if is_move {
8040 invalidation_row_range =
8041 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8042 let target = first_edit_start;
8043 EditPrediction::MoveWithin { target, snapshot }
8044 } else {
8045 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8046 && !self.edit_predictions_hidden_for_vim_mode;
8047
8048 if show_completions_in_buffer {
8049 if edits
8050 .iter()
8051 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8052 {
8053 let mut inlays = Vec::new();
8054 for (range, new_text) in &edits {
8055 let inlay = Inlay::edit_prediction(
8056 post_inc(&mut self.next_inlay_id),
8057 range.start,
8058 new_text.as_str(),
8059 );
8060 inlay_ids.push(inlay.id);
8061 inlays.push(inlay);
8062 }
8063
8064 self.splice_inlays(&[], inlays, cx);
8065 } else {
8066 let background_color = cx.theme().status().deleted_background;
8067 self.highlight_text::<EditPredictionHighlight>(
8068 edits.iter().map(|(range, _)| range.clone()).collect(),
8069 HighlightStyle {
8070 background_color: Some(background_color),
8071 ..Default::default()
8072 },
8073 cx,
8074 );
8075 }
8076 }
8077
8078 invalidation_row_range = edit_start_row..edit_end_row;
8079
8080 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8081 if provider.show_tab_accept_marker() {
8082 EditDisplayMode::TabAccept
8083 } else {
8084 EditDisplayMode::Inline
8085 }
8086 } else {
8087 EditDisplayMode::DiffPopover
8088 };
8089
8090 EditPrediction::Edit {
8091 edits,
8092 edit_preview,
8093 display_mode,
8094 snapshot,
8095 }
8096 };
8097
8098 let invalidation_range = multibuffer
8099 .anchor_before(Point::new(invalidation_row_range.start, 0))
8100 ..multibuffer.anchor_after(Point::new(
8101 invalidation_row_range.end,
8102 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8103 ));
8104
8105 self.stale_edit_prediction_in_menu = None;
8106 self.active_edit_prediction = Some(EditPredictionState {
8107 inlay_ids,
8108 completion,
8109 completion_id,
8110 invalidation_range: Some(invalidation_range),
8111 });
8112
8113 cx.notify();
8114
8115 Some(())
8116 }
8117
8118 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8119 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8120 }
8121
8122 fn clear_tasks(&mut self) {
8123 self.tasks.clear()
8124 }
8125
8126 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8127 if self.tasks.insert(key, value).is_some() {
8128 // This case should hopefully be rare, but just in case...
8129 log::error!(
8130 "multiple different run targets found on a single line, only the last target will be rendered"
8131 )
8132 }
8133 }
8134
8135 /// Get all display points of breakpoints that will be rendered within editor
8136 ///
8137 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8138 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8139 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8140 fn active_breakpoints(
8141 &self,
8142 range: Range<DisplayRow>,
8143 window: &mut Window,
8144 cx: &mut Context<Self>,
8145 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8146 let mut breakpoint_display_points = HashMap::default();
8147
8148 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8149 return breakpoint_display_points;
8150 };
8151
8152 let snapshot = self.snapshot(window, cx);
8153
8154 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8155 let Some(project) = self.project() else {
8156 return breakpoint_display_points;
8157 };
8158
8159 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8160 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8161
8162 for (buffer_snapshot, range, excerpt_id) in
8163 multi_buffer_snapshot.range_to_buffer_ranges(range)
8164 {
8165 let Some(buffer) = project
8166 .read(cx)
8167 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8168 else {
8169 continue;
8170 };
8171 let breakpoints = breakpoint_store.read(cx).breakpoints(
8172 &buffer,
8173 Some(
8174 buffer_snapshot.anchor_before(range.start)
8175 ..buffer_snapshot.anchor_after(range.end),
8176 ),
8177 buffer_snapshot,
8178 cx,
8179 );
8180 for (breakpoint, state) in breakpoints {
8181 let multi_buffer_anchor =
8182 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8183 let position = multi_buffer_anchor
8184 .to_point(multi_buffer_snapshot)
8185 .to_display_point(&snapshot);
8186
8187 breakpoint_display_points.insert(
8188 position.row(),
8189 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8190 );
8191 }
8192 }
8193
8194 breakpoint_display_points
8195 }
8196
8197 fn breakpoint_context_menu(
8198 &self,
8199 anchor: Anchor,
8200 window: &mut Window,
8201 cx: &mut Context<Self>,
8202 ) -> Entity<ui::ContextMenu> {
8203 let weak_editor = cx.weak_entity();
8204 let focus_handle = self.focus_handle(cx);
8205
8206 let row = self
8207 .buffer
8208 .read(cx)
8209 .snapshot(cx)
8210 .summary_for_anchor::<Point>(&anchor)
8211 .row;
8212
8213 let breakpoint = self
8214 .breakpoint_at_row(row, window, cx)
8215 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8216
8217 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8218 "Edit Log Breakpoint"
8219 } else {
8220 "Set Log Breakpoint"
8221 };
8222
8223 let condition_breakpoint_msg = if breakpoint
8224 .as_ref()
8225 .is_some_and(|bp| bp.1.condition.is_some())
8226 {
8227 "Edit Condition Breakpoint"
8228 } else {
8229 "Set Condition Breakpoint"
8230 };
8231
8232 let hit_condition_breakpoint_msg = if breakpoint
8233 .as_ref()
8234 .is_some_and(|bp| bp.1.hit_condition.is_some())
8235 {
8236 "Edit Hit Condition Breakpoint"
8237 } else {
8238 "Set Hit Condition Breakpoint"
8239 };
8240
8241 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8242 "Unset Breakpoint"
8243 } else {
8244 "Set Breakpoint"
8245 };
8246
8247 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8248
8249 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8250 BreakpointState::Enabled => Some("Disable"),
8251 BreakpointState::Disabled => Some("Enable"),
8252 });
8253
8254 let (anchor, breakpoint) =
8255 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8256
8257 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8258 menu.on_blur_subscription(Subscription::new(|| {}))
8259 .context(focus_handle)
8260 .when(run_to_cursor, |this| {
8261 let weak_editor = weak_editor.clone();
8262 this.entry("Run to cursor", None, move |window, cx| {
8263 weak_editor
8264 .update(cx, |editor, cx| {
8265 editor.change_selections(
8266 SelectionEffects::no_scroll(),
8267 window,
8268 cx,
8269 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8270 );
8271 })
8272 .ok();
8273
8274 window.dispatch_action(Box::new(RunToCursor), cx);
8275 })
8276 .separator()
8277 })
8278 .when_some(toggle_state_msg, |this, msg| {
8279 this.entry(msg, None, {
8280 let weak_editor = weak_editor.clone();
8281 let breakpoint = breakpoint.clone();
8282 move |_window, cx| {
8283 weak_editor
8284 .update(cx, |this, cx| {
8285 this.edit_breakpoint_at_anchor(
8286 anchor,
8287 breakpoint.as_ref().clone(),
8288 BreakpointEditAction::InvertState,
8289 cx,
8290 );
8291 })
8292 .log_err();
8293 }
8294 })
8295 })
8296 .entry(set_breakpoint_msg, None, {
8297 let weak_editor = weak_editor.clone();
8298 let breakpoint = breakpoint.clone();
8299 move |_window, cx| {
8300 weak_editor
8301 .update(cx, |this, cx| {
8302 this.edit_breakpoint_at_anchor(
8303 anchor,
8304 breakpoint.as_ref().clone(),
8305 BreakpointEditAction::Toggle,
8306 cx,
8307 );
8308 })
8309 .log_err();
8310 }
8311 })
8312 .entry(log_breakpoint_msg, None, {
8313 let breakpoint = breakpoint.clone();
8314 let weak_editor = weak_editor.clone();
8315 move |window, cx| {
8316 weak_editor
8317 .update(cx, |this, cx| {
8318 this.add_edit_breakpoint_block(
8319 anchor,
8320 breakpoint.as_ref(),
8321 BreakpointPromptEditAction::Log,
8322 window,
8323 cx,
8324 );
8325 })
8326 .log_err();
8327 }
8328 })
8329 .entry(condition_breakpoint_msg, None, {
8330 let breakpoint = breakpoint.clone();
8331 let weak_editor = weak_editor.clone();
8332 move |window, cx| {
8333 weak_editor
8334 .update(cx, |this, cx| {
8335 this.add_edit_breakpoint_block(
8336 anchor,
8337 breakpoint.as_ref(),
8338 BreakpointPromptEditAction::Condition,
8339 window,
8340 cx,
8341 );
8342 })
8343 .log_err();
8344 }
8345 })
8346 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8347 weak_editor
8348 .update(cx, |this, cx| {
8349 this.add_edit_breakpoint_block(
8350 anchor,
8351 breakpoint.as_ref(),
8352 BreakpointPromptEditAction::HitCondition,
8353 window,
8354 cx,
8355 );
8356 })
8357 .log_err();
8358 })
8359 })
8360 }
8361
8362 fn render_breakpoint(
8363 &self,
8364 position: Anchor,
8365 row: DisplayRow,
8366 breakpoint: &Breakpoint,
8367 state: Option<BreakpointSessionState>,
8368 cx: &mut Context<Self>,
8369 ) -> IconButton {
8370 let is_rejected = state.is_some_and(|s| !s.verified);
8371 // Is it a breakpoint that shows up when hovering over gutter?
8372 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8373 (false, false),
8374 |PhantomBreakpointIndicator {
8375 is_active,
8376 display_row,
8377 collides_with_existing_breakpoint,
8378 }| {
8379 (
8380 is_active && display_row == row,
8381 collides_with_existing_breakpoint,
8382 )
8383 },
8384 );
8385
8386 let (color, icon) = {
8387 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8388 (false, false) => ui::IconName::DebugBreakpoint,
8389 (true, false) => ui::IconName::DebugLogBreakpoint,
8390 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8391 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8392 };
8393
8394 let color = if is_phantom {
8395 Color::Hint
8396 } else if is_rejected {
8397 Color::Disabled
8398 } else {
8399 Color::Debugger
8400 };
8401
8402 (color, icon)
8403 };
8404
8405 let breakpoint = Arc::from(breakpoint.clone());
8406
8407 let alt_as_text = gpui::Keystroke {
8408 modifiers: Modifiers::secondary_key(),
8409 ..Default::default()
8410 };
8411 let primary_action_text = if breakpoint.is_disabled() {
8412 "Enable breakpoint"
8413 } else if is_phantom && !collides_with_existing {
8414 "Set breakpoint"
8415 } else {
8416 "Unset breakpoint"
8417 };
8418 let focus_handle = self.focus_handle.clone();
8419
8420 let meta = if is_rejected {
8421 SharedString::from("No executable code is associated with this line.")
8422 } else if collides_with_existing && !breakpoint.is_disabled() {
8423 SharedString::from(format!(
8424 "{alt_as_text}-click to disable,\nright-click for more options."
8425 ))
8426 } else {
8427 SharedString::from("Right-click for more options.")
8428 };
8429 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8430 .icon_size(IconSize::XSmall)
8431 .size(ui::ButtonSize::None)
8432 .when(is_rejected, |this| {
8433 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8434 })
8435 .icon_color(color)
8436 .style(ButtonStyle::Transparent)
8437 .on_click(cx.listener({
8438 move |editor, event: &ClickEvent, window, cx| {
8439 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8440 BreakpointEditAction::InvertState
8441 } else {
8442 BreakpointEditAction::Toggle
8443 };
8444
8445 window.focus(&editor.focus_handle(cx));
8446 editor.edit_breakpoint_at_anchor(
8447 position,
8448 breakpoint.as_ref().clone(),
8449 edit_action,
8450 cx,
8451 );
8452 }
8453 }))
8454 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8455 editor.set_breakpoint_context_menu(
8456 row,
8457 Some(position),
8458 event.position(),
8459 window,
8460 cx,
8461 );
8462 }))
8463 .tooltip(move |window, cx| {
8464 Tooltip::with_meta_in(
8465 primary_action_text,
8466 Some(&ToggleBreakpoint),
8467 meta.clone(),
8468 &focus_handle,
8469 window,
8470 cx,
8471 )
8472 })
8473 }
8474
8475 fn build_tasks_context(
8476 project: &Entity<Project>,
8477 buffer: &Entity<Buffer>,
8478 buffer_row: u32,
8479 tasks: &Arc<RunnableTasks>,
8480 cx: &mut Context<Self>,
8481 ) -> Task<Option<task::TaskContext>> {
8482 let position = Point::new(buffer_row, tasks.column);
8483 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8484 let location = Location {
8485 buffer: buffer.clone(),
8486 range: range_start..range_start,
8487 };
8488 // Fill in the environmental variables from the tree-sitter captures
8489 let mut captured_task_variables = TaskVariables::default();
8490 for (capture_name, value) in tasks.extra_variables.clone() {
8491 captured_task_variables.insert(
8492 task::VariableName::Custom(capture_name.into()),
8493 value.clone(),
8494 );
8495 }
8496 project.update(cx, |project, cx| {
8497 project.task_store().update(cx, |task_store, cx| {
8498 task_store.task_context_for_location(captured_task_variables, location, cx)
8499 })
8500 })
8501 }
8502
8503 pub fn spawn_nearest_task(
8504 &mut self,
8505 action: &SpawnNearestTask,
8506 window: &mut Window,
8507 cx: &mut Context<Self>,
8508 ) {
8509 let Some((workspace, _)) = self.workspace.clone() else {
8510 return;
8511 };
8512 let Some(project) = self.project.clone() else {
8513 return;
8514 };
8515
8516 // Try to find a closest, enclosing node using tree-sitter that has a task
8517 let Some((buffer, buffer_row, tasks)) = self
8518 .find_enclosing_node_task(cx)
8519 // Or find the task that's closest in row-distance.
8520 .or_else(|| self.find_closest_task(cx))
8521 else {
8522 return;
8523 };
8524
8525 let reveal_strategy = action.reveal;
8526 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8527 cx.spawn_in(window, async move |_, cx| {
8528 let context = task_context.await?;
8529 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8530
8531 let resolved = &mut resolved_task.resolved;
8532 resolved.reveal = reveal_strategy;
8533
8534 workspace
8535 .update_in(cx, |workspace, window, cx| {
8536 workspace.schedule_resolved_task(
8537 task_source_kind,
8538 resolved_task,
8539 false,
8540 window,
8541 cx,
8542 );
8543 })
8544 .ok()
8545 })
8546 .detach();
8547 }
8548
8549 fn find_closest_task(
8550 &mut self,
8551 cx: &mut Context<Self>,
8552 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8553 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8554
8555 let ((buffer_id, row), tasks) = self
8556 .tasks
8557 .iter()
8558 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8559
8560 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8561 let tasks = Arc::new(tasks.to_owned());
8562 Some((buffer, *row, tasks))
8563 }
8564
8565 fn find_enclosing_node_task(
8566 &mut self,
8567 cx: &mut Context<Self>,
8568 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8569 let snapshot = self.buffer.read(cx).snapshot(cx);
8570 let offset = self.selections.newest::<usize>(cx).head();
8571 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8572 let buffer_id = excerpt.buffer().remote_id();
8573
8574 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8575 let mut cursor = layer.node().walk();
8576
8577 while cursor.goto_first_child_for_byte(offset).is_some() {
8578 if cursor.node().end_byte() == offset {
8579 cursor.goto_next_sibling();
8580 }
8581 }
8582
8583 // Ascend to the smallest ancestor that contains the range and has a task.
8584 loop {
8585 let node = cursor.node();
8586 let node_range = node.byte_range();
8587 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8588
8589 // Check if this node contains our offset
8590 if node_range.start <= offset && node_range.end >= offset {
8591 // If it contains offset, check for task
8592 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8593 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8594 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8595 }
8596 }
8597
8598 if !cursor.goto_parent() {
8599 break;
8600 }
8601 }
8602 None
8603 }
8604
8605 fn render_run_indicator(
8606 &self,
8607 _style: &EditorStyle,
8608 is_active: bool,
8609 row: DisplayRow,
8610 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8611 cx: &mut Context<Self>,
8612 ) -> IconButton {
8613 let color = Color::Muted;
8614 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8615
8616 IconButton::new(
8617 ("run_indicator", row.0 as usize),
8618 ui::IconName::PlayOutlined,
8619 )
8620 .shape(ui::IconButtonShape::Square)
8621 .icon_size(IconSize::XSmall)
8622 .icon_color(color)
8623 .toggle_state(is_active)
8624 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8625 let quick_launch = match e {
8626 ClickEvent::Keyboard(_) => true,
8627 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8628 };
8629
8630 window.focus(&editor.focus_handle(cx));
8631 editor.toggle_code_actions(
8632 &ToggleCodeActions {
8633 deployed_from: Some(CodeActionSource::RunMenu(row)),
8634 quick_launch,
8635 },
8636 window,
8637 cx,
8638 );
8639 }))
8640 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8641 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8642 }))
8643 }
8644
8645 pub fn context_menu_visible(&self) -> bool {
8646 !self.edit_prediction_preview_is_active()
8647 && self
8648 .context_menu
8649 .borrow()
8650 .as_ref()
8651 .is_some_and(|menu| menu.visible())
8652 }
8653
8654 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8655 self.context_menu
8656 .borrow()
8657 .as_ref()
8658 .map(|menu| menu.origin())
8659 }
8660
8661 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8662 self.context_menu_options = Some(options);
8663 }
8664
8665 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8666 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8667
8668 fn render_edit_prediction_popover(
8669 &mut self,
8670 text_bounds: &Bounds<Pixels>,
8671 content_origin: gpui::Point<Pixels>,
8672 right_margin: Pixels,
8673 editor_snapshot: &EditorSnapshot,
8674 visible_row_range: Range<DisplayRow>,
8675 scroll_top: ScrollOffset,
8676 scroll_bottom: ScrollOffset,
8677 line_layouts: &[LineWithInvisibles],
8678 line_height: Pixels,
8679 scroll_position: gpui::Point<ScrollOffset>,
8680 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8681 newest_selection_head: Option<DisplayPoint>,
8682 editor_width: Pixels,
8683 style: &EditorStyle,
8684 window: &mut Window,
8685 cx: &mut App,
8686 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8687 if self.mode().is_minimap() {
8688 return None;
8689 }
8690 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8691
8692 if self.edit_prediction_visible_in_cursor_popover(true) {
8693 return None;
8694 }
8695
8696 match &active_edit_prediction.completion {
8697 EditPrediction::MoveWithin { target, .. } => {
8698 let target_display_point = target.to_display_point(editor_snapshot);
8699
8700 if self.edit_prediction_requires_modifier() {
8701 if !self.edit_prediction_preview_is_active() {
8702 return None;
8703 }
8704
8705 self.render_edit_prediction_modifier_jump_popover(
8706 text_bounds,
8707 content_origin,
8708 visible_row_range,
8709 line_layouts,
8710 line_height,
8711 scroll_pixel_position,
8712 newest_selection_head,
8713 target_display_point,
8714 window,
8715 cx,
8716 )
8717 } else {
8718 self.render_edit_prediction_eager_jump_popover(
8719 text_bounds,
8720 content_origin,
8721 editor_snapshot,
8722 visible_row_range,
8723 scroll_top,
8724 scroll_bottom,
8725 line_height,
8726 scroll_pixel_position,
8727 target_display_point,
8728 editor_width,
8729 window,
8730 cx,
8731 )
8732 }
8733 }
8734 EditPrediction::Edit {
8735 display_mode: EditDisplayMode::Inline,
8736 ..
8737 } => None,
8738 EditPrediction::Edit {
8739 display_mode: EditDisplayMode::TabAccept,
8740 edits,
8741 ..
8742 } => {
8743 let range = &edits.first()?.0;
8744 let target_display_point = range.end.to_display_point(editor_snapshot);
8745
8746 self.render_edit_prediction_end_of_line_popover(
8747 "Accept",
8748 editor_snapshot,
8749 visible_row_range,
8750 target_display_point,
8751 line_height,
8752 scroll_pixel_position,
8753 content_origin,
8754 editor_width,
8755 window,
8756 cx,
8757 )
8758 }
8759 EditPrediction::Edit {
8760 edits,
8761 edit_preview,
8762 display_mode: EditDisplayMode::DiffPopover,
8763 snapshot,
8764 } => self.render_edit_prediction_diff_popover(
8765 text_bounds,
8766 content_origin,
8767 right_margin,
8768 editor_snapshot,
8769 visible_row_range,
8770 line_layouts,
8771 line_height,
8772 scroll_position,
8773 scroll_pixel_position,
8774 newest_selection_head,
8775 editor_width,
8776 style,
8777 edits,
8778 edit_preview,
8779 snapshot,
8780 window,
8781 cx,
8782 ),
8783 EditPrediction::MoveOutside { snapshot, .. } => {
8784 let file_name = snapshot
8785 .file()
8786 .map(|file| file.file_name(cx))
8787 .unwrap_or("untitled");
8788 let mut element = self
8789 .render_edit_prediction_line_popover(
8790 format!("Jump to {file_name}"),
8791 Some(IconName::ZedPredict),
8792 window,
8793 cx,
8794 )
8795 .into_any();
8796
8797 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8798 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8799 let origin_y = text_bounds.size.height - size.height - px(30.);
8800 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8801 element.prepaint_at(origin, window, cx);
8802
8803 Some((element, origin))
8804 }
8805 }
8806 }
8807
8808 fn render_edit_prediction_modifier_jump_popover(
8809 &mut self,
8810 text_bounds: &Bounds<Pixels>,
8811 content_origin: gpui::Point<Pixels>,
8812 visible_row_range: Range<DisplayRow>,
8813 line_layouts: &[LineWithInvisibles],
8814 line_height: Pixels,
8815 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8816 newest_selection_head: Option<DisplayPoint>,
8817 target_display_point: DisplayPoint,
8818 window: &mut Window,
8819 cx: &mut App,
8820 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8821 let scrolled_content_origin =
8822 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8823
8824 const SCROLL_PADDING_Y: Pixels = px(12.);
8825
8826 if target_display_point.row() < visible_row_range.start {
8827 return self.render_edit_prediction_scroll_popover(
8828 |_| SCROLL_PADDING_Y,
8829 IconName::ArrowUp,
8830 visible_row_range,
8831 line_layouts,
8832 newest_selection_head,
8833 scrolled_content_origin,
8834 window,
8835 cx,
8836 );
8837 } else if target_display_point.row() >= visible_row_range.end {
8838 return self.render_edit_prediction_scroll_popover(
8839 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8840 IconName::ArrowDown,
8841 visible_row_range,
8842 line_layouts,
8843 newest_selection_head,
8844 scrolled_content_origin,
8845 window,
8846 cx,
8847 );
8848 }
8849
8850 const POLE_WIDTH: Pixels = px(2.);
8851
8852 let line_layout =
8853 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8854 let target_column = target_display_point.column() as usize;
8855
8856 let target_x = line_layout.x_for_index(target_column);
8857 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8858 - scroll_pixel_position.y;
8859
8860 let flag_on_right = target_x < text_bounds.size.width / 2.;
8861
8862 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8863 border_color.l += 0.001;
8864
8865 let mut element = v_flex()
8866 .items_end()
8867 .when(flag_on_right, |el| el.items_start())
8868 .child(if flag_on_right {
8869 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8870 .rounded_bl(px(0.))
8871 .rounded_tl(px(0.))
8872 .border_l_2()
8873 .border_color(border_color)
8874 } else {
8875 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8876 .rounded_br(px(0.))
8877 .rounded_tr(px(0.))
8878 .border_r_2()
8879 .border_color(border_color)
8880 })
8881 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8882 .into_any();
8883
8884 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8885
8886 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8887 - point(
8888 if flag_on_right {
8889 POLE_WIDTH
8890 } else {
8891 size.width - POLE_WIDTH
8892 },
8893 size.height - line_height,
8894 );
8895
8896 origin.x = origin.x.max(content_origin.x);
8897
8898 element.prepaint_at(origin, window, cx);
8899
8900 Some((element, origin))
8901 }
8902
8903 fn render_edit_prediction_scroll_popover(
8904 &mut self,
8905 to_y: impl Fn(Size<Pixels>) -> Pixels,
8906 scroll_icon: IconName,
8907 visible_row_range: Range<DisplayRow>,
8908 line_layouts: &[LineWithInvisibles],
8909 newest_selection_head: Option<DisplayPoint>,
8910 scrolled_content_origin: gpui::Point<Pixels>,
8911 window: &mut Window,
8912 cx: &mut App,
8913 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8914 let mut element = self
8915 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8916 .into_any();
8917
8918 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8919
8920 let cursor = newest_selection_head?;
8921 let cursor_row_layout =
8922 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8923 let cursor_column = cursor.column() as usize;
8924
8925 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8926
8927 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8928
8929 element.prepaint_at(origin, window, cx);
8930 Some((element, origin))
8931 }
8932
8933 fn render_edit_prediction_eager_jump_popover(
8934 &mut self,
8935 text_bounds: &Bounds<Pixels>,
8936 content_origin: gpui::Point<Pixels>,
8937 editor_snapshot: &EditorSnapshot,
8938 visible_row_range: Range<DisplayRow>,
8939 scroll_top: ScrollOffset,
8940 scroll_bottom: ScrollOffset,
8941 line_height: Pixels,
8942 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8943 target_display_point: DisplayPoint,
8944 editor_width: Pixels,
8945 window: &mut Window,
8946 cx: &mut App,
8947 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8948 if target_display_point.row().as_f64() < scroll_top {
8949 let mut element = self
8950 .render_edit_prediction_line_popover(
8951 "Jump to Edit",
8952 Some(IconName::ArrowUp),
8953 window,
8954 cx,
8955 )
8956 .into_any();
8957
8958 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8959 let offset = point(
8960 (text_bounds.size.width - size.width) / 2.,
8961 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8962 );
8963
8964 let origin = text_bounds.origin + offset;
8965 element.prepaint_at(origin, window, cx);
8966 Some((element, origin))
8967 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8968 let mut element = self
8969 .render_edit_prediction_line_popover(
8970 "Jump to Edit",
8971 Some(IconName::ArrowDown),
8972 window,
8973 cx,
8974 )
8975 .into_any();
8976
8977 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8978 let offset = point(
8979 (text_bounds.size.width - size.width) / 2.,
8980 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8981 );
8982
8983 let origin = text_bounds.origin + offset;
8984 element.prepaint_at(origin, window, cx);
8985 Some((element, origin))
8986 } else {
8987 self.render_edit_prediction_end_of_line_popover(
8988 "Jump to Edit",
8989 editor_snapshot,
8990 visible_row_range,
8991 target_display_point,
8992 line_height,
8993 scroll_pixel_position,
8994 content_origin,
8995 editor_width,
8996 window,
8997 cx,
8998 )
8999 }
9000 }
9001
9002 fn render_edit_prediction_end_of_line_popover(
9003 self: &mut Editor,
9004 label: &'static str,
9005 editor_snapshot: &EditorSnapshot,
9006 visible_row_range: Range<DisplayRow>,
9007 target_display_point: DisplayPoint,
9008 line_height: Pixels,
9009 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9010 content_origin: gpui::Point<Pixels>,
9011 editor_width: Pixels,
9012 window: &mut Window,
9013 cx: &mut App,
9014 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9015 let target_line_end = DisplayPoint::new(
9016 target_display_point.row(),
9017 editor_snapshot.line_len(target_display_point.row()),
9018 );
9019
9020 let mut element = self
9021 .render_edit_prediction_line_popover(label, None, window, cx)
9022 .into_any();
9023
9024 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9025
9026 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9027
9028 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9029 let mut origin = start_point
9030 + line_origin
9031 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9032 origin.x = origin.x.max(content_origin.x);
9033
9034 let max_x = content_origin.x + editor_width - size.width;
9035
9036 if origin.x > max_x {
9037 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9038
9039 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9040 origin.y += offset;
9041 IconName::ArrowUp
9042 } else {
9043 origin.y -= offset;
9044 IconName::ArrowDown
9045 };
9046
9047 element = self
9048 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9049 .into_any();
9050
9051 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9052
9053 origin.x = content_origin.x + editor_width - size.width - px(2.);
9054 }
9055
9056 element.prepaint_at(origin, window, cx);
9057 Some((element, origin))
9058 }
9059
9060 fn render_edit_prediction_diff_popover(
9061 self: &Editor,
9062 text_bounds: &Bounds<Pixels>,
9063 content_origin: gpui::Point<Pixels>,
9064 right_margin: Pixels,
9065 editor_snapshot: &EditorSnapshot,
9066 visible_row_range: Range<DisplayRow>,
9067 line_layouts: &[LineWithInvisibles],
9068 line_height: Pixels,
9069 scroll_position: gpui::Point<ScrollOffset>,
9070 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9071 newest_selection_head: Option<DisplayPoint>,
9072 editor_width: Pixels,
9073 style: &EditorStyle,
9074 edits: &Vec<(Range<Anchor>, String)>,
9075 edit_preview: &Option<language::EditPreview>,
9076 snapshot: &language::BufferSnapshot,
9077 window: &mut Window,
9078 cx: &mut App,
9079 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9080 let edit_start = edits
9081 .first()
9082 .unwrap()
9083 .0
9084 .start
9085 .to_display_point(editor_snapshot);
9086 let edit_end = edits
9087 .last()
9088 .unwrap()
9089 .0
9090 .end
9091 .to_display_point(editor_snapshot);
9092
9093 let is_visible = visible_row_range.contains(&edit_start.row())
9094 || visible_row_range.contains(&edit_end.row());
9095 if !is_visible {
9096 return None;
9097 }
9098
9099 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9100 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9101 } else {
9102 // Fallback for providers without edit_preview
9103 crate::edit_prediction_fallback_text(edits, cx)
9104 };
9105
9106 let styled_text = highlighted_edits.to_styled_text(&style.text);
9107 let line_count = highlighted_edits.text.lines().count();
9108
9109 const BORDER_WIDTH: Pixels = px(1.);
9110
9111 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9112 let has_keybind = keybind.is_some();
9113
9114 let mut element = h_flex()
9115 .items_start()
9116 .child(
9117 h_flex()
9118 .bg(cx.theme().colors().editor_background)
9119 .border(BORDER_WIDTH)
9120 .shadow_xs()
9121 .border_color(cx.theme().colors().border)
9122 .rounded_l_lg()
9123 .when(line_count > 1, |el| el.rounded_br_lg())
9124 .pr_1()
9125 .child(styled_text),
9126 )
9127 .child(
9128 h_flex()
9129 .h(line_height + BORDER_WIDTH * 2.)
9130 .px_1p5()
9131 .gap_1()
9132 // Workaround: For some reason, there's a gap if we don't do this
9133 .ml(-BORDER_WIDTH)
9134 .shadow(vec![gpui::BoxShadow {
9135 color: gpui::black().opacity(0.05),
9136 offset: point(px(1.), px(1.)),
9137 blur_radius: px(2.),
9138 spread_radius: px(0.),
9139 }])
9140 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9141 .border(BORDER_WIDTH)
9142 .border_color(cx.theme().colors().border)
9143 .rounded_r_lg()
9144 .id("edit_prediction_diff_popover_keybind")
9145 .when(!has_keybind, |el| {
9146 let status_colors = cx.theme().status();
9147
9148 el.bg(status_colors.error_background)
9149 .border_color(status_colors.error.opacity(0.6))
9150 .child(Icon::new(IconName::Info).color(Color::Error))
9151 .cursor_default()
9152 .hoverable_tooltip(move |_window, cx| {
9153 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9154 })
9155 })
9156 .children(keybind),
9157 )
9158 .into_any();
9159
9160 let longest_row =
9161 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9162 let longest_line_width = if visible_row_range.contains(&longest_row) {
9163 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9164 } else {
9165 layout_line(
9166 longest_row,
9167 editor_snapshot,
9168 style,
9169 editor_width,
9170 |_| false,
9171 window,
9172 cx,
9173 )
9174 .width
9175 };
9176
9177 let viewport_bounds =
9178 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9179 right: -right_margin,
9180 ..Default::default()
9181 });
9182
9183 let x_after_longest = Pixels::from(
9184 ScrollPixelOffset::from(
9185 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9186 ) - scroll_pixel_position.x,
9187 );
9188
9189 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9190
9191 // Fully visible if it can be displayed within the window (allow overlapping other
9192 // panes). However, this is only allowed if the popover starts within text_bounds.
9193 let can_position_to_the_right = x_after_longest < text_bounds.right()
9194 && x_after_longest + element_bounds.width < viewport_bounds.right();
9195
9196 let mut origin = if can_position_to_the_right {
9197 point(
9198 x_after_longest,
9199 text_bounds.origin.y
9200 + Pixels::from(
9201 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9202 - scroll_pixel_position.y,
9203 ),
9204 )
9205 } else {
9206 let cursor_row = newest_selection_head.map(|head| head.row());
9207 let above_edit = edit_start
9208 .row()
9209 .0
9210 .checked_sub(line_count as u32)
9211 .map(DisplayRow);
9212 let below_edit = Some(edit_end.row() + 1);
9213 let above_cursor =
9214 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9215 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9216
9217 // Place the edit popover adjacent to the edit if there is a location
9218 // available that is onscreen and does not obscure the cursor. Otherwise,
9219 // place it adjacent to the cursor.
9220 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9221 .into_iter()
9222 .flatten()
9223 .find(|&start_row| {
9224 let end_row = start_row + line_count as u32;
9225 visible_row_range.contains(&start_row)
9226 && visible_row_range.contains(&end_row)
9227 && cursor_row
9228 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9229 })?;
9230
9231 content_origin
9232 + point(
9233 Pixels::from(-scroll_pixel_position.x),
9234 Pixels::from(
9235 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9236 ),
9237 )
9238 };
9239
9240 origin.x -= BORDER_WIDTH;
9241
9242 window.defer_draw(element, origin, 1);
9243
9244 // Do not return an element, since it will already be drawn due to defer_draw.
9245 None
9246 }
9247
9248 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9249 px(30.)
9250 }
9251
9252 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9253 if self.read_only(cx) {
9254 cx.theme().players().read_only()
9255 } else {
9256 self.style.as_ref().unwrap().local_player
9257 }
9258 }
9259
9260 fn render_edit_prediction_accept_keybind(
9261 &self,
9262 window: &mut Window,
9263 cx: &App,
9264 ) -> Option<AnyElement> {
9265 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9266 let accept_keystroke = accept_binding.keystroke()?;
9267
9268 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9269
9270 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9271 Color::Accent
9272 } else {
9273 Color::Muted
9274 };
9275
9276 h_flex()
9277 .px_0p5()
9278 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9279 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9280 .text_size(TextSize::XSmall.rems(cx))
9281 .child(h_flex().children(ui::render_modifiers(
9282 accept_keystroke.modifiers(),
9283 PlatformStyle::platform(),
9284 Some(modifiers_color),
9285 Some(IconSize::XSmall.rems().into()),
9286 true,
9287 )))
9288 .when(is_platform_style_mac, |parent| {
9289 parent.child(accept_keystroke.key().to_string())
9290 })
9291 .when(!is_platform_style_mac, |parent| {
9292 parent.child(
9293 Key::new(
9294 util::capitalize(accept_keystroke.key()),
9295 Some(Color::Default),
9296 )
9297 .size(Some(IconSize::XSmall.rems().into())),
9298 )
9299 })
9300 .into_any()
9301 .into()
9302 }
9303
9304 fn render_edit_prediction_line_popover(
9305 &self,
9306 label: impl Into<SharedString>,
9307 icon: Option<IconName>,
9308 window: &mut Window,
9309 cx: &App,
9310 ) -> Stateful<Div> {
9311 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9312
9313 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9314 let has_keybind = keybind.is_some();
9315
9316 h_flex()
9317 .id("ep-line-popover")
9318 .py_0p5()
9319 .pl_1()
9320 .pr(padding_right)
9321 .gap_1()
9322 .rounded_md()
9323 .border_1()
9324 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9325 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9326 .shadow_xs()
9327 .when(!has_keybind, |el| {
9328 let status_colors = cx.theme().status();
9329
9330 el.bg(status_colors.error_background)
9331 .border_color(status_colors.error.opacity(0.6))
9332 .pl_2()
9333 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9334 .cursor_default()
9335 .hoverable_tooltip(move |_window, cx| {
9336 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9337 })
9338 })
9339 .children(keybind)
9340 .child(
9341 Label::new(label)
9342 .size(LabelSize::Small)
9343 .when(!has_keybind, |el| {
9344 el.color(cx.theme().status().error.into()).strikethrough()
9345 }),
9346 )
9347 .when(!has_keybind, |el| {
9348 el.child(
9349 h_flex().ml_1().child(
9350 Icon::new(IconName::Info)
9351 .size(IconSize::Small)
9352 .color(cx.theme().status().error.into()),
9353 ),
9354 )
9355 })
9356 .when_some(icon, |element, icon| {
9357 element.child(
9358 div()
9359 .mt(px(1.5))
9360 .child(Icon::new(icon).size(IconSize::Small)),
9361 )
9362 })
9363 }
9364
9365 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9366 let accent_color = cx.theme().colors().text_accent;
9367 let editor_bg_color = cx.theme().colors().editor_background;
9368 editor_bg_color.blend(accent_color.opacity(0.1))
9369 }
9370
9371 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9372 let accent_color = cx.theme().colors().text_accent;
9373 let editor_bg_color = cx.theme().colors().editor_background;
9374 editor_bg_color.blend(accent_color.opacity(0.6))
9375 }
9376 fn get_prediction_provider_icon_name(
9377 provider: &Option<RegisteredEditPredictionProvider>,
9378 ) -> IconName {
9379 match provider {
9380 Some(provider) => match provider.provider.name() {
9381 "copilot" => IconName::Copilot,
9382 "supermaven" => IconName::Supermaven,
9383 _ => IconName::ZedPredict,
9384 },
9385 None => IconName::ZedPredict,
9386 }
9387 }
9388
9389 fn render_edit_prediction_cursor_popover(
9390 &self,
9391 min_width: Pixels,
9392 max_width: Pixels,
9393 cursor_point: Point,
9394 style: &EditorStyle,
9395 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9396 _window: &Window,
9397 cx: &mut Context<Editor>,
9398 ) -> Option<AnyElement> {
9399 let provider = self.edit_prediction_provider.as_ref()?;
9400 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9401
9402 let is_refreshing = provider.provider.is_refreshing(cx);
9403
9404 fn pending_completion_container(icon: IconName) -> Div {
9405 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9406 }
9407
9408 let completion = match &self.active_edit_prediction {
9409 Some(prediction) => {
9410 if !self.has_visible_completions_menu() {
9411 const RADIUS: Pixels = px(6.);
9412 const BORDER_WIDTH: Pixels = px(1.);
9413
9414 return Some(
9415 h_flex()
9416 .elevation_2(cx)
9417 .border(BORDER_WIDTH)
9418 .border_color(cx.theme().colors().border)
9419 .when(accept_keystroke.is_none(), |el| {
9420 el.border_color(cx.theme().status().error)
9421 })
9422 .rounded(RADIUS)
9423 .rounded_tl(px(0.))
9424 .overflow_hidden()
9425 .child(div().px_1p5().child(match &prediction.completion {
9426 EditPrediction::MoveWithin { target, snapshot } => {
9427 use text::ToPoint as _;
9428 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9429 {
9430 Icon::new(IconName::ZedPredictDown)
9431 } else {
9432 Icon::new(IconName::ZedPredictUp)
9433 }
9434 }
9435 EditPrediction::MoveOutside { .. } => {
9436 // TODO [zeta2] custom icon for external jump?
9437 Icon::new(provider_icon)
9438 }
9439 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9440 }))
9441 .child(
9442 h_flex()
9443 .gap_1()
9444 .py_1()
9445 .px_2()
9446 .rounded_r(RADIUS - BORDER_WIDTH)
9447 .border_l_1()
9448 .border_color(cx.theme().colors().border)
9449 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9450 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9451 el.child(
9452 Label::new("Hold")
9453 .size(LabelSize::Small)
9454 .when(accept_keystroke.is_none(), |el| {
9455 el.strikethrough()
9456 })
9457 .line_height_style(LineHeightStyle::UiLabel),
9458 )
9459 })
9460 .id("edit_prediction_cursor_popover_keybind")
9461 .when(accept_keystroke.is_none(), |el| {
9462 let status_colors = cx.theme().status();
9463
9464 el.bg(status_colors.error_background)
9465 .border_color(status_colors.error.opacity(0.6))
9466 .child(Icon::new(IconName::Info).color(Color::Error))
9467 .cursor_default()
9468 .hoverable_tooltip(move |_window, cx| {
9469 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9470 .into()
9471 })
9472 })
9473 .when_some(
9474 accept_keystroke.as_ref(),
9475 |el, accept_keystroke| {
9476 el.child(h_flex().children(ui::render_modifiers(
9477 accept_keystroke.modifiers(),
9478 PlatformStyle::platform(),
9479 Some(Color::Default),
9480 Some(IconSize::XSmall.rems().into()),
9481 false,
9482 )))
9483 },
9484 ),
9485 )
9486 .into_any(),
9487 );
9488 }
9489
9490 self.render_edit_prediction_cursor_popover_preview(
9491 prediction,
9492 cursor_point,
9493 style,
9494 cx,
9495 )?
9496 }
9497
9498 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9499 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9500 stale_completion,
9501 cursor_point,
9502 style,
9503 cx,
9504 )?,
9505
9506 None => pending_completion_container(provider_icon)
9507 .child(Label::new("...").size(LabelSize::Small)),
9508 },
9509
9510 None => pending_completion_container(provider_icon)
9511 .child(Label::new("...").size(LabelSize::Small)),
9512 };
9513
9514 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9515 completion
9516 .with_animation(
9517 "loading-completion",
9518 Animation::new(Duration::from_secs(2))
9519 .repeat()
9520 .with_easing(pulsating_between(0.4, 0.8)),
9521 |label, delta| label.opacity(delta),
9522 )
9523 .into_any_element()
9524 } else {
9525 completion.into_any_element()
9526 };
9527
9528 let has_completion = self.active_edit_prediction.is_some();
9529
9530 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9531 Some(
9532 h_flex()
9533 .min_w(min_width)
9534 .max_w(max_width)
9535 .flex_1()
9536 .elevation_2(cx)
9537 .border_color(cx.theme().colors().border)
9538 .child(
9539 div()
9540 .flex_1()
9541 .py_1()
9542 .px_2()
9543 .overflow_hidden()
9544 .child(completion),
9545 )
9546 .when_some(accept_keystroke, |el, accept_keystroke| {
9547 if !accept_keystroke.modifiers().modified() {
9548 return el;
9549 }
9550
9551 el.child(
9552 h_flex()
9553 .h_full()
9554 .border_l_1()
9555 .rounded_r_lg()
9556 .border_color(cx.theme().colors().border)
9557 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9558 .gap_1()
9559 .py_1()
9560 .px_2()
9561 .child(
9562 h_flex()
9563 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9564 .when(is_platform_style_mac, |parent| parent.gap_1())
9565 .child(h_flex().children(ui::render_modifiers(
9566 accept_keystroke.modifiers(),
9567 PlatformStyle::platform(),
9568 Some(if !has_completion {
9569 Color::Muted
9570 } else {
9571 Color::Default
9572 }),
9573 None,
9574 false,
9575 ))),
9576 )
9577 .child(Label::new("Preview").into_any_element())
9578 .opacity(if has_completion { 1.0 } else { 0.4 }),
9579 )
9580 })
9581 .into_any(),
9582 )
9583 }
9584
9585 fn render_edit_prediction_cursor_popover_preview(
9586 &self,
9587 completion: &EditPredictionState,
9588 cursor_point: Point,
9589 style: &EditorStyle,
9590 cx: &mut Context<Editor>,
9591 ) -> Option<Div> {
9592 use text::ToPoint as _;
9593
9594 fn render_relative_row_jump(
9595 prefix: impl Into<String>,
9596 current_row: u32,
9597 target_row: u32,
9598 ) -> Div {
9599 let (row_diff, arrow) = if target_row < current_row {
9600 (current_row - target_row, IconName::ArrowUp)
9601 } else {
9602 (target_row - current_row, IconName::ArrowDown)
9603 };
9604
9605 h_flex()
9606 .child(
9607 Label::new(format!("{}{}", prefix.into(), row_diff))
9608 .color(Color::Muted)
9609 .size(LabelSize::Small),
9610 )
9611 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9612 }
9613
9614 let supports_jump = self
9615 .edit_prediction_provider
9616 .as_ref()
9617 .map(|provider| provider.provider.supports_jump_to_edit())
9618 .unwrap_or(true);
9619
9620 match &completion.completion {
9621 EditPrediction::MoveWithin {
9622 target, snapshot, ..
9623 } => {
9624 if !supports_jump {
9625 return None;
9626 }
9627
9628 Some(
9629 h_flex()
9630 .px_2()
9631 .gap_2()
9632 .flex_1()
9633 .child(
9634 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9635 Icon::new(IconName::ZedPredictDown)
9636 } else {
9637 Icon::new(IconName::ZedPredictUp)
9638 },
9639 )
9640 .child(Label::new("Jump to Edit")),
9641 )
9642 }
9643 EditPrediction::MoveOutside { snapshot, .. } => {
9644 let file_name = snapshot
9645 .file()
9646 .map(|file| file.file_name(cx))
9647 .unwrap_or("untitled");
9648 Some(
9649 h_flex()
9650 .px_2()
9651 .gap_2()
9652 .flex_1()
9653 .child(Icon::new(IconName::ZedPredict))
9654 .child(Label::new(format!("Jump to {file_name}"))),
9655 )
9656 }
9657 EditPrediction::Edit {
9658 edits,
9659 edit_preview,
9660 snapshot,
9661 display_mode: _,
9662 } => {
9663 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9664
9665 let (highlighted_edits, has_more_lines) =
9666 if let Some(edit_preview) = edit_preview.as_ref() {
9667 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9668 .first_line_preview()
9669 } else {
9670 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9671 };
9672
9673 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9674 .with_default_highlights(&style.text, highlighted_edits.highlights);
9675
9676 let preview = h_flex()
9677 .gap_1()
9678 .min_w_16()
9679 .child(styled_text)
9680 .when(has_more_lines, |parent| parent.child("…"));
9681
9682 let left = if supports_jump && first_edit_row != cursor_point.row {
9683 render_relative_row_jump("", cursor_point.row, first_edit_row)
9684 .into_any_element()
9685 } else {
9686 let icon_name =
9687 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9688 Icon::new(icon_name).into_any_element()
9689 };
9690
9691 Some(
9692 h_flex()
9693 .h_full()
9694 .flex_1()
9695 .gap_2()
9696 .pr_1()
9697 .overflow_x_hidden()
9698 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9699 .child(left)
9700 .child(preview),
9701 )
9702 }
9703 }
9704 }
9705
9706 pub fn render_context_menu(
9707 &self,
9708 style: &EditorStyle,
9709 max_height_in_lines: u32,
9710 window: &mut Window,
9711 cx: &mut Context<Editor>,
9712 ) -> Option<AnyElement> {
9713 let menu = self.context_menu.borrow();
9714 let menu = menu.as_ref()?;
9715 if !menu.visible() {
9716 return None;
9717 };
9718 Some(menu.render(style, max_height_in_lines, window, cx))
9719 }
9720
9721 fn render_context_menu_aside(
9722 &mut self,
9723 max_size: Size<Pixels>,
9724 window: &mut Window,
9725 cx: &mut Context<Editor>,
9726 ) -> Option<AnyElement> {
9727 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9728 if menu.visible() {
9729 menu.render_aside(max_size, window, cx)
9730 } else {
9731 None
9732 }
9733 })
9734 }
9735
9736 fn hide_context_menu(
9737 &mut self,
9738 window: &mut Window,
9739 cx: &mut Context<Self>,
9740 ) -> Option<CodeContextMenu> {
9741 cx.notify();
9742 self.completion_tasks.clear();
9743 let context_menu = self.context_menu.borrow_mut().take();
9744 self.stale_edit_prediction_in_menu.take();
9745 self.update_visible_edit_prediction(window, cx);
9746 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9747 && let Some(completion_provider) = &self.completion_provider
9748 {
9749 completion_provider.selection_changed(None, window, cx);
9750 }
9751 context_menu
9752 }
9753
9754 fn show_snippet_choices(
9755 &mut self,
9756 choices: &Vec<String>,
9757 selection: Range<Anchor>,
9758 cx: &mut Context<Self>,
9759 ) {
9760 let Some((_, buffer, _)) = self
9761 .buffer()
9762 .read(cx)
9763 .excerpt_containing(selection.start, cx)
9764 else {
9765 return;
9766 };
9767 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9768 else {
9769 return;
9770 };
9771 if buffer != end_buffer {
9772 log::error!("expected anchor range to have matching buffer IDs");
9773 return;
9774 }
9775
9776 let id = post_inc(&mut self.next_completion_id);
9777 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9778 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9779 CompletionsMenu::new_snippet_choices(
9780 id,
9781 true,
9782 choices,
9783 selection,
9784 buffer,
9785 snippet_sort_order,
9786 ),
9787 ));
9788 }
9789
9790 pub fn insert_snippet(
9791 &mut self,
9792 insertion_ranges: &[Range<usize>],
9793 snippet: Snippet,
9794 window: &mut Window,
9795 cx: &mut Context<Self>,
9796 ) -> Result<()> {
9797 struct Tabstop<T> {
9798 is_end_tabstop: bool,
9799 ranges: Vec<Range<T>>,
9800 choices: Option<Vec<String>>,
9801 }
9802
9803 let tabstops = self.buffer.update(cx, |buffer, cx| {
9804 let snippet_text: Arc<str> = snippet.text.clone().into();
9805 let edits = insertion_ranges
9806 .iter()
9807 .cloned()
9808 .map(|range| (range, snippet_text.clone()));
9809 let autoindent_mode = AutoindentMode::Block {
9810 original_indent_columns: Vec::new(),
9811 };
9812 buffer.edit(edits, Some(autoindent_mode), cx);
9813
9814 let snapshot = &*buffer.read(cx);
9815 let snippet = &snippet;
9816 snippet
9817 .tabstops
9818 .iter()
9819 .map(|tabstop| {
9820 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9821 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9822 });
9823 let mut tabstop_ranges = tabstop
9824 .ranges
9825 .iter()
9826 .flat_map(|tabstop_range| {
9827 let mut delta = 0_isize;
9828 insertion_ranges.iter().map(move |insertion_range| {
9829 let insertion_start = insertion_range.start as isize + delta;
9830 delta +=
9831 snippet.text.len() as isize - insertion_range.len() as isize;
9832
9833 let start = ((insertion_start + tabstop_range.start) as usize)
9834 .min(snapshot.len());
9835 let end = ((insertion_start + tabstop_range.end) as usize)
9836 .min(snapshot.len());
9837 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9838 })
9839 })
9840 .collect::<Vec<_>>();
9841 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9842
9843 Tabstop {
9844 is_end_tabstop,
9845 ranges: tabstop_ranges,
9846 choices: tabstop.choices.clone(),
9847 }
9848 })
9849 .collect::<Vec<_>>()
9850 });
9851 if let Some(tabstop) = tabstops.first() {
9852 self.change_selections(Default::default(), window, cx, |s| {
9853 // Reverse order so that the first range is the newest created selection.
9854 // Completions will use it and autoscroll will prioritize it.
9855 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9856 });
9857
9858 if let Some(choices) = &tabstop.choices
9859 && let Some(selection) = tabstop.ranges.first()
9860 {
9861 self.show_snippet_choices(choices, selection.clone(), cx)
9862 }
9863
9864 // If we're already at the last tabstop and it's at the end of the snippet,
9865 // we're done, we don't need to keep the state around.
9866 if !tabstop.is_end_tabstop {
9867 let choices = tabstops
9868 .iter()
9869 .map(|tabstop| tabstop.choices.clone())
9870 .collect();
9871
9872 let ranges = tabstops
9873 .into_iter()
9874 .map(|tabstop| tabstop.ranges)
9875 .collect::<Vec<_>>();
9876
9877 self.snippet_stack.push(SnippetState {
9878 active_index: 0,
9879 ranges,
9880 choices,
9881 });
9882 }
9883
9884 // Check whether the just-entered snippet ends with an auto-closable bracket.
9885 if self.autoclose_regions.is_empty() {
9886 let snapshot = self.buffer.read(cx).snapshot(cx);
9887 let mut all_selections = self.selections.all::<Point>(cx);
9888 for selection in &mut all_selections {
9889 let selection_head = selection.head();
9890 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9891 continue;
9892 };
9893
9894 let mut bracket_pair = None;
9895 let max_lookup_length = scope
9896 .brackets()
9897 .map(|(pair, _)| {
9898 pair.start
9899 .as_str()
9900 .chars()
9901 .count()
9902 .max(pair.end.as_str().chars().count())
9903 })
9904 .max();
9905 if let Some(max_lookup_length) = max_lookup_length {
9906 let next_text = snapshot
9907 .chars_at(selection_head)
9908 .take(max_lookup_length)
9909 .collect::<String>();
9910 let prev_text = snapshot
9911 .reversed_chars_at(selection_head)
9912 .take(max_lookup_length)
9913 .collect::<String>();
9914
9915 for (pair, enabled) in scope.brackets() {
9916 if enabled
9917 && pair.close
9918 && prev_text.starts_with(pair.start.as_str())
9919 && next_text.starts_with(pair.end.as_str())
9920 {
9921 bracket_pair = Some(pair.clone());
9922 break;
9923 }
9924 }
9925 }
9926
9927 if let Some(pair) = bracket_pair {
9928 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9929 let autoclose_enabled =
9930 self.use_autoclose && snapshot_settings.use_autoclose;
9931 if autoclose_enabled {
9932 let start = snapshot.anchor_after(selection_head);
9933 let end = snapshot.anchor_after(selection_head);
9934 self.autoclose_regions.push(AutocloseRegion {
9935 selection_id: selection.id,
9936 range: start..end,
9937 pair,
9938 });
9939 }
9940 }
9941 }
9942 }
9943 }
9944 Ok(())
9945 }
9946
9947 pub fn move_to_next_snippet_tabstop(
9948 &mut self,
9949 window: &mut Window,
9950 cx: &mut Context<Self>,
9951 ) -> bool {
9952 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9953 }
9954
9955 pub fn move_to_prev_snippet_tabstop(
9956 &mut self,
9957 window: &mut Window,
9958 cx: &mut Context<Self>,
9959 ) -> bool {
9960 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9961 }
9962
9963 pub fn move_to_snippet_tabstop(
9964 &mut self,
9965 bias: Bias,
9966 window: &mut Window,
9967 cx: &mut Context<Self>,
9968 ) -> bool {
9969 if let Some(mut snippet) = self.snippet_stack.pop() {
9970 match bias {
9971 Bias::Left => {
9972 if snippet.active_index > 0 {
9973 snippet.active_index -= 1;
9974 } else {
9975 self.snippet_stack.push(snippet);
9976 return false;
9977 }
9978 }
9979 Bias::Right => {
9980 if snippet.active_index + 1 < snippet.ranges.len() {
9981 snippet.active_index += 1;
9982 } else {
9983 self.snippet_stack.push(snippet);
9984 return false;
9985 }
9986 }
9987 }
9988 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9989 self.change_selections(Default::default(), window, cx, |s| {
9990 // Reverse order so that the first range is the newest created selection.
9991 // Completions will use it and autoscroll will prioritize it.
9992 s.select_ranges(current_ranges.iter().rev().cloned())
9993 });
9994
9995 if let Some(choices) = &snippet.choices[snippet.active_index]
9996 && let Some(selection) = current_ranges.first()
9997 {
9998 self.show_snippet_choices(choices, selection.clone(), cx);
9999 }
10000
10001 // If snippet state is not at the last tabstop, push it back on the stack
10002 if snippet.active_index + 1 < snippet.ranges.len() {
10003 self.snippet_stack.push(snippet);
10004 }
10005 return true;
10006 }
10007 }
10008
10009 false
10010 }
10011
10012 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10013 self.transact(window, cx, |this, window, cx| {
10014 this.select_all(&SelectAll, window, cx);
10015 this.insert("", window, cx);
10016 });
10017 }
10018
10019 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10020 if self.read_only(cx) {
10021 return;
10022 }
10023 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10024 self.transact(window, cx, |this, window, cx| {
10025 this.select_autoclose_pair(window, cx);
10026 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10027 if !this.linked_edit_ranges.is_empty() {
10028 let selections = this.selections.all::<MultiBufferPoint>(cx);
10029 let snapshot = this.buffer.read(cx).snapshot(cx);
10030
10031 for selection in selections.iter() {
10032 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10033 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10034 if selection_start.buffer_id != selection_end.buffer_id {
10035 continue;
10036 }
10037 if let Some(ranges) =
10038 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10039 {
10040 for (buffer, entries) in ranges {
10041 linked_ranges.entry(buffer).or_default().extend(entries);
10042 }
10043 }
10044 }
10045 }
10046
10047 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10048 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10049 for selection in &mut selections {
10050 if selection.is_empty() {
10051 let old_head = selection.head();
10052 let mut new_head =
10053 movement::left(&display_map, old_head.to_display_point(&display_map))
10054 .to_point(&display_map);
10055 if let Some((buffer, line_buffer_range)) = display_map
10056 .buffer_snapshot()
10057 .buffer_line_for_row(MultiBufferRow(old_head.row))
10058 {
10059 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10060 let indent_len = match indent_size.kind {
10061 IndentKind::Space => {
10062 buffer.settings_at(line_buffer_range.start, cx).tab_size
10063 }
10064 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10065 };
10066 if old_head.column <= indent_size.len && old_head.column > 0 {
10067 let indent_len = indent_len.get();
10068 new_head = cmp::min(
10069 new_head,
10070 MultiBufferPoint::new(
10071 old_head.row,
10072 ((old_head.column - 1) / indent_len) * indent_len,
10073 ),
10074 );
10075 }
10076 }
10077
10078 selection.set_head(new_head, SelectionGoal::None);
10079 }
10080 }
10081
10082 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10083 this.insert("", window, cx);
10084 let empty_str: Arc<str> = Arc::from("");
10085 for (buffer, edits) in linked_ranges {
10086 let snapshot = buffer.read(cx).snapshot();
10087 use text::ToPoint as TP;
10088
10089 let edits = edits
10090 .into_iter()
10091 .map(|range| {
10092 let end_point = TP::to_point(&range.end, &snapshot);
10093 let mut start_point = TP::to_point(&range.start, &snapshot);
10094
10095 if end_point == start_point {
10096 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10097 .saturating_sub(1);
10098 start_point =
10099 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10100 };
10101
10102 (start_point..end_point, empty_str.clone())
10103 })
10104 .sorted_by_key(|(range, _)| range.start)
10105 .collect::<Vec<_>>();
10106 buffer.update(cx, |this, cx| {
10107 this.edit(edits, None, cx);
10108 })
10109 }
10110 this.refresh_edit_prediction(true, false, window, cx);
10111 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10112 });
10113 }
10114
10115 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10116 if self.read_only(cx) {
10117 return;
10118 }
10119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10120 self.transact(window, cx, |this, window, cx| {
10121 this.change_selections(Default::default(), window, cx, |s| {
10122 s.move_with(|map, selection| {
10123 if selection.is_empty() {
10124 let cursor = movement::right(map, selection.head());
10125 selection.end = cursor;
10126 selection.reversed = true;
10127 selection.goal = SelectionGoal::None;
10128 }
10129 })
10130 });
10131 this.insert("", window, cx);
10132 this.refresh_edit_prediction(true, false, window, cx);
10133 });
10134 }
10135
10136 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10137 if self.mode.is_single_line() {
10138 cx.propagate();
10139 return;
10140 }
10141
10142 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10143 if self.move_to_prev_snippet_tabstop(window, cx) {
10144 return;
10145 }
10146 self.outdent(&Outdent, window, cx);
10147 }
10148
10149 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10150 if self.mode.is_single_line() {
10151 cx.propagate();
10152 return;
10153 }
10154
10155 if self.move_to_next_snippet_tabstop(window, cx) {
10156 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10157 return;
10158 }
10159 if self.read_only(cx) {
10160 return;
10161 }
10162 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10163 let mut selections = self.selections.all_adjusted(cx);
10164 let buffer = self.buffer.read(cx);
10165 let snapshot = buffer.snapshot(cx);
10166 let rows_iter = selections.iter().map(|s| s.head().row);
10167 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10168
10169 let has_some_cursor_in_whitespace = selections
10170 .iter()
10171 .filter(|selection| selection.is_empty())
10172 .any(|selection| {
10173 let cursor = selection.head();
10174 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10175 cursor.column < current_indent.len
10176 });
10177
10178 let mut edits = Vec::new();
10179 let mut prev_edited_row = 0;
10180 let mut row_delta = 0;
10181 for selection in &mut selections {
10182 if selection.start.row != prev_edited_row {
10183 row_delta = 0;
10184 }
10185 prev_edited_row = selection.end.row;
10186
10187 // If the selection is non-empty, then increase the indentation of the selected lines.
10188 if !selection.is_empty() {
10189 row_delta =
10190 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10191 continue;
10192 }
10193
10194 let cursor = selection.head();
10195 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10196 if let Some(suggested_indent) =
10197 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10198 {
10199 // Don't do anything if already at suggested indent
10200 // and there is any other cursor which is not
10201 if has_some_cursor_in_whitespace
10202 && cursor.column == current_indent.len
10203 && current_indent.len == suggested_indent.len
10204 {
10205 continue;
10206 }
10207
10208 // Adjust line and move cursor to suggested indent
10209 // if cursor is not at suggested indent
10210 if cursor.column < suggested_indent.len
10211 && cursor.column <= current_indent.len
10212 && current_indent.len <= suggested_indent.len
10213 {
10214 selection.start = Point::new(cursor.row, suggested_indent.len);
10215 selection.end = selection.start;
10216 if row_delta == 0 {
10217 edits.extend(Buffer::edit_for_indent_size_adjustment(
10218 cursor.row,
10219 current_indent,
10220 suggested_indent,
10221 ));
10222 row_delta = suggested_indent.len - current_indent.len;
10223 }
10224 continue;
10225 }
10226
10227 // If current indent is more than suggested indent
10228 // only move cursor to current indent and skip indent
10229 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10230 selection.start = Point::new(cursor.row, current_indent.len);
10231 selection.end = selection.start;
10232 continue;
10233 }
10234 }
10235
10236 // Otherwise, insert a hard or soft tab.
10237 let settings = buffer.language_settings_at(cursor, cx);
10238 let tab_size = if settings.hard_tabs {
10239 IndentSize::tab()
10240 } else {
10241 let tab_size = settings.tab_size.get();
10242 let indent_remainder = snapshot
10243 .text_for_range(Point::new(cursor.row, 0)..cursor)
10244 .flat_map(str::chars)
10245 .fold(row_delta % tab_size, |counter: u32, c| {
10246 if c == '\t' {
10247 0
10248 } else {
10249 (counter + 1) % tab_size
10250 }
10251 });
10252
10253 let chars_to_next_tab_stop = tab_size - indent_remainder;
10254 IndentSize::spaces(chars_to_next_tab_stop)
10255 };
10256 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10257 selection.end = selection.start;
10258 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10259 row_delta += tab_size.len;
10260 }
10261
10262 self.transact(window, cx, |this, window, cx| {
10263 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10264 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10265 this.refresh_edit_prediction(true, false, window, cx);
10266 });
10267 }
10268
10269 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10270 if self.read_only(cx) {
10271 return;
10272 }
10273 if self.mode.is_single_line() {
10274 cx.propagate();
10275 return;
10276 }
10277
10278 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10279 let mut selections = self.selections.all::<Point>(cx);
10280 let mut prev_edited_row = 0;
10281 let mut row_delta = 0;
10282 let mut edits = Vec::new();
10283 let buffer = self.buffer.read(cx);
10284 let snapshot = buffer.snapshot(cx);
10285 for selection in &mut selections {
10286 if selection.start.row != prev_edited_row {
10287 row_delta = 0;
10288 }
10289 prev_edited_row = selection.end.row;
10290
10291 row_delta =
10292 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10293 }
10294
10295 self.transact(window, cx, |this, window, cx| {
10296 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10297 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10298 });
10299 }
10300
10301 fn indent_selection(
10302 buffer: &MultiBuffer,
10303 snapshot: &MultiBufferSnapshot,
10304 selection: &mut Selection<Point>,
10305 edits: &mut Vec<(Range<Point>, String)>,
10306 delta_for_start_row: u32,
10307 cx: &App,
10308 ) -> u32 {
10309 let settings = buffer.language_settings_at(selection.start, cx);
10310 let tab_size = settings.tab_size.get();
10311 let indent_kind = if settings.hard_tabs {
10312 IndentKind::Tab
10313 } else {
10314 IndentKind::Space
10315 };
10316 let mut start_row = selection.start.row;
10317 let mut end_row = selection.end.row + 1;
10318
10319 // If a selection ends at the beginning of a line, don't indent
10320 // that last line.
10321 if selection.end.column == 0 && selection.end.row > selection.start.row {
10322 end_row -= 1;
10323 }
10324
10325 // Avoid re-indenting a row that has already been indented by a
10326 // previous selection, but still update this selection's column
10327 // to reflect that indentation.
10328 if delta_for_start_row > 0 {
10329 start_row += 1;
10330 selection.start.column += delta_for_start_row;
10331 if selection.end.row == selection.start.row {
10332 selection.end.column += delta_for_start_row;
10333 }
10334 }
10335
10336 let mut delta_for_end_row = 0;
10337 let has_multiple_rows = start_row + 1 != end_row;
10338 for row in start_row..end_row {
10339 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10340 let indent_delta = match (current_indent.kind, indent_kind) {
10341 (IndentKind::Space, IndentKind::Space) => {
10342 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10343 IndentSize::spaces(columns_to_next_tab_stop)
10344 }
10345 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10346 (_, IndentKind::Tab) => IndentSize::tab(),
10347 };
10348
10349 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10350 0
10351 } else {
10352 selection.start.column
10353 };
10354 let row_start = Point::new(row, start);
10355 edits.push((
10356 row_start..row_start,
10357 indent_delta.chars().collect::<String>(),
10358 ));
10359
10360 // Update this selection's endpoints to reflect the indentation.
10361 if row == selection.start.row {
10362 selection.start.column += indent_delta.len;
10363 }
10364 if row == selection.end.row {
10365 selection.end.column += indent_delta.len;
10366 delta_for_end_row = indent_delta.len;
10367 }
10368 }
10369
10370 if selection.start.row == selection.end.row {
10371 delta_for_start_row + delta_for_end_row
10372 } else {
10373 delta_for_end_row
10374 }
10375 }
10376
10377 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10378 if self.read_only(cx) {
10379 return;
10380 }
10381 if self.mode.is_single_line() {
10382 cx.propagate();
10383 return;
10384 }
10385
10386 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10387 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10388 let selections = self.selections.all::<Point>(cx);
10389 let mut deletion_ranges = Vec::new();
10390 let mut last_outdent = None;
10391 {
10392 let buffer = self.buffer.read(cx);
10393 let snapshot = buffer.snapshot(cx);
10394 for selection in &selections {
10395 let settings = buffer.language_settings_at(selection.start, cx);
10396 let tab_size = settings.tab_size.get();
10397 let mut rows = selection.spanned_rows(false, &display_map);
10398
10399 // Avoid re-outdenting a row that has already been outdented by a
10400 // previous selection.
10401 if let Some(last_row) = last_outdent
10402 && last_row == rows.start
10403 {
10404 rows.start = rows.start.next_row();
10405 }
10406 let has_multiple_rows = rows.len() > 1;
10407 for row in rows.iter_rows() {
10408 let indent_size = snapshot.indent_size_for_line(row);
10409 if indent_size.len > 0 {
10410 let deletion_len = match indent_size.kind {
10411 IndentKind::Space => {
10412 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10413 if columns_to_prev_tab_stop == 0 {
10414 tab_size
10415 } else {
10416 columns_to_prev_tab_stop
10417 }
10418 }
10419 IndentKind::Tab => 1,
10420 };
10421 let start = if has_multiple_rows
10422 || deletion_len > selection.start.column
10423 || indent_size.len < selection.start.column
10424 {
10425 0
10426 } else {
10427 selection.start.column - deletion_len
10428 };
10429 deletion_ranges.push(
10430 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10431 );
10432 last_outdent = Some(row);
10433 }
10434 }
10435 }
10436 }
10437
10438 self.transact(window, cx, |this, window, cx| {
10439 this.buffer.update(cx, |buffer, cx| {
10440 let empty_str: Arc<str> = Arc::default();
10441 buffer.edit(
10442 deletion_ranges
10443 .into_iter()
10444 .map(|range| (range, empty_str.clone())),
10445 None,
10446 cx,
10447 );
10448 });
10449 let selections = this.selections.all::<usize>(cx);
10450 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10451 });
10452 }
10453
10454 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10455 if self.read_only(cx) {
10456 return;
10457 }
10458 if self.mode.is_single_line() {
10459 cx.propagate();
10460 return;
10461 }
10462
10463 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10464 let selections = self
10465 .selections
10466 .all::<usize>(cx)
10467 .into_iter()
10468 .map(|s| s.range());
10469
10470 self.transact(window, cx, |this, window, cx| {
10471 this.buffer.update(cx, |buffer, cx| {
10472 buffer.autoindent_ranges(selections, cx);
10473 });
10474 let selections = this.selections.all::<usize>(cx);
10475 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10476 });
10477 }
10478
10479 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10480 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10481 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10482 let selections = self.selections.all::<Point>(cx);
10483
10484 let mut new_cursors = Vec::new();
10485 let mut edit_ranges = Vec::new();
10486 let mut selections = selections.iter().peekable();
10487 while let Some(selection) = selections.next() {
10488 let mut rows = selection.spanned_rows(false, &display_map);
10489
10490 // Accumulate contiguous regions of rows that we want to delete.
10491 while let Some(next_selection) = selections.peek() {
10492 let next_rows = next_selection.spanned_rows(false, &display_map);
10493 if next_rows.start <= rows.end {
10494 rows.end = next_rows.end;
10495 selections.next().unwrap();
10496 } else {
10497 break;
10498 }
10499 }
10500
10501 let buffer = display_map.buffer_snapshot();
10502 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10503 let edit_end = if buffer.max_point().row >= rows.end.0 {
10504 // If there's a line after the range, delete the \n from the end of the row range
10505 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer)
10506 } else {
10507 // If there isn't a line after the range, delete the \n from the line before the
10508 // start of the row range
10509 edit_start = edit_start.saturating_sub(1);
10510 buffer.len()
10511 };
10512
10513 let (cursor, goal) = movement::down_by_rows(
10514 &display_map,
10515 selection.head().to_display_point(&display_map),
10516 rows.len() as u32,
10517 selection.goal,
10518 false,
10519 &self.text_layout_details(window),
10520 );
10521
10522 new_cursors.push((
10523 selection.id,
10524 buffer.anchor_after(cursor.to_point(&display_map)),
10525 goal,
10526 ));
10527 edit_ranges.push(edit_start..edit_end);
10528 }
10529
10530 self.transact(window, cx, |this, window, cx| {
10531 let buffer = this.buffer.update(cx, |buffer, cx| {
10532 let empty_str: Arc<str> = Arc::default();
10533 buffer.edit(
10534 edit_ranges
10535 .into_iter()
10536 .map(|range| (range, empty_str.clone())),
10537 None,
10538 cx,
10539 );
10540 buffer.snapshot(cx)
10541 });
10542 let new_selections = new_cursors
10543 .into_iter()
10544 .map(|(id, cursor, goal)| {
10545 let cursor = cursor.to_point(&buffer);
10546 Selection {
10547 id,
10548 start: cursor,
10549 end: cursor,
10550 reversed: false,
10551 goal,
10552 }
10553 })
10554 .collect();
10555
10556 this.change_selections(Default::default(), window, cx, |s| {
10557 s.select(new_selections);
10558 });
10559 });
10560 }
10561
10562 pub fn join_lines_impl(
10563 &mut self,
10564 insert_whitespace: bool,
10565 window: &mut Window,
10566 cx: &mut Context<Self>,
10567 ) {
10568 if self.read_only(cx) {
10569 return;
10570 }
10571 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10572 for selection in self.selections.all::<Point>(cx) {
10573 let start = MultiBufferRow(selection.start.row);
10574 // Treat single line selections as if they include the next line. Otherwise this action
10575 // would do nothing for single line selections individual cursors.
10576 let end = if selection.start.row == selection.end.row {
10577 MultiBufferRow(selection.start.row + 1)
10578 } else {
10579 MultiBufferRow(selection.end.row)
10580 };
10581
10582 if let Some(last_row_range) = row_ranges.last_mut()
10583 && start <= last_row_range.end
10584 {
10585 last_row_range.end = end;
10586 continue;
10587 }
10588 row_ranges.push(start..end);
10589 }
10590
10591 let snapshot = self.buffer.read(cx).snapshot(cx);
10592 let mut cursor_positions = Vec::new();
10593 for row_range in &row_ranges {
10594 let anchor = snapshot.anchor_before(Point::new(
10595 row_range.end.previous_row().0,
10596 snapshot.line_len(row_range.end.previous_row()),
10597 ));
10598 cursor_positions.push(anchor..anchor);
10599 }
10600
10601 self.transact(window, cx, |this, window, cx| {
10602 for row_range in row_ranges.into_iter().rev() {
10603 for row in row_range.iter_rows().rev() {
10604 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10605 let next_line_row = row.next_row();
10606 let indent = snapshot.indent_size_for_line(next_line_row);
10607 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10608
10609 let replace =
10610 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10611 " "
10612 } else {
10613 ""
10614 };
10615
10616 this.buffer.update(cx, |buffer, cx| {
10617 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10618 });
10619 }
10620 }
10621
10622 this.change_selections(Default::default(), window, cx, |s| {
10623 s.select_anchor_ranges(cursor_positions)
10624 });
10625 });
10626 }
10627
10628 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10630 self.join_lines_impl(true, window, cx);
10631 }
10632
10633 pub fn sort_lines_case_sensitive(
10634 &mut self,
10635 _: &SortLinesCaseSensitive,
10636 window: &mut Window,
10637 cx: &mut Context<Self>,
10638 ) {
10639 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10640 }
10641
10642 pub fn sort_lines_by_length(
10643 &mut self,
10644 _: &SortLinesByLength,
10645 window: &mut Window,
10646 cx: &mut Context<Self>,
10647 ) {
10648 self.manipulate_immutable_lines(window, cx, |lines| {
10649 lines.sort_by_key(|&line| line.chars().count())
10650 })
10651 }
10652
10653 pub fn sort_lines_case_insensitive(
10654 &mut self,
10655 _: &SortLinesCaseInsensitive,
10656 window: &mut Window,
10657 cx: &mut Context<Self>,
10658 ) {
10659 self.manipulate_immutable_lines(window, cx, |lines| {
10660 lines.sort_by_key(|line| line.to_lowercase())
10661 })
10662 }
10663
10664 pub fn unique_lines_case_insensitive(
10665 &mut self,
10666 _: &UniqueLinesCaseInsensitive,
10667 window: &mut Window,
10668 cx: &mut Context<Self>,
10669 ) {
10670 self.manipulate_immutable_lines(window, cx, |lines| {
10671 let mut seen = HashSet::default();
10672 lines.retain(|line| seen.insert(line.to_lowercase()));
10673 })
10674 }
10675
10676 pub fn unique_lines_case_sensitive(
10677 &mut self,
10678 _: &UniqueLinesCaseSensitive,
10679 window: &mut Window,
10680 cx: &mut Context<Self>,
10681 ) {
10682 self.manipulate_immutable_lines(window, cx, |lines| {
10683 let mut seen = HashSet::default();
10684 lines.retain(|line| seen.insert(*line));
10685 })
10686 }
10687
10688 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10689 let snapshot = self.buffer.read(cx).snapshot(cx);
10690 for selection in self.selections.disjoint_anchors_arc().iter() {
10691 if snapshot
10692 .language_at(selection.start)
10693 .and_then(|lang| lang.config().wrap_characters.as_ref())
10694 .is_some()
10695 {
10696 return true;
10697 }
10698 }
10699 false
10700 }
10701
10702 fn wrap_selections_in_tag(
10703 &mut self,
10704 _: &WrapSelectionsInTag,
10705 window: &mut Window,
10706 cx: &mut Context<Self>,
10707 ) {
10708 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10709
10710 let snapshot = self.buffer.read(cx).snapshot(cx);
10711
10712 let mut edits = Vec::new();
10713 let mut boundaries = Vec::new();
10714
10715 for selection in self.selections.all::<Point>(cx).iter() {
10716 let Some(wrap_config) = snapshot
10717 .language_at(selection.start)
10718 .and_then(|lang| lang.config().wrap_characters.clone())
10719 else {
10720 continue;
10721 };
10722
10723 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10724 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10725
10726 let start_before = snapshot.anchor_before(selection.start);
10727 let end_after = snapshot.anchor_after(selection.end);
10728
10729 edits.push((start_before..start_before, open_tag));
10730 edits.push((end_after..end_after, close_tag));
10731
10732 boundaries.push((
10733 start_before,
10734 end_after,
10735 wrap_config.start_prefix.len(),
10736 wrap_config.end_suffix.len(),
10737 ));
10738 }
10739
10740 if edits.is_empty() {
10741 return;
10742 }
10743
10744 self.transact(window, cx, |this, window, cx| {
10745 let buffer = this.buffer.update(cx, |buffer, cx| {
10746 buffer.edit(edits, None, cx);
10747 buffer.snapshot(cx)
10748 });
10749
10750 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10751 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10752 boundaries.into_iter()
10753 {
10754 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10755 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10756 new_selections.push(open_offset..open_offset);
10757 new_selections.push(close_offset..close_offset);
10758 }
10759
10760 this.change_selections(Default::default(), window, cx, |s| {
10761 s.select_ranges(new_selections);
10762 });
10763
10764 this.request_autoscroll(Autoscroll::fit(), cx);
10765 });
10766 }
10767
10768 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10769 let Some(project) = self.project.clone() else {
10770 return;
10771 };
10772 self.reload(project, window, cx)
10773 .detach_and_notify_err(window, cx);
10774 }
10775
10776 pub fn restore_file(
10777 &mut self,
10778 _: &::git::RestoreFile,
10779 window: &mut Window,
10780 cx: &mut Context<Self>,
10781 ) {
10782 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10783 let mut buffer_ids = HashSet::default();
10784 let snapshot = self.buffer().read(cx).snapshot(cx);
10785 for selection in self.selections.all::<usize>(cx) {
10786 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10787 }
10788
10789 let buffer = self.buffer().read(cx);
10790 let ranges = buffer_ids
10791 .into_iter()
10792 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10793 .collect::<Vec<_>>();
10794
10795 self.restore_hunks_in_ranges(ranges, window, cx);
10796 }
10797
10798 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10799 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10800 let selections = self
10801 .selections
10802 .all(cx)
10803 .into_iter()
10804 .map(|s| s.range())
10805 .collect();
10806 self.restore_hunks_in_ranges(selections, window, cx);
10807 }
10808
10809 pub fn restore_hunks_in_ranges(
10810 &mut self,
10811 ranges: Vec<Range<Point>>,
10812 window: &mut Window,
10813 cx: &mut Context<Editor>,
10814 ) {
10815 let mut revert_changes = HashMap::default();
10816 let chunk_by = self
10817 .snapshot(window, cx)
10818 .hunks_for_ranges(ranges)
10819 .into_iter()
10820 .chunk_by(|hunk| hunk.buffer_id);
10821 for (buffer_id, hunks) in &chunk_by {
10822 let hunks = hunks.collect::<Vec<_>>();
10823 for hunk in &hunks {
10824 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10825 }
10826 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10827 }
10828 drop(chunk_by);
10829 if !revert_changes.is_empty() {
10830 self.transact(window, cx, |editor, window, cx| {
10831 editor.restore(revert_changes, window, cx);
10832 });
10833 }
10834 }
10835
10836 pub fn open_active_item_in_terminal(
10837 &mut self,
10838 _: &OpenInTerminal,
10839 window: &mut Window,
10840 cx: &mut Context<Self>,
10841 ) {
10842 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10843 let project_path = buffer.read(cx).project_path(cx)?;
10844 let project = self.project()?.read(cx);
10845 let entry = project.entry_for_path(&project_path, cx)?;
10846 let parent = match &entry.canonical_path {
10847 Some(canonical_path) => canonical_path.to_path_buf(),
10848 None => project.absolute_path(&project_path, cx)?,
10849 }
10850 .parent()?
10851 .to_path_buf();
10852 Some(parent)
10853 }) {
10854 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10855 }
10856 }
10857
10858 fn set_breakpoint_context_menu(
10859 &mut self,
10860 display_row: DisplayRow,
10861 position: Option<Anchor>,
10862 clicked_point: gpui::Point<Pixels>,
10863 window: &mut Window,
10864 cx: &mut Context<Self>,
10865 ) {
10866 let source = self
10867 .buffer
10868 .read(cx)
10869 .snapshot(cx)
10870 .anchor_before(Point::new(display_row.0, 0u32));
10871
10872 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10873
10874 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10875 self,
10876 source,
10877 clicked_point,
10878 context_menu,
10879 window,
10880 cx,
10881 );
10882 }
10883
10884 fn add_edit_breakpoint_block(
10885 &mut self,
10886 anchor: Anchor,
10887 breakpoint: &Breakpoint,
10888 edit_action: BreakpointPromptEditAction,
10889 window: &mut Window,
10890 cx: &mut Context<Self>,
10891 ) {
10892 let weak_editor = cx.weak_entity();
10893 let bp_prompt = cx.new(|cx| {
10894 BreakpointPromptEditor::new(
10895 weak_editor,
10896 anchor,
10897 breakpoint.clone(),
10898 edit_action,
10899 window,
10900 cx,
10901 )
10902 });
10903
10904 let height = bp_prompt.update(cx, |this, cx| {
10905 this.prompt
10906 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10907 });
10908 let cloned_prompt = bp_prompt.clone();
10909 let blocks = vec![BlockProperties {
10910 style: BlockStyle::Sticky,
10911 placement: BlockPlacement::Above(anchor),
10912 height: Some(height),
10913 render: Arc::new(move |cx| {
10914 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10915 cloned_prompt.clone().into_any_element()
10916 }),
10917 priority: 0,
10918 }];
10919
10920 let focus_handle = bp_prompt.focus_handle(cx);
10921 window.focus(&focus_handle);
10922
10923 let block_ids = self.insert_blocks(blocks, None, cx);
10924 bp_prompt.update(cx, |prompt, _| {
10925 prompt.add_block_ids(block_ids);
10926 });
10927 }
10928
10929 pub(crate) fn breakpoint_at_row(
10930 &self,
10931 row: u32,
10932 window: &mut Window,
10933 cx: &mut Context<Self>,
10934 ) -> Option<(Anchor, Breakpoint)> {
10935 let snapshot = self.snapshot(window, cx);
10936 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10937
10938 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10939 }
10940
10941 pub(crate) fn breakpoint_at_anchor(
10942 &self,
10943 breakpoint_position: Anchor,
10944 snapshot: &EditorSnapshot,
10945 cx: &mut Context<Self>,
10946 ) -> Option<(Anchor, Breakpoint)> {
10947 let buffer = self
10948 .buffer
10949 .read(cx)
10950 .buffer_for_anchor(breakpoint_position, cx)?;
10951
10952 let enclosing_excerpt = breakpoint_position.excerpt_id;
10953 let buffer_snapshot = buffer.read(cx).snapshot();
10954
10955 let row = buffer_snapshot
10956 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10957 .row;
10958
10959 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10960 let anchor_end = snapshot
10961 .buffer_snapshot()
10962 .anchor_after(Point::new(row, line_len));
10963
10964 self.breakpoint_store
10965 .as_ref()?
10966 .read_with(cx, |breakpoint_store, cx| {
10967 breakpoint_store
10968 .breakpoints(
10969 &buffer,
10970 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10971 &buffer_snapshot,
10972 cx,
10973 )
10974 .next()
10975 .and_then(|(bp, _)| {
10976 let breakpoint_row = buffer_snapshot
10977 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10978 .row;
10979
10980 if breakpoint_row == row {
10981 snapshot
10982 .buffer_snapshot()
10983 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10984 .map(|position| (position, bp.bp.clone()))
10985 } else {
10986 None
10987 }
10988 })
10989 })
10990 }
10991
10992 pub fn edit_log_breakpoint(
10993 &mut self,
10994 _: &EditLogBreakpoint,
10995 window: &mut Window,
10996 cx: &mut Context<Self>,
10997 ) {
10998 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10999 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11000 message: None,
11001 state: BreakpointState::Enabled,
11002 condition: None,
11003 hit_condition: None,
11004 });
11005
11006 self.add_edit_breakpoint_block(
11007 anchor,
11008 &breakpoint,
11009 BreakpointPromptEditAction::Log,
11010 window,
11011 cx,
11012 );
11013 }
11014 }
11015
11016 fn breakpoints_at_cursors(
11017 &self,
11018 window: &mut Window,
11019 cx: &mut Context<Self>,
11020 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11021 let snapshot = self.snapshot(window, cx);
11022 let cursors = self
11023 .selections
11024 .disjoint_anchors_arc()
11025 .iter()
11026 .map(|selection| {
11027 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11028
11029 let breakpoint_position = self
11030 .breakpoint_at_row(cursor_position.row, window, cx)
11031 .map(|bp| bp.0)
11032 .unwrap_or_else(|| {
11033 snapshot
11034 .display_snapshot
11035 .buffer_snapshot()
11036 .anchor_after(Point::new(cursor_position.row, 0))
11037 });
11038
11039 let breakpoint = self
11040 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11041 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11042
11043 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11044 })
11045 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
11046 .collect::<HashMap<Anchor, _>>();
11047
11048 cursors.into_iter().collect()
11049 }
11050
11051 pub fn enable_breakpoint(
11052 &mut self,
11053 _: &crate::actions::EnableBreakpoint,
11054 window: &mut Window,
11055 cx: &mut Context<Self>,
11056 ) {
11057 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11058 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11059 continue;
11060 };
11061 self.edit_breakpoint_at_anchor(
11062 anchor,
11063 breakpoint,
11064 BreakpointEditAction::InvertState,
11065 cx,
11066 );
11067 }
11068 }
11069
11070 pub fn disable_breakpoint(
11071 &mut self,
11072 _: &crate::actions::DisableBreakpoint,
11073 window: &mut Window,
11074 cx: &mut Context<Self>,
11075 ) {
11076 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11077 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11078 continue;
11079 };
11080 self.edit_breakpoint_at_anchor(
11081 anchor,
11082 breakpoint,
11083 BreakpointEditAction::InvertState,
11084 cx,
11085 );
11086 }
11087 }
11088
11089 pub fn toggle_breakpoint(
11090 &mut self,
11091 _: &crate::actions::ToggleBreakpoint,
11092 window: &mut Window,
11093 cx: &mut Context<Self>,
11094 ) {
11095 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11096 if let Some(breakpoint) = breakpoint {
11097 self.edit_breakpoint_at_anchor(
11098 anchor,
11099 breakpoint,
11100 BreakpointEditAction::Toggle,
11101 cx,
11102 );
11103 } else {
11104 self.edit_breakpoint_at_anchor(
11105 anchor,
11106 Breakpoint::new_standard(),
11107 BreakpointEditAction::Toggle,
11108 cx,
11109 );
11110 }
11111 }
11112 }
11113
11114 pub fn edit_breakpoint_at_anchor(
11115 &mut self,
11116 breakpoint_position: Anchor,
11117 breakpoint: Breakpoint,
11118 edit_action: BreakpointEditAction,
11119 cx: &mut Context<Self>,
11120 ) {
11121 let Some(breakpoint_store) = &self.breakpoint_store else {
11122 return;
11123 };
11124
11125 let Some(buffer) = self
11126 .buffer
11127 .read(cx)
11128 .buffer_for_anchor(breakpoint_position, cx)
11129 else {
11130 return;
11131 };
11132
11133 breakpoint_store.update(cx, |breakpoint_store, cx| {
11134 breakpoint_store.toggle_breakpoint(
11135 buffer,
11136 BreakpointWithPosition {
11137 position: breakpoint_position.text_anchor,
11138 bp: breakpoint,
11139 },
11140 edit_action,
11141 cx,
11142 );
11143 });
11144
11145 cx.notify();
11146 }
11147
11148 #[cfg(any(test, feature = "test-support"))]
11149 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11150 self.breakpoint_store.clone()
11151 }
11152
11153 pub fn prepare_restore_change(
11154 &self,
11155 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11156 hunk: &MultiBufferDiffHunk,
11157 cx: &mut App,
11158 ) -> Option<()> {
11159 if hunk.is_created_file() {
11160 return None;
11161 }
11162 let buffer = self.buffer.read(cx);
11163 let diff = buffer.diff_for(hunk.buffer_id)?;
11164 let buffer = buffer.buffer(hunk.buffer_id)?;
11165 let buffer = buffer.read(cx);
11166 let original_text = diff
11167 .read(cx)
11168 .base_text()
11169 .as_rope()
11170 .slice(hunk.diff_base_byte_range.clone());
11171 let buffer_snapshot = buffer.snapshot();
11172 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11173 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11174 probe
11175 .0
11176 .start
11177 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11178 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11179 }) {
11180 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11181 Some(())
11182 } else {
11183 None
11184 }
11185 }
11186
11187 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11188 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11189 }
11190
11191 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11192 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11193 }
11194
11195 fn manipulate_lines<M>(
11196 &mut self,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 mut manipulate: M,
11200 ) where
11201 M: FnMut(&str) -> LineManipulationResult,
11202 {
11203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11204
11205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11206 let buffer = self.buffer.read(cx).snapshot(cx);
11207
11208 let mut edits = Vec::new();
11209
11210 let selections = self.selections.all::<Point>(cx);
11211 let mut selections = selections.iter().peekable();
11212 let mut contiguous_row_selections = Vec::new();
11213 let mut new_selections = Vec::new();
11214 let mut added_lines = 0;
11215 let mut removed_lines = 0;
11216
11217 while let Some(selection) = selections.next() {
11218 let (start_row, end_row) = consume_contiguous_rows(
11219 &mut contiguous_row_selections,
11220 selection,
11221 &display_map,
11222 &mut selections,
11223 );
11224
11225 let start_point = Point::new(start_row.0, 0);
11226 let end_point = Point::new(
11227 end_row.previous_row().0,
11228 buffer.line_len(end_row.previous_row()),
11229 );
11230 let text = buffer
11231 .text_for_range(start_point..end_point)
11232 .collect::<String>();
11233
11234 let LineManipulationResult {
11235 new_text,
11236 line_count_before,
11237 line_count_after,
11238 } = manipulate(&text);
11239
11240 edits.push((start_point..end_point, new_text));
11241
11242 // Selections must change based on added and removed line count
11243 let start_row =
11244 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11245 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11246 new_selections.push(Selection {
11247 id: selection.id,
11248 start: start_row,
11249 end: end_row,
11250 goal: SelectionGoal::None,
11251 reversed: selection.reversed,
11252 });
11253
11254 if line_count_after > line_count_before {
11255 added_lines += line_count_after - line_count_before;
11256 } else if line_count_before > line_count_after {
11257 removed_lines += line_count_before - line_count_after;
11258 }
11259 }
11260
11261 self.transact(window, cx, |this, window, cx| {
11262 let buffer = this.buffer.update(cx, |buffer, cx| {
11263 buffer.edit(edits, None, cx);
11264 buffer.snapshot(cx)
11265 });
11266
11267 // Recalculate offsets on newly edited buffer
11268 let new_selections = new_selections
11269 .iter()
11270 .map(|s| {
11271 let start_point = Point::new(s.start.0, 0);
11272 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11273 Selection {
11274 id: s.id,
11275 start: buffer.point_to_offset(start_point),
11276 end: buffer.point_to_offset(end_point),
11277 goal: s.goal,
11278 reversed: s.reversed,
11279 }
11280 })
11281 .collect();
11282
11283 this.change_selections(Default::default(), window, cx, |s| {
11284 s.select(new_selections);
11285 });
11286
11287 this.request_autoscroll(Autoscroll::fit(), cx);
11288 });
11289 }
11290
11291 fn manipulate_immutable_lines<Fn>(
11292 &mut self,
11293 window: &mut Window,
11294 cx: &mut Context<Self>,
11295 mut callback: Fn,
11296 ) where
11297 Fn: FnMut(&mut Vec<&str>),
11298 {
11299 self.manipulate_lines(window, cx, |text| {
11300 let mut lines: Vec<&str> = text.split('\n').collect();
11301 let line_count_before = lines.len();
11302
11303 callback(&mut lines);
11304
11305 LineManipulationResult {
11306 new_text: lines.join("\n"),
11307 line_count_before,
11308 line_count_after: lines.len(),
11309 }
11310 });
11311 }
11312
11313 fn manipulate_mutable_lines<Fn>(
11314 &mut self,
11315 window: &mut Window,
11316 cx: &mut Context<Self>,
11317 mut callback: Fn,
11318 ) where
11319 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11320 {
11321 self.manipulate_lines(window, cx, |text| {
11322 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11323 let line_count_before = lines.len();
11324
11325 callback(&mut lines);
11326
11327 LineManipulationResult {
11328 new_text: lines.join("\n"),
11329 line_count_before,
11330 line_count_after: lines.len(),
11331 }
11332 });
11333 }
11334
11335 pub fn convert_indentation_to_spaces(
11336 &mut self,
11337 _: &ConvertIndentationToSpaces,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) {
11341 let settings = self.buffer.read(cx).language_settings(cx);
11342 let tab_size = settings.tab_size.get() as usize;
11343
11344 self.manipulate_mutable_lines(window, cx, |lines| {
11345 // Allocates a reasonably sized scratch buffer once for the whole loop
11346 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11347 // Avoids recomputing spaces that could be inserted many times
11348 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11349 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11350 .collect();
11351
11352 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11353 let mut chars = line.as_ref().chars();
11354 let mut col = 0;
11355 let mut changed = false;
11356
11357 for ch in chars.by_ref() {
11358 match ch {
11359 ' ' => {
11360 reindented_line.push(' ');
11361 col += 1;
11362 }
11363 '\t' => {
11364 // \t are converted to spaces depending on the current column
11365 let spaces_len = tab_size - (col % tab_size);
11366 reindented_line.extend(&space_cache[spaces_len - 1]);
11367 col += spaces_len;
11368 changed = true;
11369 }
11370 _ => {
11371 // If we dont append before break, the character is consumed
11372 reindented_line.push(ch);
11373 break;
11374 }
11375 }
11376 }
11377
11378 if !changed {
11379 reindented_line.clear();
11380 continue;
11381 }
11382 // Append the rest of the line and replace old reference with new one
11383 reindented_line.extend(chars);
11384 *line = Cow::Owned(reindented_line.clone());
11385 reindented_line.clear();
11386 }
11387 });
11388 }
11389
11390 pub fn convert_indentation_to_tabs(
11391 &mut self,
11392 _: &ConvertIndentationToTabs,
11393 window: &mut Window,
11394 cx: &mut Context<Self>,
11395 ) {
11396 let settings = self.buffer.read(cx).language_settings(cx);
11397 let tab_size = settings.tab_size.get() as usize;
11398
11399 self.manipulate_mutable_lines(window, cx, |lines| {
11400 // Allocates a reasonably sized buffer once for the whole loop
11401 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11402 // Avoids recomputing spaces that could be inserted many times
11403 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11404 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11405 .collect();
11406
11407 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11408 let mut chars = line.chars();
11409 let mut spaces_count = 0;
11410 let mut first_non_indent_char = None;
11411 let mut changed = false;
11412
11413 for ch in chars.by_ref() {
11414 match ch {
11415 ' ' => {
11416 // Keep track of spaces. Append \t when we reach tab_size
11417 spaces_count += 1;
11418 changed = true;
11419 if spaces_count == tab_size {
11420 reindented_line.push('\t');
11421 spaces_count = 0;
11422 }
11423 }
11424 '\t' => {
11425 reindented_line.push('\t');
11426 spaces_count = 0;
11427 }
11428 _ => {
11429 // Dont append it yet, we might have remaining spaces
11430 first_non_indent_char = Some(ch);
11431 break;
11432 }
11433 }
11434 }
11435
11436 if !changed {
11437 reindented_line.clear();
11438 continue;
11439 }
11440 // Remaining spaces that didn't make a full tab stop
11441 if spaces_count > 0 {
11442 reindented_line.extend(&space_cache[spaces_count - 1]);
11443 }
11444 // If we consume an extra character that was not indentation, add it back
11445 if let Some(extra_char) = first_non_indent_char {
11446 reindented_line.push(extra_char);
11447 }
11448 // Append the rest of the line and replace old reference with new one
11449 reindented_line.extend(chars);
11450 *line = Cow::Owned(reindented_line.clone());
11451 reindented_line.clear();
11452 }
11453 });
11454 }
11455
11456 pub fn convert_to_upper_case(
11457 &mut self,
11458 _: &ConvertToUpperCase,
11459 window: &mut Window,
11460 cx: &mut Context<Self>,
11461 ) {
11462 self.manipulate_text(window, cx, |text| text.to_uppercase())
11463 }
11464
11465 pub fn convert_to_lower_case(
11466 &mut self,
11467 _: &ConvertToLowerCase,
11468 window: &mut Window,
11469 cx: &mut Context<Self>,
11470 ) {
11471 self.manipulate_text(window, cx, |text| text.to_lowercase())
11472 }
11473
11474 pub fn convert_to_title_case(
11475 &mut self,
11476 _: &ConvertToTitleCase,
11477 window: &mut Window,
11478 cx: &mut Context<Self>,
11479 ) {
11480 self.manipulate_text(window, cx, |text| {
11481 text.split('\n')
11482 .map(|line| line.to_case(Case::Title))
11483 .join("\n")
11484 })
11485 }
11486
11487 pub fn convert_to_snake_case(
11488 &mut self,
11489 _: &ConvertToSnakeCase,
11490 window: &mut Window,
11491 cx: &mut Context<Self>,
11492 ) {
11493 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11494 }
11495
11496 pub fn convert_to_kebab_case(
11497 &mut self,
11498 _: &ConvertToKebabCase,
11499 window: &mut Window,
11500 cx: &mut Context<Self>,
11501 ) {
11502 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11503 }
11504
11505 pub fn convert_to_upper_camel_case(
11506 &mut self,
11507 _: &ConvertToUpperCamelCase,
11508 window: &mut Window,
11509 cx: &mut Context<Self>,
11510 ) {
11511 self.manipulate_text(window, cx, |text| {
11512 text.split('\n')
11513 .map(|line| line.to_case(Case::UpperCamel))
11514 .join("\n")
11515 })
11516 }
11517
11518 pub fn convert_to_lower_camel_case(
11519 &mut self,
11520 _: &ConvertToLowerCamelCase,
11521 window: &mut Window,
11522 cx: &mut Context<Self>,
11523 ) {
11524 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11525 }
11526
11527 pub fn convert_to_opposite_case(
11528 &mut self,
11529 _: &ConvertToOppositeCase,
11530 window: &mut Window,
11531 cx: &mut Context<Self>,
11532 ) {
11533 self.manipulate_text(window, cx, |text| {
11534 text.chars()
11535 .fold(String::with_capacity(text.len()), |mut t, c| {
11536 if c.is_uppercase() {
11537 t.extend(c.to_lowercase());
11538 } else {
11539 t.extend(c.to_uppercase());
11540 }
11541 t
11542 })
11543 })
11544 }
11545
11546 pub fn convert_to_sentence_case(
11547 &mut self,
11548 _: &ConvertToSentenceCase,
11549 window: &mut Window,
11550 cx: &mut Context<Self>,
11551 ) {
11552 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11553 }
11554
11555 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11556 self.manipulate_text(window, cx, |text| {
11557 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11558 if has_upper_case_characters {
11559 text.to_lowercase()
11560 } else {
11561 text.to_uppercase()
11562 }
11563 })
11564 }
11565
11566 pub fn convert_to_rot13(
11567 &mut self,
11568 _: &ConvertToRot13,
11569 window: &mut Window,
11570 cx: &mut Context<Self>,
11571 ) {
11572 self.manipulate_text(window, cx, |text| {
11573 text.chars()
11574 .map(|c| match c {
11575 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11576 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11577 _ => c,
11578 })
11579 .collect()
11580 })
11581 }
11582
11583 pub fn convert_to_rot47(
11584 &mut self,
11585 _: &ConvertToRot47,
11586 window: &mut Window,
11587 cx: &mut Context<Self>,
11588 ) {
11589 self.manipulate_text(window, cx, |text| {
11590 text.chars()
11591 .map(|c| {
11592 let code_point = c as u32;
11593 if code_point >= 33 && code_point <= 126 {
11594 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11595 }
11596 c
11597 })
11598 .collect()
11599 })
11600 }
11601
11602 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11603 where
11604 Fn: FnMut(&str) -> String,
11605 {
11606 let buffer = self.buffer.read(cx).snapshot(cx);
11607
11608 let mut new_selections = Vec::new();
11609 let mut edits = Vec::new();
11610 let mut selection_adjustment = 0i32;
11611
11612 for selection in self.selections.all_adjusted(cx) {
11613 let selection_is_empty = selection.is_empty();
11614
11615 let (start, end) = if selection_is_empty {
11616 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11617 (word_range.start, word_range.end)
11618 } else {
11619 (
11620 buffer.point_to_offset(selection.start),
11621 buffer.point_to_offset(selection.end),
11622 )
11623 };
11624
11625 let text = buffer.text_for_range(start..end).collect::<String>();
11626 let old_length = text.len() as i32;
11627 let text = callback(&text);
11628
11629 new_selections.push(Selection {
11630 start: (start as i32 - selection_adjustment) as usize,
11631 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11632 goal: SelectionGoal::None,
11633 id: selection.id,
11634 reversed: selection.reversed,
11635 });
11636
11637 selection_adjustment += old_length - text.len() as i32;
11638
11639 edits.push((start..end, text));
11640 }
11641
11642 self.transact(window, cx, |this, window, cx| {
11643 this.buffer.update(cx, |buffer, cx| {
11644 buffer.edit(edits, None, cx);
11645 });
11646
11647 this.change_selections(Default::default(), window, cx, |s| {
11648 s.select(new_selections);
11649 });
11650
11651 this.request_autoscroll(Autoscroll::fit(), cx);
11652 });
11653 }
11654
11655 pub fn move_selection_on_drop(
11656 &mut self,
11657 selection: &Selection<Anchor>,
11658 target: DisplayPoint,
11659 is_cut: bool,
11660 window: &mut Window,
11661 cx: &mut Context<Self>,
11662 ) {
11663 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11664 let buffer = display_map.buffer_snapshot();
11665 let mut edits = Vec::new();
11666 let insert_point = display_map
11667 .clip_point(target, Bias::Left)
11668 .to_point(&display_map);
11669 let text = buffer
11670 .text_for_range(selection.start..selection.end)
11671 .collect::<String>();
11672 if is_cut {
11673 edits.push(((selection.start..selection.end), String::new()));
11674 }
11675 let insert_anchor = buffer.anchor_before(insert_point);
11676 edits.push(((insert_anchor..insert_anchor), text));
11677 let last_edit_start = insert_anchor.bias_left(buffer);
11678 let last_edit_end = insert_anchor.bias_right(buffer);
11679 self.transact(window, cx, |this, window, cx| {
11680 this.buffer.update(cx, |buffer, cx| {
11681 buffer.edit(edits, None, cx);
11682 });
11683 this.change_selections(Default::default(), window, cx, |s| {
11684 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11685 });
11686 });
11687 }
11688
11689 pub fn clear_selection_drag_state(&mut self) {
11690 self.selection_drag_state = SelectionDragState::None;
11691 }
11692
11693 pub fn duplicate(
11694 &mut self,
11695 upwards: bool,
11696 whole_lines: bool,
11697 window: &mut Window,
11698 cx: &mut Context<Self>,
11699 ) {
11700 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11701
11702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11703 let buffer = display_map.buffer_snapshot();
11704 let selections = self.selections.all::<Point>(cx);
11705
11706 let mut edits = Vec::new();
11707 let mut selections_iter = selections.iter().peekable();
11708 while let Some(selection) = selections_iter.next() {
11709 let mut rows = selection.spanned_rows(false, &display_map);
11710 // duplicate line-wise
11711 if whole_lines || selection.start == selection.end {
11712 // Avoid duplicating the same lines twice.
11713 while let Some(next_selection) = selections_iter.peek() {
11714 let next_rows = next_selection.spanned_rows(false, &display_map);
11715 if next_rows.start < rows.end {
11716 rows.end = next_rows.end;
11717 selections_iter.next().unwrap();
11718 } else {
11719 break;
11720 }
11721 }
11722
11723 // Copy the text from the selected row region and splice it either at the start
11724 // or end of the region.
11725 let start = Point::new(rows.start.0, 0);
11726 let end = Point::new(
11727 rows.end.previous_row().0,
11728 buffer.line_len(rows.end.previous_row()),
11729 );
11730
11731 let mut text = buffer.text_for_range(start..end).collect::<String>();
11732
11733 let insert_location = if upwards {
11734 // When duplicating upward, we need to insert before the current line.
11735 // If we're on the last line and it doesn't end with a newline,
11736 // we need to add a newline before the duplicated content.
11737 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11738 && buffer.max_point().column > 0
11739 && !text.ends_with('\n');
11740
11741 if needs_leading_newline {
11742 text.insert(0, '\n');
11743 end
11744 } else {
11745 text.push('\n');
11746 Point::new(rows.end.0, 0)
11747 }
11748 } else {
11749 text.push('\n');
11750 start
11751 };
11752 edits.push((insert_location..insert_location, text));
11753 } else {
11754 // duplicate character-wise
11755 let start = selection.start;
11756 let end = selection.end;
11757 let text = buffer.text_for_range(start..end).collect::<String>();
11758 edits.push((selection.end..selection.end, text));
11759 }
11760 }
11761
11762 self.transact(window, cx, |this, _, cx| {
11763 this.buffer.update(cx, |buffer, cx| {
11764 buffer.edit(edits, None, cx);
11765 });
11766
11767 this.request_autoscroll(Autoscroll::fit(), cx);
11768 });
11769 }
11770
11771 pub fn duplicate_line_up(
11772 &mut self,
11773 _: &DuplicateLineUp,
11774 window: &mut Window,
11775 cx: &mut Context<Self>,
11776 ) {
11777 self.duplicate(true, true, window, cx);
11778 }
11779
11780 pub fn duplicate_line_down(
11781 &mut self,
11782 _: &DuplicateLineDown,
11783 window: &mut Window,
11784 cx: &mut Context<Self>,
11785 ) {
11786 self.duplicate(false, true, window, cx);
11787 }
11788
11789 pub fn duplicate_selection(
11790 &mut self,
11791 _: &DuplicateSelection,
11792 window: &mut Window,
11793 cx: &mut Context<Self>,
11794 ) {
11795 self.duplicate(false, false, window, cx);
11796 }
11797
11798 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11799 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11800 if self.mode.is_single_line() {
11801 cx.propagate();
11802 return;
11803 }
11804
11805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11806 let buffer = self.buffer.read(cx).snapshot(cx);
11807
11808 let mut edits = Vec::new();
11809 let mut unfold_ranges = Vec::new();
11810 let mut refold_creases = Vec::new();
11811
11812 let selections = self.selections.all::<Point>(cx);
11813 let mut selections = selections.iter().peekable();
11814 let mut contiguous_row_selections = Vec::new();
11815 let mut new_selections = Vec::new();
11816
11817 while let Some(selection) = selections.next() {
11818 // Find all the selections that span a contiguous row range
11819 let (start_row, end_row) = consume_contiguous_rows(
11820 &mut contiguous_row_selections,
11821 selection,
11822 &display_map,
11823 &mut selections,
11824 );
11825
11826 // Move the text spanned by the row range to be before the line preceding the row range
11827 if start_row.0 > 0 {
11828 let range_to_move = Point::new(
11829 start_row.previous_row().0,
11830 buffer.line_len(start_row.previous_row()),
11831 )
11832 ..Point::new(
11833 end_row.previous_row().0,
11834 buffer.line_len(end_row.previous_row()),
11835 );
11836 let insertion_point = display_map
11837 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11838 .0;
11839
11840 // Don't move lines across excerpts
11841 if buffer
11842 .excerpt_containing(insertion_point..range_to_move.end)
11843 .is_some()
11844 {
11845 let text = buffer
11846 .text_for_range(range_to_move.clone())
11847 .flat_map(|s| s.chars())
11848 .skip(1)
11849 .chain(['\n'])
11850 .collect::<String>();
11851
11852 edits.push((
11853 buffer.anchor_after(range_to_move.start)
11854 ..buffer.anchor_before(range_to_move.end),
11855 String::new(),
11856 ));
11857 let insertion_anchor = buffer.anchor_after(insertion_point);
11858 edits.push((insertion_anchor..insertion_anchor, text));
11859
11860 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11861
11862 // Move selections up
11863 new_selections.extend(contiguous_row_selections.drain(..).map(
11864 |mut selection| {
11865 selection.start.row -= row_delta;
11866 selection.end.row -= row_delta;
11867 selection
11868 },
11869 ));
11870
11871 // Move folds up
11872 unfold_ranges.push(range_to_move.clone());
11873 for fold in display_map.folds_in_range(
11874 buffer.anchor_before(range_to_move.start)
11875 ..buffer.anchor_after(range_to_move.end),
11876 ) {
11877 let mut start = fold.range.start.to_point(&buffer);
11878 let mut end = fold.range.end.to_point(&buffer);
11879 start.row -= row_delta;
11880 end.row -= row_delta;
11881 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11882 }
11883 }
11884 }
11885
11886 // If we didn't move line(s), preserve the existing selections
11887 new_selections.append(&mut contiguous_row_selections);
11888 }
11889
11890 self.transact(window, cx, |this, window, cx| {
11891 this.unfold_ranges(&unfold_ranges, true, true, cx);
11892 this.buffer.update(cx, |buffer, cx| {
11893 for (range, text) in edits {
11894 buffer.edit([(range, text)], None, cx);
11895 }
11896 });
11897 this.fold_creases(refold_creases, true, window, cx);
11898 this.change_selections(Default::default(), window, cx, |s| {
11899 s.select(new_selections);
11900 })
11901 });
11902 }
11903
11904 pub fn move_line_down(
11905 &mut self,
11906 _: &MoveLineDown,
11907 window: &mut Window,
11908 cx: &mut Context<Self>,
11909 ) {
11910 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11911 if self.mode.is_single_line() {
11912 cx.propagate();
11913 return;
11914 }
11915
11916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11917 let buffer = self.buffer.read(cx).snapshot(cx);
11918
11919 let mut edits = Vec::new();
11920 let mut unfold_ranges = Vec::new();
11921 let mut refold_creases = Vec::new();
11922
11923 let selections = self.selections.all::<Point>(cx);
11924 let mut selections = selections.iter().peekable();
11925 let mut contiguous_row_selections = Vec::new();
11926 let mut new_selections = Vec::new();
11927
11928 while let Some(selection) = selections.next() {
11929 // Find all the selections that span a contiguous row range
11930 let (start_row, end_row) = consume_contiguous_rows(
11931 &mut contiguous_row_selections,
11932 selection,
11933 &display_map,
11934 &mut selections,
11935 );
11936
11937 // Move the text spanned by the row range to be after the last line of the row range
11938 if end_row.0 <= buffer.max_point().row {
11939 let range_to_move =
11940 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11941 let insertion_point = display_map
11942 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11943 .0;
11944
11945 // Don't move lines across excerpt boundaries
11946 if buffer
11947 .excerpt_containing(range_to_move.start..insertion_point)
11948 .is_some()
11949 {
11950 let mut text = String::from("\n");
11951 text.extend(buffer.text_for_range(range_to_move.clone()));
11952 text.pop(); // Drop trailing newline
11953 edits.push((
11954 buffer.anchor_after(range_to_move.start)
11955 ..buffer.anchor_before(range_to_move.end),
11956 String::new(),
11957 ));
11958 let insertion_anchor = buffer.anchor_after(insertion_point);
11959 edits.push((insertion_anchor..insertion_anchor, text));
11960
11961 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11962
11963 // Move selections down
11964 new_selections.extend(contiguous_row_selections.drain(..).map(
11965 |mut selection| {
11966 selection.start.row += row_delta;
11967 selection.end.row += row_delta;
11968 selection
11969 },
11970 ));
11971
11972 // Move folds down
11973 unfold_ranges.push(range_to_move.clone());
11974 for fold in display_map.folds_in_range(
11975 buffer.anchor_before(range_to_move.start)
11976 ..buffer.anchor_after(range_to_move.end),
11977 ) {
11978 let mut start = fold.range.start.to_point(&buffer);
11979 let mut end = fold.range.end.to_point(&buffer);
11980 start.row += row_delta;
11981 end.row += row_delta;
11982 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11983 }
11984 }
11985 }
11986
11987 // If we didn't move line(s), preserve the existing selections
11988 new_selections.append(&mut contiguous_row_selections);
11989 }
11990
11991 self.transact(window, cx, |this, window, cx| {
11992 this.unfold_ranges(&unfold_ranges, true, true, cx);
11993 this.buffer.update(cx, |buffer, cx| {
11994 for (range, text) in edits {
11995 buffer.edit([(range, text)], None, cx);
11996 }
11997 });
11998 this.fold_creases(refold_creases, true, window, cx);
11999 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12000 });
12001 }
12002
12003 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12004 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12005 let text_layout_details = &self.text_layout_details(window);
12006 self.transact(window, cx, |this, window, cx| {
12007 let edits = this.change_selections(Default::default(), window, cx, |s| {
12008 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12009 s.move_with(|display_map, selection| {
12010 if !selection.is_empty() {
12011 return;
12012 }
12013
12014 let mut head = selection.head();
12015 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12016 if head.column() == display_map.line_len(head.row()) {
12017 transpose_offset = display_map
12018 .buffer_snapshot()
12019 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12020 }
12021
12022 if transpose_offset == 0 {
12023 return;
12024 }
12025
12026 *head.column_mut() += 1;
12027 head = display_map.clip_point(head, Bias::Right);
12028 let goal = SelectionGoal::HorizontalPosition(
12029 display_map
12030 .x_for_display_point(head, text_layout_details)
12031 .into(),
12032 );
12033 selection.collapse_to(head, goal);
12034
12035 let transpose_start = display_map
12036 .buffer_snapshot()
12037 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12038 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12039 let transpose_end = display_map
12040 .buffer_snapshot()
12041 .clip_offset(transpose_offset + 1, Bias::Right);
12042 if let Some(ch) = display_map
12043 .buffer_snapshot()
12044 .chars_at(transpose_start)
12045 .next()
12046 {
12047 edits.push((transpose_start..transpose_offset, String::new()));
12048 edits.push((transpose_end..transpose_end, ch.to_string()));
12049 }
12050 }
12051 });
12052 edits
12053 });
12054 this.buffer
12055 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12056 let selections = this.selections.all::<usize>(cx);
12057 this.change_selections(Default::default(), window, cx, |s| {
12058 s.select(selections);
12059 });
12060 });
12061 }
12062
12063 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12065 if self.mode.is_single_line() {
12066 cx.propagate();
12067 return;
12068 }
12069
12070 self.rewrap_impl(RewrapOptions::default(), cx)
12071 }
12072
12073 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12074 let buffer = self.buffer.read(cx).snapshot(cx);
12075 let selections = self.selections.all::<Point>(cx);
12076
12077 #[derive(Clone, Debug, PartialEq)]
12078 enum CommentFormat {
12079 /// single line comment, with prefix for line
12080 Line(String),
12081 /// single line within a block comment, with prefix for line
12082 BlockLine(String),
12083 /// a single line of a block comment that includes the initial delimiter
12084 BlockCommentWithStart(BlockCommentConfig),
12085 /// a single line of a block comment that includes the ending delimiter
12086 BlockCommentWithEnd(BlockCommentConfig),
12087 }
12088
12089 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12090 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12091 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12092 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12093 .peekable();
12094
12095 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12096 row
12097 } else {
12098 return Vec::new();
12099 };
12100
12101 let language_settings = buffer.language_settings_at(selection.head(), cx);
12102 let language_scope = buffer.language_scope_at(selection.head());
12103
12104 let indent_and_prefix_for_row =
12105 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12106 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12107 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12108 &language_scope
12109 {
12110 let indent_end = Point::new(row, indent.len);
12111 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12112 let line_text_after_indent = buffer
12113 .text_for_range(indent_end..line_end)
12114 .collect::<String>();
12115
12116 let is_within_comment_override = buffer
12117 .language_scope_at(indent_end)
12118 .is_some_and(|scope| scope.override_name() == Some("comment"));
12119 let comment_delimiters = if is_within_comment_override {
12120 // we are within a comment syntax node, but we don't
12121 // yet know what kind of comment: block, doc or line
12122 match (
12123 language_scope.documentation_comment(),
12124 language_scope.block_comment(),
12125 ) {
12126 (Some(config), _) | (_, Some(config))
12127 if buffer.contains_str_at(indent_end, &config.start) =>
12128 {
12129 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12130 }
12131 (Some(config), _) | (_, Some(config))
12132 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12133 {
12134 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12135 }
12136 (Some(config), _) | (_, Some(config))
12137 if buffer.contains_str_at(indent_end, &config.prefix) =>
12138 {
12139 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12140 }
12141 (_, _) => language_scope
12142 .line_comment_prefixes()
12143 .iter()
12144 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12145 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12146 }
12147 } else {
12148 // we not in an overridden comment node, but we may
12149 // be within a non-overridden line comment node
12150 language_scope
12151 .line_comment_prefixes()
12152 .iter()
12153 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12154 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12155 };
12156
12157 let rewrap_prefix = language_scope
12158 .rewrap_prefixes()
12159 .iter()
12160 .find_map(|prefix_regex| {
12161 prefix_regex.find(&line_text_after_indent).map(|mat| {
12162 if mat.start() == 0 {
12163 Some(mat.as_str().to_string())
12164 } else {
12165 None
12166 }
12167 })
12168 })
12169 .flatten();
12170 (comment_delimiters, rewrap_prefix)
12171 } else {
12172 (None, None)
12173 };
12174 (indent, comment_prefix, rewrap_prefix)
12175 };
12176
12177 let mut ranges = Vec::new();
12178 let from_empty_selection = selection.is_empty();
12179
12180 let mut current_range_start = first_row;
12181 let mut prev_row = first_row;
12182 let (
12183 mut current_range_indent,
12184 mut current_range_comment_delimiters,
12185 mut current_range_rewrap_prefix,
12186 ) = indent_and_prefix_for_row(first_row);
12187
12188 for row in non_blank_rows_iter.skip(1) {
12189 let has_paragraph_break = row > prev_row + 1;
12190
12191 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12192 indent_and_prefix_for_row(row);
12193
12194 let has_indent_change = row_indent != current_range_indent;
12195 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12196
12197 let has_boundary_change = has_comment_change
12198 || row_rewrap_prefix.is_some()
12199 || (has_indent_change && current_range_comment_delimiters.is_some());
12200
12201 if has_paragraph_break || has_boundary_change {
12202 ranges.push((
12203 language_settings.clone(),
12204 Point::new(current_range_start, 0)
12205 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12206 current_range_indent,
12207 current_range_comment_delimiters.clone(),
12208 current_range_rewrap_prefix.clone(),
12209 from_empty_selection,
12210 ));
12211 current_range_start = row;
12212 current_range_indent = row_indent;
12213 current_range_comment_delimiters = row_comment_delimiters;
12214 current_range_rewrap_prefix = row_rewrap_prefix;
12215 }
12216 prev_row = row;
12217 }
12218
12219 ranges.push((
12220 language_settings.clone(),
12221 Point::new(current_range_start, 0)
12222 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12223 current_range_indent,
12224 current_range_comment_delimiters,
12225 current_range_rewrap_prefix,
12226 from_empty_selection,
12227 ));
12228
12229 ranges
12230 });
12231
12232 let mut edits = Vec::new();
12233 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12234
12235 for (
12236 language_settings,
12237 wrap_range,
12238 mut indent_size,
12239 comment_prefix,
12240 rewrap_prefix,
12241 from_empty_selection,
12242 ) in wrap_ranges
12243 {
12244 let mut start_row = wrap_range.start.row;
12245 let mut end_row = wrap_range.end.row;
12246
12247 // Skip selections that overlap with a range that has already been rewrapped.
12248 let selection_range = start_row..end_row;
12249 if rewrapped_row_ranges
12250 .iter()
12251 .any(|range| range.overlaps(&selection_range))
12252 {
12253 continue;
12254 }
12255
12256 let tab_size = language_settings.tab_size;
12257
12258 let (line_prefix, inside_comment) = match &comment_prefix {
12259 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12260 (Some(prefix.as_str()), true)
12261 }
12262 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12263 (Some(prefix.as_ref()), true)
12264 }
12265 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12266 start: _,
12267 end: _,
12268 prefix,
12269 tab_size,
12270 })) => {
12271 indent_size.len += tab_size;
12272 (Some(prefix.as_ref()), true)
12273 }
12274 None => (None, false),
12275 };
12276 let indent_prefix = indent_size.chars().collect::<String>();
12277 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12278
12279 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12280 RewrapBehavior::InComments => inside_comment,
12281 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12282 RewrapBehavior::Anywhere => true,
12283 };
12284
12285 let should_rewrap = options.override_language_settings
12286 || allow_rewrap_based_on_language
12287 || self.hard_wrap.is_some();
12288 if !should_rewrap {
12289 continue;
12290 }
12291
12292 if from_empty_selection {
12293 'expand_upwards: while start_row > 0 {
12294 let prev_row = start_row - 1;
12295 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12296 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12297 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12298 {
12299 start_row = prev_row;
12300 } else {
12301 break 'expand_upwards;
12302 }
12303 }
12304
12305 'expand_downwards: while end_row < buffer.max_point().row {
12306 let next_row = end_row + 1;
12307 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12308 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12309 && !buffer.is_line_blank(MultiBufferRow(next_row))
12310 {
12311 end_row = next_row;
12312 } else {
12313 break 'expand_downwards;
12314 }
12315 }
12316 }
12317
12318 let start = Point::new(start_row, 0);
12319 let start_offset = ToOffset::to_offset(&start, &buffer);
12320 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12321 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12322 let mut first_line_delimiter = None;
12323 let mut last_line_delimiter = None;
12324 let Some(lines_without_prefixes) = selection_text
12325 .lines()
12326 .enumerate()
12327 .map(|(ix, line)| {
12328 let line_trimmed = line.trim_start();
12329 if rewrap_prefix.is_some() && ix > 0 {
12330 Ok(line_trimmed)
12331 } else if let Some(
12332 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12333 start,
12334 prefix,
12335 end,
12336 tab_size,
12337 })
12338 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12339 start,
12340 prefix,
12341 end,
12342 tab_size,
12343 }),
12344 ) = &comment_prefix
12345 {
12346 let line_trimmed = line_trimmed
12347 .strip_prefix(start.as_ref())
12348 .map(|s| {
12349 let mut indent_size = indent_size;
12350 indent_size.len -= tab_size;
12351 let indent_prefix: String = indent_size.chars().collect();
12352 first_line_delimiter = Some((indent_prefix, start));
12353 s.trim_start()
12354 })
12355 .unwrap_or(line_trimmed);
12356 let line_trimmed = line_trimmed
12357 .strip_suffix(end.as_ref())
12358 .map(|s| {
12359 last_line_delimiter = Some(end);
12360 s.trim_end()
12361 })
12362 .unwrap_or(line_trimmed);
12363 let line_trimmed = line_trimmed
12364 .strip_prefix(prefix.as_ref())
12365 .unwrap_or(line_trimmed);
12366 Ok(line_trimmed)
12367 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12368 line_trimmed.strip_prefix(prefix).with_context(|| {
12369 format!("line did not start with prefix {prefix:?}: {line:?}")
12370 })
12371 } else {
12372 line_trimmed
12373 .strip_prefix(&line_prefix.trim_start())
12374 .with_context(|| {
12375 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12376 })
12377 }
12378 })
12379 .collect::<Result<Vec<_>, _>>()
12380 .log_err()
12381 else {
12382 continue;
12383 };
12384
12385 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12386 buffer
12387 .language_settings_at(Point::new(start_row, 0), cx)
12388 .preferred_line_length as usize
12389 });
12390
12391 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12392 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12393 } else {
12394 line_prefix.clone()
12395 };
12396
12397 let wrapped_text = {
12398 let mut wrapped_text = wrap_with_prefix(
12399 line_prefix,
12400 subsequent_lines_prefix,
12401 lines_without_prefixes.join("\n"),
12402 wrap_column,
12403 tab_size,
12404 options.preserve_existing_whitespace,
12405 );
12406
12407 if let Some((indent, delimiter)) = first_line_delimiter {
12408 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12409 }
12410 if let Some(last_line) = last_line_delimiter {
12411 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12412 }
12413
12414 wrapped_text
12415 };
12416
12417 // TODO: should always use char-based diff while still supporting cursor behavior that
12418 // matches vim.
12419 let mut diff_options = DiffOptions::default();
12420 if options.override_language_settings {
12421 diff_options.max_word_diff_len = 0;
12422 diff_options.max_word_diff_line_count = 0;
12423 } else {
12424 diff_options.max_word_diff_len = usize::MAX;
12425 diff_options.max_word_diff_line_count = usize::MAX;
12426 }
12427
12428 for (old_range, new_text) in
12429 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12430 {
12431 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12432 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12433 edits.push((edit_start..edit_end, new_text));
12434 }
12435
12436 rewrapped_row_ranges.push(start_row..=end_row);
12437 }
12438
12439 self.buffer
12440 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12441 }
12442
12443 pub fn cut_common(
12444 &mut self,
12445 cut_no_selection_line: bool,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) -> ClipboardItem {
12449 let mut text = String::new();
12450 let buffer = self.buffer.read(cx).snapshot(cx);
12451 let mut selections = self.selections.all::<Point>(cx);
12452 let mut clipboard_selections = Vec::with_capacity(selections.len());
12453 {
12454 let max_point = buffer.max_point();
12455 let mut is_first = true;
12456 for selection in &mut selections {
12457 let is_entire_line =
12458 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12459 if is_entire_line {
12460 selection.start = Point::new(selection.start.row, 0);
12461 if !selection.is_empty() && selection.end.column == 0 {
12462 selection.end = cmp::min(max_point, selection.end);
12463 } else {
12464 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12465 }
12466 selection.goal = SelectionGoal::None;
12467 }
12468 if is_first {
12469 is_first = false;
12470 } else {
12471 text += "\n";
12472 }
12473 let mut len = 0;
12474 for chunk in buffer.text_for_range(selection.start..selection.end) {
12475 text.push_str(chunk);
12476 len += chunk.len();
12477 }
12478 clipboard_selections.push(ClipboardSelection {
12479 len,
12480 is_entire_line,
12481 first_line_indent: buffer
12482 .indent_size_for_line(MultiBufferRow(selection.start.row))
12483 .len,
12484 });
12485 }
12486 }
12487
12488 self.transact(window, cx, |this, window, cx| {
12489 this.change_selections(Default::default(), window, cx, |s| {
12490 s.select(selections);
12491 });
12492 this.insert("", window, cx);
12493 });
12494 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12495 }
12496
12497 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12498 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12499 let item = self.cut_common(true, window, cx);
12500 cx.write_to_clipboard(item);
12501 }
12502
12503 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12505 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12506 s.move_with(|snapshot, sel| {
12507 if sel.is_empty() {
12508 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12509 }
12510 if sel.is_empty() {
12511 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12512 }
12513 });
12514 });
12515 let item = self.cut_common(false, window, cx);
12516 cx.set_global(KillRing(item))
12517 }
12518
12519 pub fn kill_ring_yank(
12520 &mut self,
12521 _: &KillRingYank,
12522 window: &mut Window,
12523 cx: &mut Context<Self>,
12524 ) {
12525 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12526 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12527 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12528 (kill_ring.text().to_string(), kill_ring.metadata_json())
12529 } else {
12530 return;
12531 }
12532 } else {
12533 return;
12534 };
12535 self.do_paste(&text, metadata, false, window, cx);
12536 }
12537
12538 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12539 self.do_copy(true, cx);
12540 }
12541
12542 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12543 self.do_copy(false, cx);
12544 }
12545
12546 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12547 let selections = self.selections.all::<Point>(cx);
12548 let buffer = self.buffer.read(cx).read(cx);
12549 let mut text = String::new();
12550
12551 let mut clipboard_selections = Vec::with_capacity(selections.len());
12552 {
12553 let max_point = buffer.max_point();
12554 let mut is_first = true;
12555 for selection in &selections {
12556 let mut start = selection.start;
12557 let mut end = selection.end;
12558 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12559 let mut add_trailing_newline = false;
12560 if is_entire_line {
12561 start = Point::new(start.row, 0);
12562 let next_line_start = Point::new(end.row + 1, 0);
12563 if next_line_start <= max_point {
12564 end = next_line_start;
12565 } else {
12566 // We're on the last line without a trailing newline.
12567 // Copy to the end of the line and add a newline afterwards.
12568 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12569 add_trailing_newline = true;
12570 }
12571 }
12572
12573 let mut trimmed_selections = Vec::new();
12574 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12575 let row = MultiBufferRow(start.row);
12576 let first_indent = buffer.indent_size_for_line(row);
12577 if first_indent.len == 0 || start.column > first_indent.len {
12578 trimmed_selections.push(start..end);
12579 } else {
12580 trimmed_selections.push(
12581 Point::new(row.0, first_indent.len)
12582 ..Point::new(row.0, buffer.line_len(row)),
12583 );
12584 for row in start.row + 1..=end.row {
12585 let mut line_len = buffer.line_len(MultiBufferRow(row));
12586 if row == end.row {
12587 line_len = end.column;
12588 }
12589 if line_len == 0 {
12590 trimmed_selections
12591 .push(Point::new(row, 0)..Point::new(row, line_len));
12592 continue;
12593 }
12594 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12595 if row_indent_size.len >= first_indent.len {
12596 trimmed_selections.push(
12597 Point::new(row, first_indent.len)..Point::new(row, line_len),
12598 );
12599 } else {
12600 trimmed_selections.clear();
12601 trimmed_selections.push(start..end);
12602 break;
12603 }
12604 }
12605 }
12606 } else {
12607 trimmed_selections.push(start..end);
12608 }
12609
12610 for trimmed_range in trimmed_selections {
12611 if is_first {
12612 is_first = false;
12613 } else {
12614 text += "\n";
12615 }
12616 let mut len = 0;
12617 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12618 text.push_str(chunk);
12619 len += chunk.len();
12620 }
12621 if add_trailing_newline {
12622 text.push('\n');
12623 len += 1;
12624 }
12625 clipboard_selections.push(ClipboardSelection {
12626 len,
12627 is_entire_line,
12628 first_line_indent: buffer
12629 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12630 .len,
12631 });
12632 }
12633 }
12634 }
12635
12636 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12637 text,
12638 clipboard_selections,
12639 ));
12640 }
12641
12642 pub fn do_paste(
12643 &mut self,
12644 text: &String,
12645 clipboard_selections: Option<Vec<ClipboardSelection>>,
12646 handle_entire_lines: bool,
12647 window: &mut Window,
12648 cx: &mut Context<Self>,
12649 ) {
12650 if self.read_only(cx) {
12651 return;
12652 }
12653
12654 let clipboard_text = Cow::Borrowed(text.as_str());
12655
12656 self.transact(window, cx, |this, window, cx| {
12657 let had_active_edit_prediction = this.has_active_edit_prediction();
12658 let old_selections = this.selections.all::<usize>(cx);
12659 let cursor_offset = this.selections.last::<usize>(cx).head();
12660
12661 if let Some(mut clipboard_selections) = clipboard_selections {
12662 let all_selections_were_entire_line =
12663 clipboard_selections.iter().all(|s| s.is_entire_line);
12664 let first_selection_indent_column =
12665 clipboard_selections.first().map(|s| s.first_line_indent);
12666 if clipboard_selections.len() != old_selections.len() {
12667 clipboard_selections.drain(..);
12668 }
12669 let mut auto_indent_on_paste = true;
12670
12671 this.buffer.update(cx, |buffer, cx| {
12672 let snapshot = buffer.read(cx);
12673 auto_indent_on_paste = snapshot
12674 .language_settings_at(cursor_offset, cx)
12675 .auto_indent_on_paste;
12676
12677 let mut start_offset = 0;
12678 let mut edits = Vec::new();
12679 let mut original_indent_columns = Vec::new();
12680 for (ix, selection) in old_selections.iter().enumerate() {
12681 let to_insert;
12682 let entire_line;
12683 let original_indent_column;
12684 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12685 let end_offset = start_offset + clipboard_selection.len;
12686 to_insert = &clipboard_text[start_offset..end_offset];
12687 entire_line = clipboard_selection.is_entire_line;
12688 start_offset = end_offset + 1;
12689 original_indent_column = Some(clipboard_selection.first_line_indent);
12690 } else {
12691 to_insert = &*clipboard_text;
12692 entire_line = all_selections_were_entire_line;
12693 original_indent_column = first_selection_indent_column
12694 }
12695
12696 let (range, to_insert) =
12697 if selection.is_empty() && handle_entire_lines && entire_line {
12698 // If the corresponding selection was empty when this slice of the
12699 // clipboard text was written, then the entire line containing the
12700 // selection was copied. If this selection is also currently empty,
12701 // then paste the line before the current line of the buffer.
12702 let column = selection.start.to_point(&snapshot).column as usize;
12703 let line_start = selection.start - column;
12704 (line_start..line_start, Cow::Borrowed(to_insert))
12705 } else {
12706 let language = snapshot.language_at(selection.head());
12707 let range = selection.range();
12708 if let Some(language) = language
12709 && language.name() == "Markdown".into()
12710 {
12711 edit_for_markdown_paste(
12712 &snapshot,
12713 range,
12714 to_insert,
12715 url::Url::parse(to_insert).ok(),
12716 )
12717 } else {
12718 (range, Cow::Borrowed(to_insert))
12719 }
12720 };
12721
12722 edits.push((range, to_insert));
12723 original_indent_columns.push(original_indent_column);
12724 }
12725 drop(snapshot);
12726
12727 buffer.edit(
12728 edits,
12729 if auto_indent_on_paste {
12730 Some(AutoindentMode::Block {
12731 original_indent_columns,
12732 })
12733 } else {
12734 None
12735 },
12736 cx,
12737 );
12738 });
12739
12740 let selections = this.selections.all::<usize>(cx);
12741 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12742 } else {
12743 let url = url::Url::parse(&clipboard_text).ok();
12744
12745 let auto_indent_mode = if !clipboard_text.is_empty() {
12746 Some(AutoindentMode::Block {
12747 original_indent_columns: Vec::new(),
12748 })
12749 } else {
12750 None
12751 };
12752
12753 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12754 let snapshot = buffer.snapshot(cx);
12755
12756 let anchors = old_selections
12757 .iter()
12758 .map(|s| {
12759 let anchor = snapshot.anchor_after(s.head());
12760 s.map(|_| anchor)
12761 })
12762 .collect::<Vec<_>>();
12763
12764 let mut edits = Vec::new();
12765
12766 for selection in old_selections.iter() {
12767 let language = snapshot.language_at(selection.head());
12768 let range = selection.range();
12769
12770 let (edit_range, edit_text) = if let Some(language) = language
12771 && language.name() == "Markdown".into()
12772 {
12773 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12774 } else {
12775 (range, clipboard_text.clone())
12776 };
12777
12778 edits.push((edit_range, edit_text));
12779 }
12780
12781 drop(snapshot);
12782 buffer.edit(edits, auto_indent_mode, cx);
12783
12784 anchors
12785 });
12786
12787 this.change_selections(Default::default(), window, cx, |s| {
12788 s.select_anchors(selection_anchors);
12789 });
12790 }
12791
12792 let trigger_in_words =
12793 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12794
12795 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12796 });
12797 }
12798
12799 pub fn diff_clipboard_with_selection(
12800 &mut self,
12801 _: &DiffClipboardWithSelection,
12802 window: &mut Window,
12803 cx: &mut Context<Self>,
12804 ) {
12805 let selections = self.selections.all::<usize>(cx);
12806
12807 if selections.is_empty() {
12808 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12809 return;
12810 };
12811
12812 let clipboard_text = match cx.read_from_clipboard() {
12813 Some(item) => match item.entries().first() {
12814 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12815 _ => None,
12816 },
12817 None => None,
12818 };
12819
12820 let Some(clipboard_text) = clipboard_text else {
12821 log::warn!("Clipboard doesn't contain text.");
12822 return;
12823 };
12824
12825 window.dispatch_action(
12826 Box::new(DiffClipboardWithSelectionData {
12827 clipboard_text,
12828 editor: cx.entity(),
12829 }),
12830 cx,
12831 );
12832 }
12833
12834 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12835 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12836 if let Some(item) = cx.read_from_clipboard() {
12837 let entries = item.entries();
12838
12839 match entries.first() {
12840 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12841 // of all the pasted entries.
12842 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12843 .do_paste(
12844 clipboard_string.text(),
12845 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12846 true,
12847 window,
12848 cx,
12849 ),
12850 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12851 }
12852 }
12853 }
12854
12855 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12856 if self.read_only(cx) {
12857 return;
12858 }
12859
12860 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12861
12862 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12863 if let Some((selections, _)) =
12864 self.selection_history.transaction(transaction_id).cloned()
12865 {
12866 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12867 s.select_anchors(selections.to_vec());
12868 });
12869 } else {
12870 log::error!(
12871 "No entry in selection_history found for undo. \
12872 This may correspond to a bug where undo does not update the selection. \
12873 If this is occurring, please add details to \
12874 https://github.com/zed-industries/zed/issues/22692"
12875 );
12876 }
12877 self.request_autoscroll(Autoscroll::fit(), cx);
12878 self.unmark_text(window, cx);
12879 self.refresh_edit_prediction(true, false, window, cx);
12880 cx.emit(EditorEvent::Edited { transaction_id });
12881 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12882 }
12883 }
12884
12885 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12886 if self.read_only(cx) {
12887 return;
12888 }
12889
12890 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12891
12892 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12893 if let Some((_, Some(selections))) =
12894 self.selection_history.transaction(transaction_id).cloned()
12895 {
12896 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12897 s.select_anchors(selections.to_vec());
12898 });
12899 } else {
12900 log::error!(
12901 "No entry in selection_history found for redo. \
12902 This may correspond to a bug where undo does not update the selection. \
12903 If this is occurring, please add details to \
12904 https://github.com/zed-industries/zed/issues/22692"
12905 );
12906 }
12907 self.request_autoscroll(Autoscroll::fit(), cx);
12908 self.unmark_text(window, cx);
12909 self.refresh_edit_prediction(true, false, window, cx);
12910 cx.emit(EditorEvent::Edited { transaction_id });
12911 }
12912 }
12913
12914 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12915 self.buffer
12916 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12917 }
12918
12919 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12920 self.buffer
12921 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12922 }
12923
12924 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12926 self.change_selections(Default::default(), window, cx, |s| {
12927 s.move_with(|map, selection| {
12928 let cursor = if selection.is_empty() {
12929 movement::left(map, selection.start)
12930 } else {
12931 selection.start
12932 };
12933 selection.collapse_to(cursor, SelectionGoal::None);
12934 });
12935 })
12936 }
12937
12938 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12940 self.change_selections(Default::default(), window, cx, |s| {
12941 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12942 })
12943 }
12944
12945 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12947 self.change_selections(Default::default(), window, cx, |s| {
12948 s.move_with(|map, selection| {
12949 let cursor = if selection.is_empty() {
12950 movement::right(map, selection.end)
12951 } else {
12952 selection.end
12953 };
12954 selection.collapse_to(cursor, SelectionGoal::None)
12955 });
12956 })
12957 }
12958
12959 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12961 self.change_selections(Default::default(), window, cx, |s| {
12962 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12963 });
12964 }
12965
12966 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12967 if self.take_rename(true, window, cx).is_some() {
12968 return;
12969 }
12970
12971 if self.mode.is_single_line() {
12972 cx.propagate();
12973 return;
12974 }
12975
12976 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12977
12978 let text_layout_details = &self.text_layout_details(window);
12979 let selection_count = self.selections.count();
12980 let first_selection = self.selections.first_anchor();
12981
12982 self.change_selections(Default::default(), window, cx, |s| {
12983 s.move_with(|map, selection| {
12984 if !selection.is_empty() {
12985 selection.goal = SelectionGoal::None;
12986 }
12987 let (cursor, goal) = movement::up(
12988 map,
12989 selection.start,
12990 selection.goal,
12991 false,
12992 text_layout_details,
12993 );
12994 selection.collapse_to(cursor, goal);
12995 });
12996 });
12997
12998 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12999 {
13000 cx.propagate();
13001 }
13002 }
13003
13004 pub fn move_up_by_lines(
13005 &mut self,
13006 action: &MoveUpByLines,
13007 window: &mut Window,
13008 cx: &mut Context<Self>,
13009 ) {
13010 if self.take_rename(true, window, cx).is_some() {
13011 return;
13012 }
13013
13014 if self.mode.is_single_line() {
13015 cx.propagate();
13016 return;
13017 }
13018
13019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13020
13021 let text_layout_details = &self.text_layout_details(window);
13022
13023 self.change_selections(Default::default(), window, cx, |s| {
13024 s.move_with(|map, selection| {
13025 if !selection.is_empty() {
13026 selection.goal = SelectionGoal::None;
13027 }
13028 let (cursor, goal) = movement::up_by_rows(
13029 map,
13030 selection.start,
13031 action.lines,
13032 selection.goal,
13033 false,
13034 text_layout_details,
13035 );
13036 selection.collapse_to(cursor, goal);
13037 });
13038 })
13039 }
13040
13041 pub fn move_down_by_lines(
13042 &mut self,
13043 action: &MoveDownByLines,
13044 window: &mut Window,
13045 cx: &mut Context<Self>,
13046 ) {
13047 if self.take_rename(true, window, cx).is_some() {
13048 return;
13049 }
13050
13051 if self.mode.is_single_line() {
13052 cx.propagate();
13053 return;
13054 }
13055
13056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13057
13058 let text_layout_details = &self.text_layout_details(window);
13059
13060 self.change_selections(Default::default(), window, cx, |s| {
13061 s.move_with(|map, selection| {
13062 if !selection.is_empty() {
13063 selection.goal = SelectionGoal::None;
13064 }
13065 let (cursor, goal) = movement::down_by_rows(
13066 map,
13067 selection.start,
13068 action.lines,
13069 selection.goal,
13070 false,
13071 text_layout_details,
13072 );
13073 selection.collapse_to(cursor, goal);
13074 });
13075 })
13076 }
13077
13078 pub fn select_down_by_lines(
13079 &mut self,
13080 action: &SelectDownByLines,
13081 window: &mut Window,
13082 cx: &mut Context<Self>,
13083 ) {
13084 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13085 let text_layout_details = &self.text_layout_details(window);
13086 self.change_selections(Default::default(), window, cx, |s| {
13087 s.move_heads_with(|map, head, goal| {
13088 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13089 })
13090 })
13091 }
13092
13093 pub fn select_up_by_lines(
13094 &mut self,
13095 action: &SelectUpByLines,
13096 window: &mut Window,
13097 cx: &mut Context<Self>,
13098 ) {
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13100 let text_layout_details = &self.text_layout_details(window);
13101 self.change_selections(Default::default(), window, cx, |s| {
13102 s.move_heads_with(|map, head, goal| {
13103 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13104 })
13105 })
13106 }
13107
13108 pub fn select_page_up(
13109 &mut self,
13110 _: &SelectPageUp,
13111 window: &mut Window,
13112 cx: &mut Context<Self>,
13113 ) {
13114 let Some(row_count) = self.visible_row_count() else {
13115 return;
13116 };
13117
13118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13119
13120 let text_layout_details = &self.text_layout_details(window);
13121
13122 self.change_selections(Default::default(), window, cx, |s| {
13123 s.move_heads_with(|map, head, goal| {
13124 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13125 })
13126 })
13127 }
13128
13129 pub fn move_page_up(
13130 &mut self,
13131 action: &MovePageUp,
13132 window: &mut Window,
13133 cx: &mut Context<Self>,
13134 ) {
13135 if self.take_rename(true, window, cx).is_some() {
13136 return;
13137 }
13138
13139 if self
13140 .context_menu
13141 .borrow_mut()
13142 .as_mut()
13143 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13144 .unwrap_or(false)
13145 {
13146 return;
13147 }
13148
13149 if matches!(self.mode, EditorMode::SingleLine) {
13150 cx.propagate();
13151 return;
13152 }
13153
13154 let Some(row_count) = self.visible_row_count() else {
13155 return;
13156 };
13157
13158 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13159
13160 let effects = if action.center_cursor {
13161 SelectionEffects::scroll(Autoscroll::center())
13162 } else {
13163 SelectionEffects::default()
13164 };
13165
13166 let text_layout_details = &self.text_layout_details(window);
13167
13168 self.change_selections(effects, window, cx, |s| {
13169 s.move_with(|map, selection| {
13170 if !selection.is_empty() {
13171 selection.goal = SelectionGoal::None;
13172 }
13173 let (cursor, goal) = movement::up_by_rows(
13174 map,
13175 selection.end,
13176 row_count,
13177 selection.goal,
13178 false,
13179 text_layout_details,
13180 );
13181 selection.collapse_to(cursor, goal);
13182 });
13183 });
13184 }
13185
13186 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13188 let text_layout_details = &self.text_layout_details(window);
13189 self.change_selections(Default::default(), window, cx, |s| {
13190 s.move_heads_with(|map, head, goal| {
13191 movement::up(map, head, goal, false, text_layout_details)
13192 })
13193 })
13194 }
13195
13196 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13197 self.take_rename(true, window, cx);
13198
13199 if self.mode.is_single_line() {
13200 cx.propagate();
13201 return;
13202 }
13203
13204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13205
13206 let text_layout_details = &self.text_layout_details(window);
13207 let selection_count = self.selections.count();
13208 let first_selection = self.selections.first_anchor();
13209
13210 self.change_selections(Default::default(), window, cx, |s| {
13211 s.move_with(|map, selection| {
13212 if !selection.is_empty() {
13213 selection.goal = SelectionGoal::None;
13214 }
13215 let (cursor, goal) = movement::down(
13216 map,
13217 selection.end,
13218 selection.goal,
13219 false,
13220 text_layout_details,
13221 );
13222 selection.collapse_to(cursor, goal);
13223 });
13224 });
13225
13226 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13227 {
13228 cx.propagate();
13229 }
13230 }
13231
13232 pub fn select_page_down(
13233 &mut self,
13234 _: &SelectPageDown,
13235 window: &mut Window,
13236 cx: &mut Context<Self>,
13237 ) {
13238 let Some(row_count) = self.visible_row_count() else {
13239 return;
13240 };
13241
13242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13243
13244 let text_layout_details = &self.text_layout_details(window);
13245
13246 self.change_selections(Default::default(), window, cx, |s| {
13247 s.move_heads_with(|map, head, goal| {
13248 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13249 })
13250 })
13251 }
13252
13253 pub fn move_page_down(
13254 &mut self,
13255 action: &MovePageDown,
13256 window: &mut Window,
13257 cx: &mut Context<Self>,
13258 ) {
13259 if self.take_rename(true, window, cx).is_some() {
13260 return;
13261 }
13262
13263 if self
13264 .context_menu
13265 .borrow_mut()
13266 .as_mut()
13267 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13268 .unwrap_or(false)
13269 {
13270 return;
13271 }
13272
13273 if matches!(self.mode, EditorMode::SingleLine) {
13274 cx.propagate();
13275 return;
13276 }
13277
13278 let Some(row_count) = self.visible_row_count() else {
13279 return;
13280 };
13281
13282 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13283
13284 let effects = if action.center_cursor {
13285 SelectionEffects::scroll(Autoscroll::center())
13286 } else {
13287 SelectionEffects::default()
13288 };
13289
13290 let text_layout_details = &self.text_layout_details(window);
13291 self.change_selections(effects, window, cx, |s| {
13292 s.move_with(|map, selection| {
13293 if !selection.is_empty() {
13294 selection.goal = SelectionGoal::None;
13295 }
13296 let (cursor, goal) = movement::down_by_rows(
13297 map,
13298 selection.end,
13299 row_count,
13300 selection.goal,
13301 false,
13302 text_layout_details,
13303 );
13304 selection.collapse_to(cursor, goal);
13305 });
13306 });
13307 }
13308
13309 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13310 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13311 let text_layout_details = &self.text_layout_details(window);
13312 self.change_selections(Default::default(), window, cx, |s| {
13313 s.move_heads_with(|map, head, goal| {
13314 movement::down(map, head, goal, false, text_layout_details)
13315 })
13316 });
13317 }
13318
13319 pub fn context_menu_first(
13320 &mut self,
13321 _: &ContextMenuFirst,
13322 window: &mut Window,
13323 cx: &mut Context<Self>,
13324 ) {
13325 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13326 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13327 }
13328 }
13329
13330 pub fn context_menu_prev(
13331 &mut self,
13332 _: &ContextMenuPrevious,
13333 window: &mut Window,
13334 cx: &mut Context<Self>,
13335 ) {
13336 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13337 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13338 }
13339 }
13340
13341 pub fn context_menu_next(
13342 &mut self,
13343 _: &ContextMenuNext,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) {
13347 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13348 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13349 }
13350 }
13351
13352 pub fn context_menu_last(
13353 &mut self,
13354 _: &ContextMenuLast,
13355 window: &mut Window,
13356 cx: &mut Context<Self>,
13357 ) {
13358 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13359 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13360 }
13361 }
13362
13363 pub fn signature_help_prev(
13364 &mut self,
13365 _: &SignatureHelpPrevious,
13366 _: &mut Window,
13367 cx: &mut Context<Self>,
13368 ) {
13369 if let Some(popover) = self.signature_help_state.popover_mut() {
13370 if popover.current_signature == 0 {
13371 popover.current_signature = popover.signatures.len() - 1;
13372 } else {
13373 popover.current_signature -= 1;
13374 }
13375 cx.notify();
13376 }
13377 }
13378
13379 pub fn signature_help_next(
13380 &mut self,
13381 _: &SignatureHelpNext,
13382 _: &mut Window,
13383 cx: &mut Context<Self>,
13384 ) {
13385 if let Some(popover) = self.signature_help_state.popover_mut() {
13386 if popover.current_signature + 1 == popover.signatures.len() {
13387 popover.current_signature = 0;
13388 } else {
13389 popover.current_signature += 1;
13390 }
13391 cx.notify();
13392 }
13393 }
13394
13395 pub fn move_to_previous_word_start(
13396 &mut self,
13397 _: &MoveToPreviousWordStart,
13398 window: &mut Window,
13399 cx: &mut Context<Self>,
13400 ) {
13401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13402 self.change_selections(Default::default(), window, cx, |s| {
13403 s.move_cursors_with(|map, head, _| {
13404 (
13405 movement::previous_word_start(map, head),
13406 SelectionGoal::None,
13407 )
13408 });
13409 })
13410 }
13411
13412 pub fn move_to_previous_subword_start(
13413 &mut self,
13414 _: &MoveToPreviousSubwordStart,
13415 window: &mut Window,
13416 cx: &mut Context<Self>,
13417 ) {
13418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13419 self.change_selections(Default::default(), window, cx, |s| {
13420 s.move_cursors_with(|map, head, _| {
13421 (
13422 movement::previous_subword_start(map, head),
13423 SelectionGoal::None,
13424 )
13425 });
13426 })
13427 }
13428
13429 pub fn select_to_previous_word_start(
13430 &mut self,
13431 _: &SelectToPreviousWordStart,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436 self.change_selections(Default::default(), window, cx, |s| {
13437 s.move_heads_with(|map, head, _| {
13438 (
13439 movement::previous_word_start(map, head),
13440 SelectionGoal::None,
13441 )
13442 });
13443 })
13444 }
13445
13446 pub fn select_to_previous_subword_start(
13447 &mut self,
13448 _: &SelectToPreviousSubwordStart,
13449 window: &mut Window,
13450 cx: &mut Context<Self>,
13451 ) {
13452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13453 self.change_selections(Default::default(), window, cx, |s| {
13454 s.move_heads_with(|map, head, _| {
13455 (
13456 movement::previous_subword_start(map, head),
13457 SelectionGoal::None,
13458 )
13459 });
13460 })
13461 }
13462
13463 pub fn delete_to_previous_word_start(
13464 &mut self,
13465 action: &DeleteToPreviousWordStart,
13466 window: &mut Window,
13467 cx: &mut Context<Self>,
13468 ) {
13469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13470 self.transact(window, cx, |this, window, cx| {
13471 this.select_autoclose_pair(window, cx);
13472 this.change_selections(Default::default(), window, cx, |s| {
13473 s.move_with(|map, selection| {
13474 if selection.is_empty() {
13475 let mut cursor = if action.ignore_newlines {
13476 movement::previous_word_start(map, selection.head())
13477 } else {
13478 movement::previous_word_start_or_newline(map, selection.head())
13479 };
13480 cursor = movement::adjust_greedy_deletion(
13481 map,
13482 selection.head(),
13483 cursor,
13484 action.ignore_brackets,
13485 );
13486 selection.set_head(cursor, SelectionGoal::None);
13487 }
13488 });
13489 });
13490 this.insert("", window, cx);
13491 });
13492 }
13493
13494 pub fn delete_to_previous_subword_start(
13495 &mut self,
13496 _: &DeleteToPreviousSubwordStart,
13497 window: &mut Window,
13498 cx: &mut Context<Self>,
13499 ) {
13500 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13501 self.transact(window, cx, |this, window, cx| {
13502 this.select_autoclose_pair(window, cx);
13503 this.change_selections(Default::default(), window, cx, |s| {
13504 s.move_with(|map, selection| {
13505 if selection.is_empty() {
13506 let mut cursor = movement::previous_subword_start(map, selection.head());
13507 cursor =
13508 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13509 selection.set_head(cursor, SelectionGoal::None);
13510 }
13511 });
13512 });
13513 this.insert("", window, cx);
13514 });
13515 }
13516
13517 pub fn move_to_next_word_end(
13518 &mut self,
13519 _: &MoveToNextWordEnd,
13520 window: &mut Window,
13521 cx: &mut Context<Self>,
13522 ) {
13523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13524 self.change_selections(Default::default(), window, cx, |s| {
13525 s.move_cursors_with(|map, head, _| {
13526 (movement::next_word_end(map, head), SelectionGoal::None)
13527 });
13528 })
13529 }
13530
13531 pub fn move_to_next_subword_end(
13532 &mut self,
13533 _: &MoveToNextSubwordEnd,
13534 window: &mut Window,
13535 cx: &mut Context<Self>,
13536 ) {
13537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13538 self.change_selections(Default::default(), window, cx, |s| {
13539 s.move_cursors_with(|map, head, _| {
13540 (movement::next_subword_end(map, head), SelectionGoal::None)
13541 });
13542 })
13543 }
13544
13545 pub fn select_to_next_word_end(
13546 &mut self,
13547 _: &SelectToNextWordEnd,
13548 window: &mut Window,
13549 cx: &mut Context<Self>,
13550 ) {
13551 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13552 self.change_selections(Default::default(), window, cx, |s| {
13553 s.move_heads_with(|map, head, _| {
13554 (movement::next_word_end(map, head), SelectionGoal::None)
13555 });
13556 })
13557 }
13558
13559 pub fn select_to_next_subword_end(
13560 &mut self,
13561 _: &SelectToNextSubwordEnd,
13562 window: &mut Window,
13563 cx: &mut Context<Self>,
13564 ) {
13565 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13566 self.change_selections(Default::default(), window, cx, |s| {
13567 s.move_heads_with(|map, head, _| {
13568 (movement::next_subword_end(map, head), SelectionGoal::None)
13569 });
13570 })
13571 }
13572
13573 pub fn delete_to_next_word_end(
13574 &mut self,
13575 action: &DeleteToNextWordEnd,
13576 window: &mut Window,
13577 cx: &mut Context<Self>,
13578 ) {
13579 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13580 self.transact(window, cx, |this, window, cx| {
13581 this.change_selections(Default::default(), window, cx, |s| {
13582 s.move_with(|map, selection| {
13583 if selection.is_empty() {
13584 let mut cursor = if action.ignore_newlines {
13585 movement::next_word_end(map, selection.head())
13586 } else {
13587 movement::next_word_end_or_newline(map, selection.head())
13588 };
13589 cursor = movement::adjust_greedy_deletion(
13590 map,
13591 selection.head(),
13592 cursor,
13593 action.ignore_brackets,
13594 );
13595 selection.set_head(cursor, SelectionGoal::None);
13596 }
13597 });
13598 });
13599 this.insert("", window, cx);
13600 });
13601 }
13602
13603 pub fn delete_to_next_subword_end(
13604 &mut self,
13605 _: &DeleteToNextSubwordEnd,
13606 window: &mut Window,
13607 cx: &mut Context<Self>,
13608 ) {
13609 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13610 self.transact(window, cx, |this, window, cx| {
13611 this.change_selections(Default::default(), window, cx, |s| {
13612 s.move_with(|map, selection| {
13613 if selection.is_empty() {
13614 let mut cursor = movement::next_subword_end(map, selection.head());
13615 cursor =
13616 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13617 selection.set_head(cursor, SelectionGoal::None);
13618 }
13619 });
13620 });
13621 this.insert("", window, cx);
13622 });
13623 }
13624
13625 pub fn move_to_beginning_of_line(
13626 &mut self,
13627 action: &MoveToBeginningOfLine,
13628 window: &mut Window,
13629 cx: &mut Context<Self>,
13630 ) {
13631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13632 self.change_selections(Default::default(), window, cx, |s| {
13633 s.move_cursors_with(|map, head, _| {
13634 (
13635 movement::indented_line_beginning(
13636 map,
13637 head,
13638 action.stop_at_soft_wraps,
13639 action.stop_at_indent,
13640 ),
13641 SelectionGoal::None,
13642 )
13643 });
13644 })
13645 }
13646
13647 pub fn select_to_beginning_of_line(
13648 &mut self,
13649 action: &SelectToBeginningOfLine,
13650 window: &mut Window,
13651 cx: &mut Context<Self>,
13652 ) {
13653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13654 self.change_selections(Default::default(), window, cx, |s| {
13655 s.move_heads_with(|map, head, _| {
13656 (
13657 movement::indented_line_beginning(
13658 map,
13659 head,
13660 action.stop_at_soft_wraps,
13661 action.stop_at_indent,
13662 ),
13663 SelectionGoal::None,
13664 )
13665 });
13666 });
13667 }
13668
13669 pub fn delete_to_beginning_of_line(
13670 &mut self,
13671 action: &DeleteToBeginningOfLine,
13672 window: &mut Window,
13673 cx: &mut Context<Self>,
13674 ) {
13675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13676 self.transact(window, cx, |this, window, cx| {
13677 this.change_selections(Default::default(), window, cx, |s| {
13678 s.move_with(|_, selection| {
13679 selection.reversed = true;
13680 });
13681 });
13682
13683 this.select_to_beginning_of_line(
13684 &SelectToBeginningOfLine {
13685 stop_at_soft_wraps: false,
13686 stop_at_indent: action.stop_at_indent,
13687 },
13688 window,
13689 cx,
13690 );
13691 this.backspace(&Backspace, window, cx);
13692 });
13693 }
13694
13695 pub fn move_to_end_of_line(
13696 &mut self,
13697 action: &MoveToEndOfLine,
13698 window: &mut Window,
13699 cx: &mut Context<Self>,
13700 ) {
13701 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13702 self.change_selections(Default::default(), window, cx, |s| {
13703 s.move_cursors_with(|map, head, _| {
13704 (
13705 movement::line_end(map, head, action.stop_at_soft_wraps),
13706 SelectionGoal::None,
13707 )
13708 });
13709 })
13710 }
13711
13712 pub fn select_to_end_of_line(
13713 &mut self,
13714 action: &SelectToEndOfLine,
13715 window: &mut Window,
13716 cx: &mut Context<Self>,
13717 ) {
13718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13719 self.change_selections(Default::default(), window, cx, |s| {
13720 s.move_heads_with(|map, head, _| {
13721 (
13722 movement::line_end(map, head, action.stop_at_soft_wraps),
13723 SelectionGoal::None,
13724 )
13725 });
13726 })
13727 }
13728
13729 pub fn delete_to_end_of_line(
13730 &mut self,
13731 _: &DeleteToEndOfLine,
13732 window: &mut Window,
13733 cx: &mut Context<Self>,
13734 ) {
13735 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13736 self.transact(window, cx, |this, window, cx| {
13737 this.select_to_end_of_line(
13738 &SelectToEndOfLine {
13739 stop_at_soft_wraps: false,
13740 },
13741 window,
13742 cx,
13743 );
13744 this.delete(&Delete, window, cx);
13745 });
13746 }
13747
13748 pub fn cut_to_end_of_line(
13749 &mut self,
13750 action: &CutToEndOfLine,
13751 window: &mut Window,
13752 cx: &mut Context<Self>,
13753 ) {
13754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13755 self.transact(window, cx, |this, window, cx| {
13756 this.select_to_end_of_line(
13757 &SelectToEndOfLine {
13758 stop_at_soft_wraps: false,
13759 },
13760 window,
13761 cx,
13762 );
13763 if !action.stop_at_newlines {
13764 this.change_selections(Default::default(), window, cx, |s| {
13765 s.move_with(|_, sel| {
13766 if sel.is_empty() {
13767 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13768 }
13769 });
13770 });
13771 }
13772 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13773 let item = this.cut_common(false, window, cx);
13774 cx.write_to_clipboard(item);
13775 });
13776 }
13777
13778 pub fn move_to_start_of_paragraph(
13779 &mut self,
13780 _: &MoveToStartOfParagraph,
13781 window: &mut Window,
13782 cx: &mut Context<Self>,
13783 ) {
13784 if matches!(self.mode, EditorMode::SingleLine) {
13785 cx.propagate();
13786 return;
13787 }
13788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13789 self.change_selections(Default::default(), window, cx, |s| {
13790 s.move_with(|map, selection| {
13791 selection.collapse_to(
13792 movement::start_of_paragraph(map, selection.head(), 1),
13793 SelectionGoal::None,
13794 )
13795 });
13796 })
13797 }
13798
13799 pub fn move_to_end_of_paragraph(
13800 &mut self,
13801 _: &MoveToEndOfParagraph,
13802 window: &mut Window,
13803 cx: &mut Context<Self>,
13804 ) {
13805 if matches!(self.mode, EditorMode::SingleLine) {
13806 cx.propagate();
13807 return;
13808 }
13809 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13810 self.change_selections(Default::default(), window, cx, |s| {
13811 s.move_with(|map, selection| {
13812 selection.collapse_to(
13813 movement::end_of_paragraph(map, selection.head(), 1),
13814 SelectionGoal::None,
13815 )
13816 });
13817 })
13818 }
13819
13820 pub fn select_to_start_of_paragraph(
13821 &mut self,
13822 _: &SelectToStartOfParagraph,
13823 window: &mut Window,
13824 cx: &mut Context<Self>,
13825 ) {
13826 if matches!(self.mode, EditorMode::SingleLine) {
13827 cx.propagate();
13828 return;
13829 }
13830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13831 self.change_selections(Default::default(), window, cx, |s| {
13832 s.move_heads_with(|map, head, _| {
13833 (
13834 movement::start_of_paragraph(map, head, 1),
13835 SelectionGoal::None,
13836 )
13837 });
13838 })
13839 }
13840
13841 pub fn select_to_end_of_paragraph(
13842 &mut self,
13843 _: &SelectToEndOfParagraph,
13844 window: &mut Window,
13845 cx: &mut Context<Self>,
13846 ) {
13847 if matches!(self.mode, EditorMode::SingleLine) {
13848 cx.propagate();
13849 return;
13850 }
13851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13852 self.change_selections(Default::default(), window, cx, |s| {
13853 s.move_heads_with(|map, head, _| {
13854 (
13855 movement::end_of_paragraph(map, head, 1),
13856 SelectionGoal::None,
13857 )
13858 });
13859 })
13860 }
13861
13862 pub fn move_to_start_of_excerpt(
13863 &mut self,
13864 _: &MoveToStartOfExcerpt,
13865 window: &mut Window,
13866 cx: &mut Context<Self>,
13867 ) {
13868 if matches!(self.mode, EditorMode::SingleLine) {
13869 cx.propagate();
13870 return;
13871 }
13872 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13873 self.change_selections(Default::default(), window, cx, |s| {
13874 s.move_with(|map, selection| {
13875 selection.collapse_to(
13876 movement::start_of_excerpt(
13877 map,
13878 selection.head(),
13879 workspace::searchable::Direction::Prev,
13880 ),
13881 SelectionGoal::None,
13882 )
13883 });
13884 })
13885 }
13886
13887 pub fn move_to_start_of_next_excerpt(
13888 &mut self,
13889 _: &MoveToStartOfNextExcerpt,
13890 window: &mut Window,
13891 cx: &mut Context<Self>,
13892 ) {
13893 if matches!(self.mode, EditorMode::SingleLine) {
13894 cx.propagate();
13895 return;
13896 }
13897
13898 self.change_selections(Default::default(), window, cx, |s| {
13899 s.move_with(|map, selection| {
13900 selection.collapse_to(
13901 movement::start_of_excerpt(
13902 map,
13903 selection.head(),
13904 workspace::searchable::Direction::Next,
13905 ),
13906 SelectionGoal::None,
13907 )
13908 });
13909 })
13910 }
13911
13912 pub fn move_to_end_of_excerpt(
13913 &mut self,
13914 _: &MoveToEndOfExcerpt,
13915 window: &mut Window,
13916 cx: &mut Context<Self>,
13917 ) {
13918 if matches!(self.mode, EditorMode::SingleLine) {
13919 cx.propagate();
13920 return;
13921 }
13922 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13923 self.change_selections(Default::default(), window, cx, |s| {
13924 s.move_with(|map, selection| {
13925 selection.collapse_to(
13926 movement::end_of_excerpt(
13927 map,
13928 selection.head(),
13929 workspace::searchable::Direction::Next,
13930 ),
13931 SelectionGoal::None,
13932 )
13933 });
13934 })
13935 }
13936
13937 pub fn move_to_end_of_previous_excerpt(
13938 &mut self,
13939 _: &MoveToEndOfPreviousExcerpt,
13940 window: &mut Window,
13941 cx: &mut Context<Self>,
13942 ) {
13943 if matches!(self.mode, EditorMode::SingleLine) {
13944 cx.propagate();
13945 return;
13946 }
13947 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13948 self.change_selections(Default::default(), window, cx, |s| {
13949 s.move_with(|map, selection| {
13950 selection.collapse_to(
13951 movement::end_of_excerpt(
13952 map,
13953 selection.head(),
13954 workspace::searchable::Direction::Prev,
13955 ),
13956 SelectionGoal::None,
13957 )
13958 });
13959 })
13960 }
13961
13962 pub fn select_to_start_of_excerpt(
13963 &mut self,
13964 _: &SelectToStartOfExcerpt,
13965 window: &mut Window,
13966 cx: &mut Context<Self>,
13967 ) {
13968 if matches!(self.mode, EditorMode::SingleLine) {
13969 cx.propagate();
13970 return;
13971 }
13972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13973 self.change_selections(Default::default(), window, cx, |s| {
13974 s.move_heads_with(|map, head, _| {
13975 (
13976 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13977 SelectionGoal::None,
13978 )
13979 });
13980 })
13981 }
13982
13983 pub fn select_to_start_of_next_excerpt(
13984 &mut self,
13985 _: &SelectToStartOfNextExcerpt,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) {
13989 if matches!(self.mode, EditorMode::SingleLine) {
13990 cx.propagate();
13991 return;
13992 }
13993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13994 self.change_selections(Default::default(), window, cx, |s| {
13995 s.move_heads_with(|map, head, _| {
13996 (
13997 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13998 SelectionGoal::None,
13999 )
14000 });
14001 })
14002 }
14003
14004 pub fn select_to_end_of_excerpt(
14005 &mut self,
14006 _: &SelectToEndOfExcerpt,
14007 window: &mut Window,
14008 cx: &mut Context<Self>,
14009 ) {
14010 if matches!(self.mode, EditorMode::SingleLine) {
14011 cx.propagate();
14012 return;
14013 }
14014 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14015 self.change_selections(Default::default(), window, cx, |s| {
14016 s.move_heads_with(|map, head, _| {
14017 (
14018 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14019 SelectionGoal::None,
14020 )
14021 });
14022 })
14023 }
14024
14025 pub fn select_to_end_of_previous_excerpt(
14026 &mut self,
14027 _: &SelectToEndOfPreviousExcerpt,
14028 window: &mut Window,
14029 cx: &mut Context<Self>,
14030 ) {
14031 if matches!(self.mode, EditorMode::SingleLine) {
14032 cx.propagate();
14033 return;
14034 }
14035 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14036 self.change_selections(Default::default(), window, cx, |s| {
14037 s.move_heads_with(|map, head, _| {
14038 (
14039 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14040 SelectionGoal::None,
14041 )
14042 });
14043 })
14044 }
14045
14046 pub fn move_to_beginning(
14047 &mut self,
14048 _: &MoveToBeginning,
14049 window: &mut Window,
14050 cx: &mut Context<Self>,
14051 ) {
14052 if matches!(self.mode, EditorMode::SingleLine) {
14053 cx.propagate();
14054 return;
14055 }
14056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14057 self.change_selections(Default::default(), window, cx, |s| {
14058 s.select_ranges(vec![0..0]);
14059 });
14060 }
14061
14062 pub fn select_to_beginning(
14063 &mut self,
14064 _: &SelectToBeginning,
14065 window: &mut Window,
14066 cx: &mut Context<Self>,
14067 ) {
14068 let mut selection = self.selections.last::<Point>(cx);
14069 selection.set_head(Point::zero(), SelectionGoal::None);
14070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14071 self.change_selections(Default::default(), window, cx, |s| {
14072 s.select(vec![selection]);
14073 });
14074 }
14075
14076 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14077 if matches!(self.mode, EditorMode::SingleLine) {
14078 cx.propagate();
14079 return;
14080 }
14081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14082 let cursor = self.buffer.read(cx).read(cx).len();
14083 self.change_selections(Default::default(), window, cx, |s| {
14084 s.select_ranges(vec![cursor..cursor])
14085 });
14086 }
14087
14088 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14089 self.nav_history = nav_history;
14090 }
14091
14092 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14093 self.nav_history.as_ref()
14094 }
14095
14096 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14097 self.push_to_nav_history(
14098 self.selections.newest_anchor().head(),
14099 None,
14100 false,
14101 true,
14102 cx,
14103 );
14104 }
14105
14106 fn push_to_nav_history(
14107 &mut self,
14108 cursor_anchor: Anchor,
14109 new_position: Option<Point>,
14110 is_deactivate: bool,
14111 always: bool,
14112 cx: &mut Context<Self>,
14113 ) {
14114 if let Some(nav_history) = self.nav_history.as_mut() {
14115 let buffer = self.buffer.read(cx).read(cx);
14116 let cursor_position = cursor_anchor.to_point(&buffer);
14117 let scroll_state = self.scroll_manager.anchor();
14118 let scroll_top_row = scroll_state.top_row(&buffer);
14119 drop(buffer);
14120
14121 if let Some(new_position) = new_position {
14122 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14123 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14124 return;
14125 }
14126 }
14127
14128 nav_history.push(
14129 Some(NavigationData {
14130 cursor_anchor,
14131 cursor_position,
14132 scroll_anchor: scroll_state,
14133 scroll_top_row,
14134 }),
14135 cx,
14136 );
14137 cx.emit(EditorEvent::PushedToNavHistory {
14138 anchor: cursor_anchor,
14139 is_deactivate,
14140 })
14141 }
14142 }
14143
14144 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14145 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14146 let buffer = self.buffer.read(cx).snapshot(cx);
14147 let mut selection = self.selections.first::<usize>(cx);
14148 selection.set_head(buffer.len(), SelectionGoal::None);
14149 self.change_selections(Default::default(), window, cx, |s| {
14150 s.select(vec![selection]);
14151 });
14152 }
14153
14154 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14156 let end = self.buffer.read(cx).read(cx).len();
14157 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14158 s.select_ranges(vec![0..end]);
14159 });
14160 }
14161
14162 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14163 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14164 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14165 let mut selections = self.selections.all::<Point>(cx);
14166 let max_point = display_map.buffer_snapshot().max_point();
14167 for selection in &mut selections {
14168 let rows = selection.spanned_rows(true, &display_map);
14169 selection.start = Point::new(rows.start.0, 0);
14170 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14171 selection.reversed = false;
14172 }
14173 self.change_selections(Default::default(), window, cx, |s| {
14174 s.select(selections);
14175 });
14176 }
14177
14178 pub fn split_selection_into_lines(
14179 &mut self,
14180 action: &SplitSelectionIntoLines,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) {
14184 let selections = self
14185 .selections
14186 .all::<Point>(cx)
14187 .into_iter()
14188 .map(|selection| selection.start..selection.end)
14189 .collect::<Vec<_>>();
14190 self.unfold_ranges(&selections, true, true, cx);
14191
14192 let mut new_selection_ranges = Vec::new();
14193 {
14194 let buffer = self.buffer.read(cx).read(cx);
14195 for selection in selections {
14196 for row in selection.start.row..selection.end.row {
14197 let line_start = Point::new(row, 0);
14198 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14199
14200 if action.keep_selections {
14201 // Keep the selection range for each line
14202 let selection_start = if row == selection.start.row {
14203 selection.start
14204 } else {
14205 line_start
14206 };
14207 new_selection_ranges.push(selection_start..line_end);
14208 } else {
14209 // Collapse to cursor at end of line
14210 new_selection_ranges.push(line_end..line_end);
14211 }
14212 }
14213
14214 let is_multiline_selection = selection.start.row != selection.end.row;
14215 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14216 // so this action feels more ergonomic when paired with other selection operations
14217 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14218 if !should_skip_last {
14219 if action.keep_selections {
14220 if is_multiline_selection {
14221 let line_start = Point::new(selection.end.row, 0);
14222 new_selection_ranges.push(line_start..selection.end);
14223 } else {
14224 new_selection_ranges.push(selection.start..selection.end);
14225 }
14226 } else {
14227 new_selection_ranges.push(selection.end..selection.end);
14228 }
14229 }
14230 }
14231 }
14232 self.change_selections(Default::default(), window, cx, |s| {
14233 s.select_ranges(new_selection_ranges);
14234 });
14235 }
14236
14237 pub fn add_selection_above(
14238 &mut self,
14239 _: &AddSelectionAbove,
14240 window: &mut Window,
14241 cx: &mut Context<Self>,
14242 ) {
14243 self.add_selection(true, window, cx);
14244 }
14245
14246 pub fn add_selection_below(
14247 &mut self,
14248 _: &AddSelectionBelow,
14249 window: &mut Window,
14250 cx: &mut Context<Self>,
14251 ) {
14252 self.add_selection(false, window, cx);
14253 }
14254
14255 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14257
14258 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14259 let all_selections = self.selections.all::<Point>(cx);
14260 let text_layout_details = self.text_layout_details(window);
14261
14262 let (mut columnar_selections, new_selections_to_columnarize) = {
14263 if let Some(state) = self.add_selections_state.as_ref() {
14264 let columnar_selection_ids: HashSet<_> = state
14265 .groups
14266 .iter()
14267 .flat_map(|group| group.stack.iter())
14268 .copied()
14269 .collect();
14270
14271 all_selections
14272 .into_iter()
14273 .partition(|s| columnar_selection_ids.contains(&s.id))
14274 } else {
14275 (Vec::new(), all_selections)
14276 }
14277 };
14278
14279 let mut state = self
14280 .add_selections_state
14281 .take()
14282 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14283
14284 for selection in new_selections_to_columnarize {
14285 let range = selection.display_range(&display_map).sorted();
14286 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14287 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14288 let positions = start_x.min(end_x)..start_x.max(end_x);
14289 let mut stack = Vec::new();
14290 for row in range.start.row().0..=range.end.row().0 {
14291 if let Some(selection) = self.selections.build_columnar_selection(
14292 &display_map,
14293 DisplayRow(row),
14294 &positions,
14295 selection.reversed,
14296 &text_layout_details,
14297 ) {
14298 stack.push(selection.id);
14299 columnar_selections.push(selection);
14300 }
14301 }
14302 if !stack.is_empty() {
14303 if above {
14304 stack.reverse();
14305 }
14306 state.groups.push(AddSelectionsGroup { above, stack });
14307 }
14308 }
14309
14310 let mut final_selections = Vec::new();
14311 let end_row = if above {
14312 DisplayRow(0)
14313 } else {
14314 display_map.max_point().row()
14315 };
14316
14317 let mut last_added_item_per_group = HashMap::default();
14318 for group in state.groups.iter_mut() {
14319 if let Some(last_id) = group.stack.last() {
14320 last_added_item_per_group.insert(*last_id, group);
14321 }
14322 }
14323
14324 for selection in columnar_selections {
14325 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14326 if above == group.above {
14327 let range = selection.display_range(&display_map).sorted();
14328 debug_assert_eq!(range.start.row(), range.end.row());
14329 let mut row = range.start.row();
14330 let positions =
14331 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14332 Pixels::from(start)..Pixels::from(end)
14333 } else {
14334 let start_x =
14335 display_map.x_for_display_point(range.start, &text_layout_details);
14336 let end_x =
14337 display_map.x_for_display_point(range.end, &text_layout_details);
14338 start_x.min(end_x)..start_x.max(end_x)
14339 };
14340
14341 let mut maybe_new_selection = None;
14342 while row != end_row {
14343 if above {
14344 row.0 -= 1;
14345 } else {
14346 row.0 += 1;
14347 }
14348 if let Some(new_selection) = self.selections.build_columnar_selection(
14349 &display_map,
14350 row,
14351 &positions,
14352 selection.reversed,
14353 &text_layout_details,
14354 ) {
14355 maybe_new_selection = Some(new_selection);
14356 break;
14357 }
14358 }
14359
14360 if let Some(new_selection) = maybe_new_selection {
14361 group.stack.push(new_selection.id);
14362 if above {
14363 final_selections.push(new_selection);
14364 final_selections.push(selection);
14365 } else {
14366 final_selections.push(selection);
14367 final_selections.push(new_selection);
14368 }
14369 } else {
14370 final_selections.push(selection);
14371 }
14372 } else {
14373 group.stack.pop();
14374 }
14375 } else {
14376 final_selections.push(selection);
14377 }
14378 }
14379
14380 self.change_selections(Default::default(), window, cx, |s| {
14381 s.select(final_selections);
14382 });
14383
14384 let final_selection_ids: HashSet<_> = self
14385 .selections
14386 .all::<Point>(cx)
14387 .iter()
14388 .map(|s| s.id)
14389 .collect();
14390 state.groups.retain_mut(|group| {
14391 // selections might get merged above so we remove invalid items from stacks
14392 group.stack.retain(|id| final_selection_ids.contains(id));
14393
14394 // single selection in stack can be treated as initial state
14395 group.stack.len() > 1
14396 });
14397
14398 if !state.groups.is_empty() {
14399 self.add_selections_state = Some(state);
14400 }
14401 }
14402
14403 fn select_match_ranges(
14404 &mut self,
14405 range: Range<usize>,
14406 reversed: bool,
14407 replace_newest: bool,
14408 auto_scroll: Option<Autoscroll>,
14409 window: &mut Window,
14410 cx: &mut Context<Editor>,
14411 ) {
14412 self.unfold_ranges(
14413 std::slice::from_ref(&range),
14414 false,
14415 auto_scroll.is_some(),
14416 cx,
14417 );
14418 let effects = if let Some(scroll) = auto_scroll {
14419 SelectionEffects::scroll(scroll)
14420 } else {
14421 SelectionEffects::no_scroll()
14422 };
14423 self.change_selections(effects, window, cx, |s| {
14424 if replace_newest {
14425 s.delete(s.newest_anchor().id);
14426 }
14427 if reversed {
14428 s.insert_range(range.end..range.start);
14429 } else {
14430 s.insert_range(range);
14431 }
14432 });
14433 }
14434
14435 pub fn select_next_match_internal(
14436 &mut self,
14437 display_map: &DisplaySnapshot,
14438 replace_newest: bool,
14439 autoscroll: Option<Autoscroll>,
14440 window: &mut Window,
14441 cx: &mut Context<Self>,
14442 ) -> Result<()> {
14443 let buffer = display_map.buffer_snapshot();
14444 let mut selections = self.selections.all::<usize>(cx);
14445 if let Some(mut select_next_state) = self.select_next_state.take() {
14446 let query = &select_next_state.query;
14447 if !select_next_state.done {
14448 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14449 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14450 let mut next_selected_range = None;
14451
14452 // Collect and sort selection ranges for efficient overlap checking
14453 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14454 selection_ranges.sort_by_key(|r| r.start);
14455
14456 let bytes_after_last_selection =
14457 buffer.bytes_in_range(last_selection.end..buffer.len());
14458 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14459 let query_matches = query
14460 .stream_find_iter(bytes_after_last_selection)
14461 .map(|result| (last_selection.end, result))
14462 .chain(
14463 query
14464 .stream_find_iter(bytes_before_first_selection)
14465 .map(|result| (0, result)),
14466 );
14467
14468 for (start_offset, query_match) in query_matches {
14469 let query_match = query_match.unwrap(); // can only fail due to I/O
14470 let offset_range =
14471 start_offset + query_match.start()..start_offset + query_match.end();
14472
14473 if !select_next_state.wordwise
14474 || (!buffer.is_inside_word(offset_range.start, None)
14475 && !buffer.is_inside_word(offset_range.end, None))
14476 {
14477 // Use binary search to check for overlap (O(log n))
14478 let overlaps = selection_ranges
14479 .binary_search_by(|range| {
14480 if range.end <= offset_range.start {
14481 std::cmp::Ordering::Less
14482 } else if range.start >= offset_range.end {
14483 std::cmp::Ordering::Greater
14484 } else {
14485 std::cmp::Ordering::Equal
14486 }
14487 })
14488 .is_ok();
14489
14490 if !overlaps {
14491 next_selected_range = Some(offset_range);
14492 break;
14493 }
14494 }
14495 }
14496
14497 if let Some(next_selected_range) = next_selected_range {
14498 self.select_match_ranges(
14499 next_selected_range,
14500 last_selection.reversed,
14501 replace_newest,
14502 autoscroll,
14503 window,
14504 cx,
14505 );
14506 } else {
14507 select_next_state.done = true;
14508 }
14509 }
14510
14511 self.select_next_state = Some(select_next_state);
14512 } else {
14513 let mut only_carets = true;
14514 let mut same_text_selected = true;
14515 let mut selected_text = None;
14516
14517 let mut selections_iter = selections.iter().peekable();
14518 while let Some(selection) = selections_iter.next() {
14519 if selection.start != selection.end {
14520 only_carets = false;
14521 }
14522
14523 if same_text_selected {
14524 if selected_text.is_none() {
14525 selected_text =
14526 Some(buffer.text_for_range(selection.range()).collect::<String>());
14527 }
14528
14529 if let Some(next_selection) = selections_iter.peek() {
14530 if next_selection.range().len() == selection.range().len() {
14531 let next_selected_text = buffer
14532 .text_for_range(next_selection.range())
14533 .collect::<String>();
14534 if Some(next_selected_text) != selected_text {
14535 same_text_selected = false;
14536 selected_text = None;
14537 }
14538 } else {
14539 same_text_selected = false;
14540 selected_text = None;
14541 }
14542 }
14543 }
14544 }
14545
14546 if only_carets {
14547 for selection in &mut selections {
14548 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14549 selection.start = word_range.start;
14550 selection.end = word_range.end;
14551 selection.goal = SelectionGoal::None;
14552 selection.reversed = false;
14553 self.select_match_ranges(
14554 selection.start..selection.end,
14555 selection.reversed,
14556 replace_newest,
14557 autoscroll,
14558 window,
14559 cx,
14560 );
14561 }
14562
14563 if selections.len() == 1 {
14564 let selection = selections
14565 .last()
14566 .expect("ensured that there's only one selection");
14567 let query = buffer
14568 .text_for_range(selection.start..selection.end)
14569 .collect::<String>();
14570 let is_empty = query.is_empty();
14571 let select_state = SelectNextState {
14572 query: AhoCorasick::new(&[query])?,
14573 wordwise: true,
14574 done: is_empty,
14575 };
14576 self.select_next_state = Some(select_state);
14577 } else {
14578 self.select_next_state = None;
14579 }
14580 } else if let Some(selected_text) = selected_text {
14581 self.select_next_state = Some(SelectNextState {
14582 query: AhoCorasick::new(&[selected_text])?,
14583 wordwise: false,
14584 done: false,
14585 });
14586 self.select_next_match_internal(
14587 display_map,
14588 replace_newest,
14589 autoscroll,
14590 window,
14591 cx,
14592 )?;
14593 }
14594 }
14595 Ok(())
14596 }
14597
14598 pub fn select_all_matches(
14599 &mut self,
14600 _action: &SelectAllMatches,
14601 window: &mut Window,
14602 cx: &mut Context<Self>,
14603 ) -> Result<()> {
14604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14605
14606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14607
14608 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14609 let Some(select_next_state) = self.select_next_state.as_mut() else {
14610 return Ok(());
14611 };
14612 if select_next_state.done {
14613 return Ok(());
14614 }
14615
14616 let mut new_selections = Vec::new();
14617
14618 let reversed = self.selections.oldest::<usize>(cx).reversed;
14619 let buffer = display_map.buffer_snapshot();
14620 let query_matches = select_next_state
14621 .query
14622 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14623
14624 for query_match in query_matches.into_iter() {
14625 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14626 let offset_range = if reversed {
14627 query_match.end()..query_match.start()
14628 } else {
14629 query_match.start()..query_match.end()
14630 };
14631
14632 if !select_next_state.wordwise
14633 || (!buffer.is_inside_word(offset_range.start, None)
14634 && !buffer.is_inside_word(offset_range.end, None))
14635 {
14636 new_selections.push(offset_range.start..offset_range.end);
14637 }
14638 }
14639
14640 select_next_state.done = true;
14641
14642 if new_selections.is_empty() {
14643 log::error!("bug: new_selections is empty in select_all_matches");
14644 return Ok(());
14645 }
14646
14647 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14648 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14649 selections.select_ranges(new_selections)
14650 });
14651
14652 Ok(())
14653 }
14654
14655 pub fn select_next(
14656 &mut self,
14657 action: &SelectNext,
14658 window: &mut Window,
14659 cx: &mut Context<Self>,
14660 ) -> Result<()> {
14661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14663 self.select_next_match_internal(
14664 &display_map,
14665 action.replace_newest,
14666 Some(Autoscroll::newest()),
14667 window,
14668 cx,
14669 )?;
14670 Ok(())
14671 }
14672
14673 pub fn select_previous(
14674 &mut self,
14675 action: &SelectPrevious,
14676 window: &mut Window,
14677 cx: &mut Context<Self>,
14678 ) -> Result<()> {
14679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14681 let buffer = display_map.buffer_snapshot();
14682 let mut selections = self.selections.all::<usize>(cx);
14683 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14684 let query = &select_prev_state.query;
14685 if !select_prev_state.done {
14686 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14687 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14688 let mut next_selected_range = None;
14689 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14690 let bytes_before_last_selection =
14691 buffer.reversed_bytes_in_range(0..last_selection.start);
14692 let bytes_after_first_selection =
14693 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14694 let query_matches = query
14695 .stream_find_iter(bytes_before_last_selection)
14696 .map(|result| (last_selection.start, result))
14697 .chain(
14698 query
14699 .stream_find_iter(bytes_after_first_selection)
14700 .map(|result| (buffer.len(), result)),
14701 );
14702 for (end_offset, query_match) in query_matches {
14703 let query_match = query_match.unwrap(); // can only fail due to I/O
14704 let offset_range =
14705 end_offset - query_match.end()..end_offset - query_match.start();
14706
14707 if !select_prev_state.wordwise
14708 || (!buffer.is_inside_word(offset_range.start, None)
14709 && !buffer.is_inside_word(offset_range.end, None))
14710 {
14711 next_selected_range = Some(offset_range);
14712 break;
14713 }
14714 }
14715
14716 if let Some(next_selected_range) = next_selected_range {
14717 self.select_match_ranges(
14718 next_selected_range,
14719 last_selection.reversed,
14720 action.replace_newest,
14721 Some(Autoscroll::newest()),
14722 window,
14723 cx,
14724 );
14725 } else {
14726 select_prev_state.done = true;
14727 }
14728 }
14729
14730 self.select_prev_state = Some(select_prev_state);
14731 } else {
14732 let mut only_carets = true;
14733 let mut same_text_selected = true;
14734 let mut selected_text = None;
14735
14736 let mut selections_iter = selections.iter().peekable();
14737 while let Some(selection) = selections_iter.next() {
14738 if selection.start != selection.end {
14739 only_carets = false;
14740 }
14741
14742 if same_text_selected {
14743 if selected_text.is_none() {
14744 selected_text =
14745 Some(buffer.text_for_range(selection.range()).collect::<String>());
14746 }
14747
14748 if let Some(next_selection) = selections_iter.peek() {
14749 if next_selection.range().len() == selection.range().len() {
14750 let next_selected_text = buffer
14751 .text_for_range(next_selection.range())
14752 .collect::<String>();
14753 if Some(next_selected_text) != selected_text {
14754 same_text_selected = false;
14755 selected_text = None;
14756 }
14757 } else {
14758 same_text_selected = false;
14759 selected_text = None;
14760 }
14761 }
14762 }
14763 }
14764
14765 if only_carets {
14766 for selection in &mut selections {
14767 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14768 selection.start = word_range.start;
14769 selection.end = word_range.end;
14770 selection.goal = SelectionGoal::None;
14771 selection.reversed = false;
14772 self.select_match_ranges(
14773 selection.start..selection.end,
14774 selection.reversed,
14775 action.replace_newest,
14776 Some(Autoscroll::newest()),
14777 window,
14778 cx,
14779 );
14780 }
14781 if selections.len() == 1 {
14782 let selection = selections
14783 .last()
14784 .expect("ensured that there's only one selection");
14785 let query = buffer
14786 .text_for_range(selection.start..selection.end)
14787 .collect::<String>();
14788 let is_empty = query.is_empty();
14789 let select_state = SelectNextState {
14790 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14791 wordwise: true,
14792 done: is_empty,
14793 };
14794 self.select_prev_state = Some(select_state);
14795 } else {
14796 self.select_prev_state = None;
14797 }
14798 } else if let Some(selected_text) = selected_text {
14799 self.select_prev_state = Some(SelectNextState {
14800 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14801 wordwise: false,
14802 done: false,
14803 });
14804 self.select_previous(action, window, cx)?;
14805 }
14806 }
14807 Ok(())
14808 }
14809
14810 pub fn find_next_match(
14811 &mut self,
14812 _: &FindNextMatch,
14813 window: &mut Window,
14814 cx: &mut Context<Self>,
14815 ) -> Result<()> {
14816 let selections = self.selections.disjoint_anchors_arc();
14817 match selections.first() {
14818 Some(first) if selections.len() >= 2 => {
14819 self.change_selections(Default::default(), window, cx, |s| {
14820 s.select_ranges([first.range()]);
14821 });
14822 }
14823 _ => self.select_next(
14824 &SelectNext {
14825 replace_newest: true,
14826 },
14827 window,
14828 cx,
14829 )?,
14830 }
14831 Ok(())
14832 }
14833
14834 pub fn find_previous_match(
14835 &mut self,
14836 _: &FindPreviousMatch,
14837 window: &mut Window,
14838 cx: &mut Context<Self>,
14839 ) -> Result<()> {
14840 let selections = self.selections.disjoint_anchors_arc();
14841 match selections.last() {
14842 Some(last) if selections.len() >= 2 => {
14843 self.change_selections(Default::default(), window, cx, |s| {
14844 s.select_ranges([last.range()]);
14845 });
14846 }
14847 _ => self.select_previous(
14848 &SelectPrevious {
14849 replace_newest: true,
14850 },
14851 window,
14852 cx,
14853 )?,
14854 }
14855 Ok(())
14856 }
14857
14858 pub fn toggle_comments(
14859 &mut self,
14860 action: &ToggleComments,
14861 window: &mut Window,
14862 cx: &mut Context<Self>,
14863 ) {
14864 if self.read_only(cx) {
14865 return;
14866 }
14867 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14868 let text_layout_details = &self.text_layout_details(window);
14869 self.transact(window, cx, |this, window, cx| {
14870 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14871 let mut edits = Vec::new();
14872 let mut selection_edit_ranges = Vec::new();
14873 let mut last_toggled_row = None;
14874 let snapshot = this.buffer.read(cx).read(cx);
14875 let empty_str: Arc<str> = Arc::default();
14876 let mut suffixes_inserted = Vec::new();
14877 let ignore_indent = action.ignore_indent;
14878
14879 fn comment_prefix_range(
14880 snapshot: &MultiBufferSnapshot,
14881 row: MultiBufferRow,
14882 comment_prefix: &str,
14883 comment_prefix_whitespace: &str,
14884 ignore_indent: bool,
14885 ) -> Range<Point> {
14886 let indent_size = if ignore_indent {
14887 0
14888 } else {
14889 snapshot.indent_size_for_line(row).len
14890 };
14891
14892 let start = Point::new(row.0, indent_size);
14893
14894 let mut line_bytes = snapshot
14895 .bytes_in_range(start..snapshot.max_point())
14896 .flatten()
14897 .copied();
14898
14899 // If this line currently begins with the line comment prefix, then record
14900 // the range containing the prefix.
14901 if line_bytes
14902 .by_ref()
14903 .take(comment_prefix.len())
14904 .eq(comment_prefix.bytes())
14905 {
14906 // Include any whitespace that matches the comment prefix.
14907 let matching_whitespace_len = line_bytes
14908 .zip(comment_prefix_whitespace.bytes())
14909 .take_while(|(a, b)| a == b)
14910 .count() as u32;
14911 let end = Point::new(
14912 start.row,
14913 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14914 );
14915 start..end
14916 } else {
14917 start..start
14918 }
14919 }
14920
14921 fn comment_suffix_range(
14922 snapshot: &MultiBufferSnapshot,
14923 row: MultiBufferRow,
14924 comment_suffix: &str,
14925 comment_suffix_has_leading_space: bool,
14926 ) -> Range<Point> {
14927 let end = Point::new(row.0, snapshot.line_len(row));
14928 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14929
14930 let mut line_end_bytes = snapshot
14931 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14932 .flatten()
14933 .copied();
14934
14935 let leading_space_len = if suffix_start_column > 0
14936 && line_end_bytes.next() == Some(b' ')
14937 && comment_suffix_has_leading_space
14938 {
14939 1
14940 } else {
14941 0
14942 };
14943
14944 // If this line currently begins with the line comment prefix, then record
14945 // the range containing the prefix.
14946 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14947 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14948 start..end
14949 } else {
14950 end..end
14951 }
14952 }
14953
14954 // TODO: Handle selections that cross excerpts
14955 for selection in &mut selections {
14956 let start_column = snapshot
14957 .indent_size_for_line(MultiBufferRow(selection.start.row))
14958 .len;
14959 let language = if let Some(language) =
14960 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14961 {
14962 language
14963 } else {
14964 continue;
14965 };
14966
14967 selection_edit_ranges.clear();
14968
14969 // If multiple selections contain a given row, avoid processing that
14970 // row more than once.
14971 let mut start_row = MultiBufferRow(selection.start.row);
14972 if last_toggled_row == Some(start_row) {
14973 start_row = start_row.next_row();
14974 }
14975 let end_row =
14976 if selection.end.row > selection.start.row && selection.end.column == 0 {
14977 MultiBufferRow(selection.end.row - 1)
14978 } else {
14979 MultiBufferRow(selection.end.row)
14980 };
14981 last_toggled_row = Some(end_row);
14982
14983 if start_row > end_row {
14984 continue;
14985 }
14986
14987 // If the language has line comments, toggle those.
14988 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14989
14990 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14991 if ignore_indent {
14992 full_comment_prefixes = full_comment_prefixes
14993 .into_iter()
14994 .map(|s| Arc::from(s.trim_end()))
14995 .collect();
14996 }
14997
14998 if !full_comment_prefixes.is_empty() {
14999 let first_prefix = full_comment_prefixes
15000 .first()
15001 .expect("prefixes is non-empty");
15002 let prefix_trimmed_lengths = full_comment_prefixes
15003 .iter()
15004 .map(|p| p.trim_end_matches(' ').len())
15005 .collect::<SmallVec<[usize; 4]>>();
15006
15007 let mut all_selection_lines_are_comments = true;
15008
15009 for row in start_row.0..=end_row.0 {
15010 let row = MultiBufferRow(row);
15011 if start_row < end_row && snapshot.is_line_blank(row) {
15012 continue;
15013 }
15014
15015 let prefix_range = full_comment_prefixes
15016 .iter()
15017 .zip(prefix_trimmed_lengths.iter().copied())
15018 .map(|(prefix, trimmed_prefix_len)| {
15019 comment_prefix_range(
15020 snapshot.deref(),
15021 row,
15022 &prefix[..trimmed_prefix_len],
15023 &prefix[trimmed_prefix_len..],
15024 ignore_indent,
15025 )
15026 })
15027 .max_by_key(|range| range.end.column - range.start.column)
15028 .expect("prefixes is non-empty");
15029
15030 if prefix_range.is_empty() {
15031 all_selection_lines_are_comments = false;
15032 }
15033
15034 selection_edit_ranges.push(prefix_range);
15035 }
15036
15037 if all_selection_lines_are_comments {
15038 edits.extend(
15039 selection_edit_ranges
15040 .iter()
15041 .cloned()
15042 .map(|range| (range, empty_str.clone())),
15043 );
15044 } else {
15045 let min_column = selection_edit_ranges
15046 .iter()
15047 .map(|range| range.start.column)
15048 .min()
15049 .unwrap_or(0);
15050 edits.extend(selection_edit_ranges.iter().map(|range| {
15051 let position = Point::new(range.start.row, min_column);
15052 (position..position, first_prefix.clone())
15053 }));
15054 }
15055 } else if let Some(BlockCommentConfig {
15056 start: full_comment_prefix,
15057 end: comment_suffix,
15058 ..
15059 }) = language.block_comment()
15060 {
15061 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15062 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15063 let prefix_range = comment_prefix_range(
15064 snapshot.deref(),
15065 start_row,
15066 comment_prefix,
15067 comment_prefix_whitespace,
15068 ignore_indent,
15069 );
15070 let suffix_range = comment_suffix_range(
15071 snapshot.deref(),
15072 end_row,
15073 comment_suffix.trim_start_matches(' '),
15074 comment_suffix.starts_with(' '),
15075 );
15076
15077 if prefix_range.is_empty() || suffix_range.is_empty() {
15078 edits.push((
15079 prefix_range.start..prefix_range.start,
15080 full_comment_prefix.clone(),
15081 ));
15082 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15083 suffixes_inserted.push((end_row, comment_suffix.len()));
15084 } else {
15085 edits.push((prefix_range, empty_str.clone()));
15086 edits.push((suffix_range, empty_str.clone()));
15087 }
15088 } else {
15089 continue;
15090 }
15091 }
15092
15093 drop(snapshot);
15094 this.buffer.update(cx, |buffer, cx| {
15095 buffer.edit(edits, None, cx);
15096 });
15097
15098 // Adjust selections so that they end before any comment suffixes that
15099 // were inserted.
15100 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15101 let mut selections = this.selections.all::<Point>(cx);
15102 let snapshot = this.buffer.read(cx).read(cx);
15103 for selection in &mut selections {
15104 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15105 match row.cmp(&MultiBufferRow(selection.end.row)) {
15106 Ordering::Less => {
15107 suffixes_inserted.next();
15108 continue;
15109 }
15110 Ordering::Greater => break,
15111 Ordering::Equal => {
15112 if selection.end.column == snapshot.line_len(row) {
15113 if selection.is_empty() {
15114 selection.start.column -= suffix_len as u32;
15115 }
15116 selection.end.column -= suffix_len as u32;
15117 }
15118 break;
15119 }
15120 }
15121 }
15122 }
15123
15124 drop(snapshot);
15125 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15126
15127 let selections = this.selections.all::<Point>(cx);
15128 let selections_on_single_row = selections.windows(2).all(|selections| {
15129 selections[0].start.row == selections[1].start.row
15130 && selections[0].end.row == selections[1].end.row
15131 && selections[0].start.row == selections[0].end.row
15132 });
15133 let selections_selecting = selections
15134 .iter()
15135 .any(|selection| selection.start != selection.end);
15136 let advance_downwards = action.advance_downwards
15137 && selections_on_single_row
15138 && !selections_selecting
15139 && !matches!(this.mode, EditorMode::SingleLine);
15140
15141 if advance_downwards {
15142 let snapshot = this.buffer.read(cx).snapshot(cx);
15143
15144 this.change_selections(Default::default(), window, cx, |s| {
15145 s.move_cursors_with(|display_snapshot, display_point, _| {
15146 let mut point = display_point.to_point(display_snapshot);
15147 point.row += 1;
15148 point = snapshot.clip_point(point, Bias::Left);
15149 let display_point = point.to_display_point(display_snapshot);
15150 let goal = SelectionGoal::HorizontalPosition(
15151 display_snapshot
15152 .x_for_display_point(display_point, text_layout_details)
15153 .into(),
15154 );
15155 (display_point, goal)
15156 })
15157 });
15158 }
15159 });
15160 }
15161
15162 pub fn select_enclosing_symbol(
15163 &mut self,
15164 _: &SelectEnclosingSymbol,
15165 window: &mut Window,
15166 cx: &mut Context<Self>,
15167 ) {
15168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15169
15170 let buffer = self.buffer.read(cx).snapshot(cx);
15171 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15172
15173 fn update_selection(
15174 selection: &Selection<usize>,
15175 buffer_snap: &MultiBufferSnapshot,
15176 ) -> Option<Selection<usize>> {
15177 let cursor = selection.head();
15178 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15179 for symbol in symbols.iter().rev() {
15180 let start = symbol.range.start.to_offset(buffer_snap);
15181 let end = symbol.range.end.to_offset(buffer_snap);
15182 let new_range = start..end;
15183 if start < selection.start || end > selection.end {
15184 return Some(Selection {
15185 id: selection.id,
15186 start: new_range.start,
15187 end: new_range.end,
15188 goal: SelectionGoal::None,
15189 reversed: selection.reversed,
15190 });
15191 }
15192 }
15193 None
15194 }
15195
15196 let mut selected_larger_symbol = false;
15197 let new_selections = old_selections
15198 .iter()
15199 .map(|selection| match update_selection(selection, &buffer) {
15200 Some(new_selection) => {
15201 if new_selection.range() != selection.range() {
15202 selected_larger_symbol = true;
15203 }
15204 new_selection
15205 }
15206 None => selection.clone(),
15207 })
15208 .collect::<Vec<_>>();
15209
15210 if selected_larger_symbol {
15211 self.change_selections(Default::default(), window, cx, |s| {
15212 s.select(new_selections);
15213 });
15214 }
15215 }
15216
15217 pub fn select_larger_syntax_node(
15218 &mut self,
15219 _: &SelectLargerSyntaxNode,
15220 window: &mut Window,
15221 cx: &mut Context<Self>,
15222 ) {
15223 let Some(visible_row_count) = self.visible_row_count() else {
15224 return;
15225 };
15226 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15227 if old_selections.is_empty() {
15228 return;
15229 }
15230
15231 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15232
15233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15234 let buffer = self.buffer.read(cx).snapshot(cx);
15235
15236 let mut selected_larger_node = false;
15237 let mut new_selections = old_selections
15238 .iter()
15239 .map(|selection| {
15240 let old_range = selection.start..selection.end;
15241
15242 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15243 // manually select word at selection
15244 if ["string_content", "inline"].contains(&node.kind()) {
15245 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15246 // ignore if word is already selected
15247 if !word_range.is_empty() && old_range != word_range {
15248 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15249 // only select word if start and end point belongs to same word
15250 if word_range == last_word_range {
15251 selected_larger_node = true;
15252 return Selection {
15253 id: selection.id,
15254 start: word_range.start,
15255 end: word_range.end,
15256 goal: SelectionGoal::None,
15257 reversed: selection.reversed,
15258 };
15259 }
15260 }
15261 }
15262 }
15263
15264 let mut new_range = old_range.clone();
15265 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15266 new_range = range;
15267 if !node.is_named() {
15268 continue;
15269 }
15270 if !display_map.intersects_fold(new_range.start)
15271 && !display_map.intersects_fold(new_range.end)
15272 {
15273 break;
15274 }
15275 }
15276
15277 selected_larger_node |= new_range != old_range;
15278 Selection {
15279 id: selection.id,
15280 start: new_range.start,
15281 end: new_range.end,
15282 goal: SelectionGoal::None,
15283 reversed: selection.reversed,
15284 }
15285 })
15286 .collect::<Vec<_>>();
15287
15288 if !selected_larger_node {
15289 return; // don't put this call in the history
15290 }
15291
15292 // scroll based on transformation done to the last selection created by the user
15293 let (last_old, last_new) = old_selections
15294 .last()
15295 .zip(new_selections.last().cloned())
15296 .expect("old_selections isn't empty");
15297
15298 // revert selection
15299 let is_selection_reversed = {
15300 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15301 new_selections.last_mut().expect("checked above").reversed =
15302 should_newest_selection_be_reversed;
15303 should_newest_selection_be_reversed
15304 };
15305
15306 if selected_larger_node {
15307 self.select_syntax_node_history.disable_clearing = true;
15308 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15309 s.select(new_selections.clone());
15310 });
15311 self.select_syntax_node_history.disable_clearing = false;
15312 }
15313
15314 let start_row = last_new.start.to_display_point(&display_map).row().0;
15315 let end_row = last_new.end.to_display_point(&display_map).row().0;
15316 let selection_height = end_row - start_row + 1;
15317 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15318
15319 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15320 let scroll_behavior = if fits_on_the_screen {
15321 self.request_autoscroll(Autoscroll::fit(), cx);
15322 SelectSyntaxNodeScrollBehavior::FitSelection
15323 } else if is_selection_reversed {
15324 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15325 SelectSyntaxNodeScrollBehavior::CursorTop
15326 } else {
15327 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15328 SelectSyntaxNodeScrollBehavior::CursorBottom
15329 };
15330
15331 self.select_syntax_node_history.push((
15332 old_selections,
15333 scroll_behavior,
15334 is_selection_reversed,
15335 ));
15336 }
15337
15338 pub fn select_smaller_syntax_node(
15339 &mut self,
15340 _: &SelectSmallerSyntaxNode,
15341 window: &mut Window,
15342 cx: &mut Context<Self>,
15343 ) {
15344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15345
15346 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15347 self.select_syntax_node_history.pop()
15348 {
15349 if let Some(selection) = selections.last_mut() {
15350 selection.reversed = is_selection_reversed;
15351 }
15352
15353 self.select_syntax_node_history.disable_clearing = true;
15354 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15355 s.select(selections.to_vec());
15356 });
15357 self.select_syntax_node_history.disable_clearing = false;
15358
15359 match scroll_behavior {
15360 SelectSyntaxNodeScrollBehavior::CursorTop => {
15361 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15362 }
15363 SelectSyntaxNodeScrollBehavior::FitSelection => {
15364 self.request_autoscroll(Autoscroll::fit(), cx);
15365 }
15366 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15367 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15368 }
15369 }
15370 }
15371 }
15372
15373 pub fn unwrap_syntax_node(
15374 &mut self,
15375 _: &UnwrapSyntaxNode,
15376 window: &mut Window,
15377 cx: &mut Context<Self>,
15378 ) {
15379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15380
15381 let buffer = self.buffer.read(cx).snapshot(cx);
15382 let selections = self
15383 .selections
15384 .all::<usize>(cx)
15385 .into_iter()
15386 // subtracting the offset requires sorting
15387 .sorted_by_key(|i| i.start);
15388
15389 let full_edits = selections
15390 .into_iter()
15391 .filter_map(|selection| {
15392 let child = if selection.is_empty()
15393 && let Some((_, ancestor_range)) =
15394 buffer.syntax_ancestor(selection.start..selection.end)
15395 {
15396 ancestor_range
15397 } else {
15398 selection.range()
15399 };
15400
15401 let mut parent = child.clone();
15402 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15403 parent = ancestor_range;
15404 if parent.start < child.start || parent.end > child.end {
15405 break;
15406 }
15407 }
15408
15409 if parent == child {
15410 return None;
15411 }
15412 let text = buffer.text_for_range(child).collect::<String>();
15413 Some((selection.id, parent, text))
15414 })
15415 .collect::<Vec<_>>();
15416 if full_edits.is_empty() {
15417 return;
15418 }
15419
15420 self.transact(window, cx, |this, window, cx| {
15421 this.buffer.update(cx, |buffer, cx| {
15422 buffer.edit(
15423 full_edits
15424 .iter()
15425 .map(|(_, p, t)| (p.clone(), t.clone()))
15426 .collect::<Vec<_>>(),
15427 None,
15428 cx,
15429 );
15430 });
15431 this.change_selections(Default::default(), window, cx, |s| {
15432 let mut offset = 0;
15433 let mut selections = vec![];
15434 for (id, parent, text) in full_edits {
15435 let start = parent.start - offset;
15436 offset += parent.len() - text.len();
15437 selections.push(Selection {
15438 id,
15439 start,
15440 end: start + text.len(),
15441 reversed: false,
15442 goal: Default::default(),
15443 });
15444 }
15445 s.select(selections);
15446 });
15447 });
15448 }
15449
15450 pub fn select_next_syntax_node(
15451 &mut self,
15452 _: &SelectNextSyntaxNode,
15453 window: &mut Window,
15454 cx: &mut Context<Self>,
15455 ) {
15456 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15457 if old_selections.is_empty() {
15458 return;
15459 }
15460
15461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15462
15463 let buffer = self.buffer.read(cx).snapshot(cx);
15464 let mut selected_sibling = false;
15465
15466 let new_selections = old_selections
15467 .iter()
15468 .map(|selection| {
15469 let old_range = selection.start..selection.end;
15470
15471 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15472 let new_range = node.byte_range();
15473 selected_sibling = true;
15474 Selection {
15475 id: selection.id,
15476 start: new_range.start,
15477 end: new_range.end,
15478 goal: SelectionGoal::None,
15479 reversed: selection.reversed,
15480 }
15481 } else {
15482 selection.clone()
15483 }
15484 })
15485 .collect::<Vec<_>>();
15486
15487 if selected_sibling {
15488 self.change_selections(
15489 SelectionEffects::scroll(Autoscroll::fit()),
15490 window,
15491 cx,
15492 |s| {
15493 s.select(new_selections);
15494 },
15495 );
15496 }
15497 }
15498
15499 pub fn select_prev_syntax_node(
15500 &mut self,
15501 _: &SelectPreviousSyntaxNode,
15502 window: &mut Window,
15503 cx: &mut Context<Self>,
15504 ) {
15505 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15506 if old_selections.is_empty() {
15507 return;
15508 }
15509
15510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15511
15512 let buffer = self.buffer.read(cx).snapshot(cx);
15513 let mut selected_sibling = false;
15514
15515 let new_selections = old_selections
15516 .iter()
15517 .map(|selection| {
15518 let old_range = selection.start..selection.end;
15519
15520 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15521 let new_range = node.byte_range();
15522 selected_sibling = true;
15523 Selection {
15524 id: selection.id,
15525 start: new_range.start,
15526 end: new_range.end,
15527 goal: SelectionGoal::None,
15528 reversed: selection.reversed,
15529 }
15530 } else {
15531 selection.clone()
15532 }
15533 })
15534 .collect::<Vec<_>>();
15535
15536 if selected_sibling {
15537 self.change_selections(
15538 SelectionEffects::scroll(Autoscroll::fit()),
15539 window,
15540 cx,
15541 |s| {
15542 s.select(new_selections);
15543 },
15544 );
15545 }
15546 }
15547
15548 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15549 if !EditorSettings::get_global(cx).gutter.runnables {
15550 self.clear_tasks();
15551 return Task::ready(());
15552 }
15553 let project = self.project().map(Entity::downgrade);
15554 let task_sources = self.lsp_task_sources(cx);
15555 let multi_buffer = self.buffer.downgrade();
15556 cx.spawn_in(window, async move |editor, cx| {
15557 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15558 let Some(project) = project.and_then(|p| p.upgrade()) else {
15559 return;
15560 };
15561 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15562 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15563 }) else {
15564 return;
15565 };
15566
15567 let hide_runnables = project
15568 .update(cx, |project, _| project.is_via_collab())
15569 .unwrap_or(true);
15570 if hide_runnables {
15571 return;
15572 }
15573 let new_rows =
15574 cx.background_spawn({
15575 let snapshot = display_snapshot.clone();
15576 async move {
15577 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15578 }
15579 })
15580 .await;
15581 let Ok(lsp_tasks) =
15582 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15583 else {
15584 return;
15585 };
15586 let lsp_tasks = lsp_tasks.await;
15587
15588 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15589 lsp_tasks
15590 .into_iter()
15591 .flat_map(|(kind, tasks)| {
15592 tasks.into_iter().filter_map(move |(location, task)| {
15593 Some((kind.clone(), location?, task))
15594 })
15595 })
15596 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15597 let buffer = location.target.buffer;
15598 let buffer_snapshot = buffer.read(cx).snapshot();
15599 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15600 |(excerpt_id, snapshot, _)| {
15601 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15602 display_snapshot
15603 .buffer_snapshot()
15604 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15605 } else {
15606 None
15607 }
15608 },
15609 );
15610 if let Some(offset) = offset {
15611 let task_buffer_range =
15612 location.target.range.to_point(&buffer_snapshot);
15613 let context_buffer_range =
15614 task_buffer_range.to_offset(&buffer_snapshot);
15615 let context_range = BufferOffset(context_buffer_range.start)
15616 ..BufferOffset(context_buffer_range.end);
15617
15618 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15619 .or_insert_with(|| RunnableTasks {
15620 templates: Vec::new(),
15621 offset,
15622 column: task_buffer_range.start.column,
15623 extra_variables: HashMap::default(),
15624 context_range,
15625 })
15626 .templates
15627 .push((kind, task.original_task().clone()));
15628 }
15629
15630 acc
15631 })
15632 }) else {
15633 return;
15634 };
15635
15636 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15637 buffer.language_settings(cx).tasks.prefer_lsp
15638 }) else {
15639 return;
15640 };
15641
15642 let rows = Self::runnable_rows(
15643 project,
15644 display_snapshot,
15645 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15646 new_rows,
15647 cx.clone(),
15648 )
15649 .await;
15650 editor
15651 .update(cx, |editor, _| {
15652 editor.clear_tasks();
15653 for (key, mut value) in rows {
15654 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15655 value.templates.extend(lsp_tasks.templates);
15656 }
15657
15658 editor.insert_tasks(key, value);
15659 }
15660 for (key, value) in lsp_tasks_by_rows {
15661 editor.insert_tasks(key, value);
15662 }
15663 })
15664 .ok();
15665 })
15666 }
15667 fn fetch_runnable_ranges(
15668 snapshot: &DisplaySnapshot,
15669 range: Range<Anchor>,
15670 ) -> Vec<language::RunnableRange> {
15671 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15672 }
15673
15674 fn runnable_rows(
15675 project: Entity<Project>,
15676 snapshot: DisplaySnapshot,
15677 prefer_lsp: bool,
15678 runnable_ranges: Vec<RunnableRange>,
15679 cx: AsyncWindowContext,
15680 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15681 cx.spawn(async move |cx| {
15682 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15683 for mut runnable in runnable_ranges {
15684 let Some(tasks) = cx
15685 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15686 .ok()
15687 else {
15688 continue;
15689 };
15690 let mut tasks = tasks.await;
15691
15692 if prefer_lsp {
15693 tasks.retain(|(task_kind, _)| {
15694 !matches!(task_kind, TaskSourceKind::Language { .. })
15695 });
15696 }
15697 if tasks.is_empty() {
15698 continue;
15699 }
15700
15701 let point = runnable
15702 .run_range
15703 .start
15704 .to_point(&snapshot.buffer_snapshot());
15705 let Some(row) = snapshot
15706 .buffer_snapshot()
15707 .buffer_line_for_row(MultiBufferRow(point.row))
15708 .map(|(_, range)| range.start.row)
15709 else {
15710 continue;
15711 };
15712
15713 let context_range =
15714 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15715 runnable_rows.push((
15716 (runnable.buffer_id, row),
15717 RunnableTasks {
15718 templates: tasks,
15719 offset: snapshot
15720 .buffer_snapshot()
15721 .anchor_before(runnable.run_range.start),
15722 context_range,
15723 column: point.column,
15724 extra_variables: runnable.extra_captures,
15725 },
15726 ));
15727 }
15728 runnable_rows
15729 })
15730 }
15731
15732 fn templates_with_tags(
15733 project: &Entity<Project>,
15734 runnable: &mut Runnable,
15735 cx: &mut App,
15736 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15737 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15738 let (worktree_id, file) = project
15739 .buffer_for_id(runnable.buffer, cx)
15740 .and_then(|buffer| buffer.read(cx).file())
15741 .map(|file| (file.worktree_id(cx), file.clone()))
15742 .unzip();
15743
15744 (
15745 project.task_store().read(cx).task_inventory().cloned(),
15746 worktree_id,
15747 file,
15748 )
15749 });
15750
15751 let tags = mem::take(&mut runnable.tags);
15752 let language = runnable.language.clone();
15753 cx.spawn(async move |cx| {
15754 let mut templates_with_tags = Vec::new();
15755 if let Some(inventory) = inventory {
15756 for RunnableTag(tag) in tags {
15757 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15758 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15759 }) else {
15760 return templates_with_tags;
15761 };
15762 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15763 move |(_, template)| {
15764 template.tags.iter().any(|source_tag| source_tag == &tag)
15765 },
15766 ));
15767 }
15768 }
15769 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15770
15771 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15772 // Strongest source wins; if we have worktree tag binding, prefer that to
15773 // global and language bindings;
15774 // if we have a global binding, prefer that to language binding.
15775 let first_mismatch = templates_with_tags
15776 .iter()
15777 .position(|(tag_source, _)| tag_source != leading_tag_source);
15778 if let Some(index) = first_mismatch {
15779 templates_with_tags.truncate(index);
15780 }
15781 }
15782
15783 templates_with_tags
15784 })
15785 }
15786
15787 pub fn move_to_enclosing_bracket(
15788 &mut self,
15789 _: &MoveToEnclosingBracket,
15790 window: &mut Window,
15791 cx: &mut Context<Self>,
15792 ) {
15793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15794 self.change_selections(Default::default(), window, cx, |s| {
15795 s.move_offsets_with(|snapshot, selection| {
15796 let Some(enclosing_bracket_ranges) =
15797 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15798 else {
15799 return;
15800 };
15801
15802 let mut best_length = usize::MAX;
15803 let mut best_inside = false;
15804 let mut best_in_bracket_range = false;
15805 let mut best_destination = None;
15806 for (open, close) in enclosing_bracket_ranges {
15807 let close = close.to_inclusive();
15808 let length = close.end() - open.start;
15809 let inside = selection.start >= open.end && selection.end <= *close.start();
15810 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15811 || close.contains(&selection.head());
15812
15813 // If best is next to a bracket and current isn't, skip
15814 if !in_bracket_range && best_in_bracket_range {
15815 continue;
15816 }
15817
15818 // Prefer smaller lengths unless best is inside and current isn't
15819 if length > best_length && (best_inside || !inside) {
15820 continue;
15821 }
15822
15823 best_length = length;
15824 best_inside = inside;
15825 best_in_bracket_range = in_bracket_range;
15826 best_destination = Some(
15827 if close.contains(&selection.start) && close.contains(&selection.end) {
15828 if inside { open.end } else { open.start }
15829 } else if inside {
15830 *close.start()
15831 } else {
15832 *close.end()
15833 },
15834 );
15835 }
15836
15837 if let Some(destination) = best_destination {
15838 selection.collapse_to(destination, SelectionGoal::None);
15839 }
15840 })
15841 });
15842 }
15843
15844 pub fn undo_selection(
15845 &mut self,
15846 _: &UndoSelection,
15847 window: &mut Window,
15848 cx: &mut Context<Self>,
15849 ) {
15850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15851 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15852 self.selection_history.mode = SelectionHistoryMode::Undoing;
15853 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15854 this.end_selection(window, cx);
15855 this.change_selections(
15856 SelectionEffects::scroll(Autoscroll::newest()),
15857 window,
15858 cx,
15859 |s| s.select_anchors(entry.selections.to_vec()),
15860 );
15861 });
15862 self.selection_history.mode = SelectionHistoryMode::Normal;
15863
15864 self.select_next_state = entry.select_next_state;
15865 self.select_prev_state = entry.select_prev_state;
15866 self.add_selections_state = entry.add_selections_state;
15867 }
15868 }
15869
15870 pub fn redo_selection(
15871 &mut self,
15872 _: &RedoSelection,
15873 window: &mut Window,
15874 cx: &mut Context<Self>,
15875 ) {
15876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15877 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15878 self.selection_history.mode = SelectionHistoryMode::Redoing;
15879 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15880 this.end_selection(window, cx);
15881 this.change_selections(
15882 SelectionEffects::scroll(Autoscroll::newest()),
15883 window,
15884 cx,
15885 |s| s.select_anchors(entry.selections.to_vec()),
15886 );
15887 });
15888 self.selection_history.mode = SelectionHistoryMode::Normal;
15889
15890 self.select_next_state = entry.select_next_state;
15891 self.select_prev_state = entry.select_prev_state;
15892 self.add_selections_state = entry.add_selections_state;
15893 }
15894 }
15895
15896 pub fn expand_excerpts(
15897 &mut self,
15898 action: &ExpandExcerpts,
15899 _: &mut Window,
15900 cx: &mut Context<Self>,
15901 ) {
15902 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15903 }
15904
15905 pub fn expand_excerpts_down(
15906 &mut self,
15907 action: &ExpandExcerptsDown,
15908 _: &mut Window,
15909 cx: &mut Context<Self>,
15910 ) {
15911 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15912 }
15913
15914 pub fn expand_excerpts_up(
15915 &mut self,
15916 action: &ExpandExcerptsUp,
15917 _: &mut Window,
15918 cx: &mut Context<Self>,
15919 ) {
15920 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15921 }
15922
15923 pub fn expand_excerpts_for_direction(
15924 &mut self,
15925 lines: u32,
15926 direction: ExpandExcerptDirection,
15927
15928 cx: &mut Context<Self>,
15929 ) {
15930 let selections = self.selections.disjoint_anchors_arc();
15931
15932 let lines = if lines == 0 {
15933 EditorSettings::get_global(cx).expand_excerpt_lines
15934 } else {
15935 lines
15936 };
15937
15938 self.buffer.update(cx, |buffer, cx| {
15939 let snapshot = buffer.snapshot(cx);
15940 let mut excerpt_ids = selections
15941 .iter()
15942 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15943 .collect::<Vec<_>>();
15944 excerpt_ids.sort();
15945 excerpt_ids.dedup();
15946 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15947 })
15948 }
15949
15950 pub fn expand_excerpt(
15951 &mut self,
15952 excerpt: ExcerptId,
15953 direction: ExpandExcerptDirection,
15954 window: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) {
15957 let current_scroll_position = self.scroll_position(cx);
15958 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15959 let mut should_scroll_up = false;
15960
15961 if direction == ExpandExcerptDirection::Down {
15962 let multi_buffer = self.buffer.read(cx);
15963 let snapshot = multi_buffer.snapshot(cx);
15964 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15965 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15966 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15967 {
15968 let buffer_snapshot = buffer.read(cx).snapshot();
15969 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15970 let last_row = buffer_snapshot.max_point().row;
15971 let lines_below = last_row.saturating_sub(excerpt_end_row);
15972 should_scroll_up = lines_below >= lines_to_expand;
15973 }
15974 }
15975
15976 self.buffer.update(cx, |buffer, cx| {
15977 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15978 });
15979
15980 if should_scroll_up {
15981 let new_scroll_position =
15982 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15983 self.set_scroll_position(new_scroll_position, window, cx);
15984 }
15985 }
15986
15987 pub fn go_to_singleton_buffer_point(
15988 &mut self,
15989 point: Point,
15990 window: &mut Window,
15991 cx: &mut Context<Self>,
15992 ) {
15993 self.go_to_singleton_buffer_range(point..point, window, cx);
15994 }
15995
15996 pub fn go_to_singleton_buffer_range(
15997 &mut self,
15998 range: Range<Point>,
15999 window: &mut Window,
16000 cx: &mut Context<Self>,
16001 ) {
16002 let multibuffer = self.buffer().read(cx);
16003 let Some(buffer) = multibuffer.as_singleton() else {
16004 return;
16005 };
16006 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16007 return;
16008 };
16009 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16010 return;
16011 };
16012 self.change_selections(
16013 SelectionEffects::default().nav_history(true),
16014 window,
16015 cx,
16016 |s| s.select_anchor_ranges([start..end]),
16017 );
16018 }
16019
16020 pub fn go_to_diagnostic(
16021 &mut self,
16022 action: &GoToDiagnostic,
16023 window: &mut Window,
16024 cx: &mut Context<Self>,
16025 ) {
16026 if !self.diagnostics_enabled() {
16027 return;
16028 }
16029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16030 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16031 }
16032
16033 pub fn go_to_prev_diagnostic(
16034 &mut self,
16035 action: &GoToPreviousDiagnostic,
16036 window: &mut Window,
16037 cx: &mut Context<Self>,
16038 ) {
16039 if !self.diagnostics_enabled() {
16040 return;
16041 }
16042 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16043 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16044 }
16045
16046 pub fn go_to_diagnostic_impl(
16047 &mut self,
16048 direction: Direction,
16049 severity: GoToDiagnosticSeverityFilter,
16050 window: &mut Window,
16051 cx: &mut Context<Self>,
16052 ) {
16053 let buffer = self.buffer.read(cx).snapshot(cx);
16054 let selection = self.selections.newest::<usize>(cx);
16055
16056 let mut active_group_id = None;
16057 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16058 && active_group.active_range.start.to_offset(&buffer) == selection.start
16059 {
16060 active_group_id = Some(active_group.group_id);
16061 }
16062
16063 fn filtered<'a>(
16064 snapshot: EditorSnapshot,
16065 severity: GoToDiagnosticSeverityFilter,
16066 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16067 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16068 diagnostics
16069 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16070 .filter(|entry| entry.range.start != entry.range.end)
16071 .filter(|entry| !entry.diagnostic.is_unnecessary)
16072 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16073 }
16074
16075 let snapshot = self.snapshot(window, cx);
16076 let before = filtered(
16077 snapshot.clone(),
16078 severity,
16079 buffer
16080 .diagnostics_in_range(0..selection.start)
16081 .filter(|entry| entry.range.start <= selection.start),
16082 );
16083 let after = filtered(
16084 snapshot,
16085 severity,
16086 buffer
16087 .diagnostics_in_range(selection.start..buffer.len())
16088 .filter(|entry| entry.range.start >= selection.start),
16089 );
16090
16091 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16092 if direction == Direction::Prev {
16093 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16094 {
16095 for diagnostic in prev_diagnostics.into_iter().rev() {
16096 if diagnostic.range.start != selection.start
16097 || active_group_id
16098 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16099 {
16100 found = Some(diagnostic);
16101 break 'outer;
16102 }
16103 }
16104 }
16105 } else {
16106 for diagnostic in after.chain(before) {
16107 if diagnostic.range.start != selection.start
16108 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16109 {
16110 found = Some(diagnostic);
16111 break;
16112 }
16113 }
16114 }
16115 let Some(next_diagnostic) = found else {
16116 return;
16117 };
16118
16119 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16120 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16121 return;
16122 };
16123 self.change_selections(Default::default(), window, cx, |s| {
16124 s.select_ranges(vec![
16125 next_diagnostic.range.start..next_diagnostic.range.start,
16126 ])
16127 });
16128 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16129 self.refresh_edit_prediction(false, true, window, cx);
16130 }
16131
16132 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16133 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16134 let snapshot = self.snapshot(window, cx);
16135 let selection = self.selections.newest::<Point>(cx);
16136 self.go_to_hunk_before_or_after_position(
16137 &snapshot,
16138 selection.head(),
16139 Direction::Next,
16140 window,
16141 cx,
16142 );
16143 }
16144
16145 pub fn go_to_hunk_before_or_after_position(
16146 &mut self,
16147 snapshot: &EditorSnapshot,
16148 position: Point,
16149 direction: Direction,
16150 window: &mut Window,
16151 cx: &mut Context<Editor>,
16152 ) {
16153 let row = if direction == Direction::Next {
16154 self.hunk_after_position(snapshot, position)
16155 .map(|hunk| hunk.row_range.start)
16156 } else {
16157 self.hunk_before_position(snapshot, position)
16158 };
16159
16160 if let Some(row) = row {
16161 let destination = Point::new(row.0, 0);
16162 let autoscroll = Autoscroll::center();
16163
16164 self.unfold_ranges(&[destination..destination], false, false, cx);
16165 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16166 s.select_ranges([destination..destination]);
16167 });
16168 }
16169 }
16170
16171 fn hunk_after_position(
16172 &mut self,
16173 snapshot: &EditorSnapshot,
16174 position: Point,
16175 ) -> Option<MultiBufferDiffHunk> {
16176 snapshot
16177 .buffer_snapshot()
16178 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16179 .find(|hunk| hunk.row_range.start.0 > position.row)
16180 .or_else(|| {
16181 snapshot
16182 .buffer_snapshot()
16183 .diff_hunks_in_range(Point::zero()..position)
16184 .find(|hunk| hunk.row_range.end.0 < position.row)
16185 })
16186 }
16187
16188 fn go_to_prev_hunk(
16189 &mut self,
16190 _: &GoToPreviousHunk,
16191 window: &mut Window,
16192 cx: &mut Context<Self>,
16193 ) {
16194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16195 let snapshot = self.snapshot(window, cx);
16196 let selection = self.selections.newest::<Point>(cx);
16197 self.go_to_hunk_before_or_after_position(
16198 &snapshot,
16199 selection.head(),
16200 Direction::Prev,
16201 window,
16202 cx,
16203 );
16204 }
16205
16206 fn hunk_before_position(
16207 &mut self,
16208 snapshot: &EditorSnapshot,
16209 position: Point,
16210 ) -> Option<MultiBufferRow> {
16211 snapshot
16212 .buffer_snapshot()
16213 .diff_hunk_before(position)
16214 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16215 }
16216
16217 fn go_to_next_change(
16218 &mut self,
16219 _: &GoToNextChange,
16220 window: &mut Window,
16221 cx: &mut Context<Self>,
16222 ) {
16223 if let Some(selections) = self
16224 .change_list
16225 .next_change(1, Direction::Next)
16226 .map(|s| s.to_vec())
16227 {
16228 self.change_selections(Default::default(), window, cx, |s| {
16229 let map = s.display_map();
16230 s.select_display_ranges(selections.iter().map(|a| {
16231 let point = a.to_display_point(&map);
16232 point..point
16233 }))
16234 })
16235 }
16236 }
16237
16238 fn go_to_previous_change(
16239 &mut self,
16240 _: &GoToPreviousChange,
16241 window: &mut Window,
16242 cx: &mut Context<Self>,
16243 ) {
16244 if let Some(selections) = self
16245 .change_list
16246 .next_change(1, Direction::Prev)
16247 .map(|s| s.to_vec())
16248 {
16249 self.change_selections(Default::default(), window, cx, |s| {
16250 let map = s.display_map();
16251 s.select_display_ranges(selections.iter().map(|a| {
16252 let point = a.to_display_point(&map);
16253 point..point
16254 }))
16255 })
16256 }
16257 }
16258
16259 pub fn go_to_next_document_highlight(
16260 &mut self,
16261 _: &GoToNextDocumentHighlight,
16262 window: &mut Window,
16263 cx: &mut Context<Self>,
16264 ) {
16265 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16266 }
16267
16268 pub fn go_to_prev_document_highlight(
16269 &mut self,
16270 _: &GoToPreviousDocumentHighlight,
16271 window: &mut Window,
16272 cx: &mut Context<Self>,
16273 ) {
16274 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16275 }
16276
16277 pub fn go_to_document_highlight_before_or_after_position(
16278 &mut self,
16279 direction: Direction,
16280 window: &mut Window,
16281 cx: &mut Context<Editor>,
16282 ) {
16283 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16284 let snapshot = self.snapshot(window, cx);
16285 let buffer = &snapshot.buffer_snapshot();
16286 let position = self.selections.newest::<Point>(cx).head();
16287 let anchor_position = buffer.anchor_after(position);
16288
16289 // Get all document highlights (both read and write)
16290 let mut all_highlights = Vec::new();
16291
16292 if let Some((_, read_highlights)) = self
16293 .background_highlights
16294 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16295 {
16296 all_highlights.extend(read_highlights.iter());
16297 }
16298
16299 if let Some((_, write_highlights)) = self
16300 .background_highlights
16301 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16302 {
16303 all_highlights.extend(write_highlights.iter());
16304 }
16305
16306 if all_highlights.is_empty() {
16307 return;
16308 }
16309
16310 // Sort highlights by position
16311 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16312
16313 let target_highlight = match direction {
16314 Direction::Next => {
16315 // Find the first highlight after the current position
16316 all_highlights
16317 .iter()
16318 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16319 }
16320 Direction::Prev => {
16321 // Find the last highlight before the current position
16322 all_highlights
16323 .iter()
16324 .rev()
16325 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16326 }
16327 };
16328
16329 if let Some(highlight) = target_highlight {
16330 let destination = highlight.start.to_point(buffer);
16331 let autoscroll = Autoscroll::center();
16332
16333 self.unfold_ranges(&[destination..destination], false, false, cx);
16334 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16335 s.select_ranges([destination..destination]);
16336 });
16337 }
16338 }
16339
16340 fn go_to_line<T: 'static>(
16341 &mut self,
16342 position: Anchor,
16343 highlight_color: Option<Hsla>,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) {
16347 let snapshot = self.snapshot(window, cx).display_snapshot;
16348 let position = position.to_point(&snapshot.buffer_snapshot());
16349 let start = snapshot
16350 .buffer_snapshot()
16351 .clip_point(Point::new(position.row, 0), Bias::Left);
16352 let end = start + Point::new(1, 0);
16353 let start = snapshot.buffer_snapshot().anchor_before(start);
16354 let end = snapshot.buffer_snapshot().anchor_before(end);
16355
16356 self.highlight_rows::<T>(
16357 start..end,
16358 highlight_color
16359 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16360 Default::default(),
16361 cx,
16362 );
16363
16364 if self.buffer.read(cx).is_singleton() {
16365 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16366 }
16367 }
16368
16369 pub fn go_to_definition(
16370 &mut self,
16371 _: &GoToDefinition,
16372 window: &mut Window,
16373 cx: &mut Context<Self>,
16374 ) -> Task<Result<Navigated>> {
16375 let definition =
16376 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16377 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16378 cx.spawn_in(window, async move |editor, cx| {
16379 if definition.await? == Navigated::Yes {
16380 return Ok(Navigated::Yes);
16381 }
16382 match fallback_strategy {
16383 GoToDefinitionFallback::None => Ok(Navigated::No),
16384 GoToDefinitionFallback::FindAllReferences => {
16385 match editor.update_in(cx, |editor, window, cx| {
16386 editor.find_all_references(&FindAllReferences, window, cx)
16387 })? {
16388 Some(references) => references.await,
16389 None => Ok(Navigated::No),
16390 }
16391 }
16392 }
16393 })
16394 }
16395
16396 pub fn go_to_declaration(
16397 &mut self,
16398 _: &GoToDeclaration,
16399 window: &mut Window,
16400 cx: &mut Context<Self>,
16401 ) -> Task<Result<Navigated>> {
16402 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16403 }
16404
16405 pub fn go_to_declaration_split(
16406 &mut self,
16407 _: &GoToDeclaration,
16408 window: &mut Window,
16409 cx: &mut Context<Self>,
16410 ) -> Task<Result<Navigated>> {
16411 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16412 }
16413
16414 pub fn go_to_implementation(
16415 &mut self,
16416 _: &GoToImplementation,
16417 window: &mut Window,
16418 cx: &mut Context<Self>,
16419 ) -> Task<Result<Navigated>> {
16420 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16421 }
16422
16423 pub fn go_to_implementation_split(
16424 &mut self,
16425 _: &GoToImplementationSplit,
16426 window: &mut Window,
16427 cx: &mut Context<Self>,
16428 ) -> Task<Result<Navigated>> {
16429 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16430 }
16431
16432 pub fn go_to_type_definition(
16433 &mut self,
16434 _: &GoToTypeDefinition,
16435 window: &mut Window,
16436 cx: &mut Context<Self>,
16437 ) -> Task<Result<Navigated>> {
16438 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16439 }
16440
16441 pub fn go_to_definition_split(
16442 &mut self,
16443 _: &GoToDefinitionSplit,
16444 window: &mut Window,
16445 cx: &mut Context<Self>,
16446 ) -> Task<Result<Navigated>> {
16447 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16448 }
16449
16450 pub fn go_to_type_definition_split(
16451 &mut self,
16452 _: &GoToTypeDefinitionSplit,
16453 window: &mut Window,
16454 cx: &mut Context<Self>,
16455 ) -> Task<Result<Navigated>> {
16456 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16457 }
16458
16459 fn go_to_definition_of_kind(
16460 &mut self,
16461 kind: GotoDefinitionKind,
16462 split: bool,
16463 window: &mut Window,
16464 cx: &mut Context<Self>,
16465 ) -> Task<Result<Navigated>> {
16466 let Some(provider) = self.semantics_provider.clone() else {
16467 return Task::ready(Ok(Navigated::No));
16468 };
16469 let head = self.selections.newest::<usize>(cx).head();
16470 let buffer = self.buffer.read(cx);
16471 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16472 return Task::ready(Ok(Navigated::No));
16473 };
16474 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16475 return Task::ready(Ok(Navigated::No));
16476 };
16477
16478 cx.spawn_in(window, async move |editor, cx| {
16479 let Some(definitions) = definitions.await? else {
16480 return Ok(Navigated::No);
16481 };
16482 let navigated = editor
16483 .update_in(cx, |editor, window, cx| {
16484 editor.navigate_to_hover_links(
16485 Some(kind),
16486 definitions
16487 .into_iter()
16488 .filter(|location| {
16489 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16490 })
16491 .map(HoverLink::Text)
16492 .collect::<Vec<_>>(),
16493 split,
16494 window,
16495 cx,
16496 )
16497 })?
16498 .await?;
16499 anyhow::Ok(navigated)
16500 })
16501 }
16502
16503 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16504 let selection = self.selections.newest_anchor();
16505 let head = selection.head();
16506 let tail = selection.tail();
16507
16508 let Some((buffer, start_position)) =
16509 self.buffer.read(cx).text_anchor_for_position(head, cx)
16510 else {
16511 return;
16512 };
16513
16514 let end_position = if head != tail {
16515 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16516 return;
16517 };
16518 Some(pos)
16519 } else {
16520 None
16521 };
16522
16523 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16524 let url = if let Some(end_pos) = end_position {
16525 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16526 } else {
16527 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16528 };
16529
16530 if let Some(url) = url {
16531 cx.update(|window, cx| {
16532 if parse_zed_link(&url, cx).is_some() {
16533 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16534 } else {
16535 cx.open_url(&url);
16536 }
16537 })?;
16538 }
16539
16540 anyhow::Ok(())
16541 });
16542
16543 url_finder.detach();
16544 }
16545
16546 pub fn open_selected_filename(
16547 &mut self,
16548 _: &OpenSelectedFilename,
16549 window: &mut Window,
16550 cx: &mut Context<Self>,
16551 ) {
16552 let Some(workspace) = self.workspace() else {
16553 return;
16554 };
16555
16556 let position = self.selections.newest_anchor().head();
16557
16558 let Some((buffer, buffer_position)) =
16559 self.buffer.read(cx).text_anchor_for_position(position, cx)
16560 else {
16561 return;
16562 };
16563
16564 let project = self.project.clone();
16565
16566 cx.spawn_in(window, async move |_, cx| {
16567 let result = find_file(&buffer, project, buffer_position, cx).await;
16568
16569 if let Some((_, path)) = result {
16570 workspace
16571 .update_in(cx, |workspace, window, cx| {
16572 workspace.open_resolved_path(path, window, cx)
16573 })?
16574 .await?;
16575 }
16576 anyhow::Ok(())
16577 })
16578 .detach();
16579 }
16580
16581 pub(crate) fn navigate_to_hover_links(
16582 &mut self,
16583 kind: Option<GotoDefinitionKind>,
16584 definitions: Vec<HoverLink>,
16585 split: bool,
16586 window: &mut Window,
16587 cx: &mut Context<Editor>,
16588 ) -> Task<Result<Navigated>> {
16589 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16590 let mut first_url_or_file = None;
16591 let definitions: Vec<_> = definitions
16592 .into_iter()
16593 .filter_map(|def| match def {
16594 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16595 HoverLink::InlayHint(lsp_location, server_id) => {
16596 let computation =
16597 self.compute_target_location(lsp_location, server_id, window, cx);
16598 Some(cx.background_spawn(computation))
16599 }
16600 HoverLink::Url(url) => {
16601 first_url_or_file = Some(Either::Left(url));
16602 None
16603 }
16604 HoverLink::File(path) => {
16605 first_url_or_file = Some(Either::Right(path));
16606 None
16607 }
16608 })
16609 .collect();
16610
16611 let workspace = self.workspace();
16612
16613 cx.spawn_in(window, async move |editor, cx| {
16614 let locations: Vec<Location> = future::join_all(definitions)
16615 .await
16616 .into_iter()
16617 .filter_map(|location| location.transpose())
16618 .collect::<Result<_>>()
16619 .context("location tasks")?;
16620 let mut locations = cx.update(|_, cx| {
16621 locations
16622 .into_iter()
16623 .map(|location| {
16624 let buffer = location.buffer.read(cx);
16625 (location.buffer, location.range.to_point(buffer))
16626 })
16627 .into_group_map()
16628 })?;
16629 let mut num_locations = 0;
16630 for ranges in locations.values_mut() {
16631 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16632 ranges.dedup();
16633 num_locations += ranges.len();
16634 }
16635
16636 if num_locations > 1 {
16637 let Some(workspace) = workspace else {
16638 return Ok(Navigated::No);
16639 };
16640
16641 let tab_kind = match kind {
16642 Some(GotoDefinitionKind::Implementation) => "Implementations",
16643 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16644 Some(GotoDefinitionKind::Declaration) => "Declarations",
16645 Some(GotoDefinitionKind::Type) => "Types",
16646 };
16647 let title = editor
16648 .update_in(cx, |_, _, cx| {
16649 let target = locations
16650 .iter()
16651 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16652 .map(|(buffer, location)| {
16653 buffer
16654 .read(cx)
16655 .text_for_range(location.clone())
16656 .collect::<String>()
16657 })
16658 .filter(|text| !text.contains('\n'))
16659 .unique()
16660 .take(3)
16661 .join(", ");
16662 if target.is_empty() {
16663 tab_kind.to_owned()
16664 } else {
16665 format!("{tab_kind} for {target}")
16666 }
16667 })
16668 .context("buffer title")?;
16669
16670 let opened = workspace
16671 .update_in(cx, |workspace, window, cx| {
16672 Self::open_locations_in_multibuffer(
16673 workspace,
16674 locations,
16675 title,
16676 split,
16677 MultibufferSelectionMode::First,
16678 window,
16679 cx,
16680 )
16681 })
16682 .is_ok();
16683
16684 anyhow::Ok(Navigated::from_bool(opened))
16685 } else if num_locations == 0 {
16686 // If there is one url or file, open it directly
16687 match first_url_or_file {
16688 Some(Either::Left(url)) => {
16689 cx.update(|_, cx| cx.open_url(&url))?;
16690 Ok(Navigated::Yes)
16691 }
16692 Some(Either::Right(path)) => {
16693 let Some(workspace) = workspace else {
16694 return Ok(Navigated::No);
16695 };
16696
16697 workspace
16698 .update_in(cx, |workspace, window, cx| {
16699 workspace.open_resolved_path(path, window, cx)
16700 })?
16701 .await?;
16702 Ok(Navigated::Yes)
16703 }
16704 None => Ok(Navigated::No),
16705 }
16706 } else {
16707 let Some(workspace) = workspace else {
16708 return Ok(Navigated::No);
16709 };
16710
16711 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16712 let target_range = target_ranges.first().unwrap().clone();
16713
16714 editor.update_in(cx, |editor, window, cx| {
16715 let range = target_range.to_point(target_buffer.read(cx));
16716 let range = editor.range_for_match(&range);
16717 let range = collapse_multiline_range(range);
16718
16719 if !split
16720 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16721 {
16722 editor.go_to_singleton_buffer_range(range, window, cx);
16723 } else {
16724 let pane = workspace.read(cx).active_pane().clone();
16725 window.defer(cx, move |window, cx| {
16726 let target_editor: Entity<Self> =
16727 workspace.update(cx, |workspace, cx| {
16728 let pane = if split {
16729 workspace.adjacent_pane(window, cx)
16730 } else {
16731 workspace.active_pane().clone()
16732 };
16733
16734 workspace.open_project_item(
16735 pane,
16736 target_buffer.clone(),
16737 true,
16738 true,
16739 window,
16740 cx,
16741 )
16742 });
16743 target_editor.update(cx, |target_editor, cx| {
16744 // When selecting a definition in a different buffer, disable the nav history
16745 // to avoid creating a history entry at the previous cursor location.
16746 pane.update(cx, |pane, _| pane.disable_history());
16747 target_editor.go_to_singleton_buffer_range(range, window, cx);
16748 pane.update(cx, |pane, _| pane.enable_history());
16749 });
16750 });
16751 }
16752 Navigated::Yes
16753 })
16754 }
16755 })
16756 }
16757
16758 fn compute_target_location(
16759 &self,
16760 lsp_location: lsp::Location,
16761 server_id: LanguageServerId,
16762 window: &mut Window,
16763 cx: &mut Context<Self>,
16764 ) -> Task<anyhow::Result<Option<Location>>> {
16765 let Some(project) = self.project.clone() else {
16766 return Task::ready(Ok(None));
16767 };
16768
16769 cx.spawn_in(window, async move |editor, cx| {
16770 let location_task = editor.update(cx, |_, cx| {
16771 project.update(cx, |project, cx| {
16772 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16773 })
16774 })?;
16775 let location = Some({
16776 let target_buffer_handle = location_task.await.context("open local buffer")?;
16777 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16778 let target_start = target_buffer
16779 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16780 let target_end = target_buffer
16781 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16782 target_buffer.anchor_after(target_start)
16783 ..target_buffer.anchor_before(target_end)
16784 })?;
16785 Location {
16786 buffer: target_buffer_handle,
16787 range,
16788 }
16789 });
16790 Ok(location)
16791 })
16792 }
16793
16794 pub fn find_all_references(
16795 &mut self,
16796 _: &FindAllReferences,
16797 window: &mut Window,
16798 cx: &mut Context<Self>,
16799 ) -> Option<Task<Result<Navigated>>> {
16800 let selection = self.selections.newest::<usize>(cx);
16801 let multi_buffer = self.buffer.read(cx);
16802 let head = selection.head();
16803
16804 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16805 let head_anchor = multi_buffer_snapshot.anchor_at(
16806 head,
16807 if head < selection.tail() {
16808 Bias::Right
16809 } else {
16810 Bias::Left
16811 },
16812 );
16813
16814 match self
16815 .find_all_references_task_sources
16816 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16817 {
16818 Ok(_) => {
16819 log::info!(
16820 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16821 );
16822 return None;
16823 }
16824 Err(i) => {
16825 self.find_all_references_task_sources.insert(i, head_anchor);
16826 }
16827 }
16828
16829 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16830 let workspace = self.workspace()?;
16831 let project = workspace.read(cx).project().clone();
16832 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16833 Some(cx.spawn_in(window, async move |editor, cx| {
16834 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16835 if let Ok(i) = editor
16836 .find_all_references_task_sources
16837 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16838 {
16839 editor.find_all_references_task_sources.remove(i);
16840 }
16841 });
16842
16843 let Some(locations) = references.await? else {
16844 return anyhow::Ok(Navigated::No);
16845 };
16846 let mut locations = cx.update(|_, cx| {
16847 locations
16848 .into_iter()
16849 .map(|location| {
16850 let buffer = location.buffer.read(cx);
16851 (location.buffer, location.range.to_point(buffer))
16852 })
16853 .into_group_map()
16854 })?;
16855 if locations.is_empty() {
16856 return anyhow::Ok(Navigated::No);
16857 }
16858 for ranges in locations.values_mut() {
16859 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16860 ranges.dedup();
16861 }
16862
16863 workspace.update_in(cx, |workspace, window, cx| {
16864 let target = locations
16865 .iter()
16866 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16867 .map(|(buffer, location)| {
16868 buffer
16869 .read(cx)
16870 .text_for_range(location.clone())
16871 .collect::<String>()
16872 })
16873 .filter(|text| !text.contains('\n'))
16874 .unique()
16875 .take(3)
16876 .join(", ");
16877 let title = if target.is_empty() {
16878 "References".to_owned()
16879 } else {
16880 format!("References to {target}")
16881 };
16882 Self::open_locations_in_multibuffer(
16883 workspace,
16884 locations,
16885 title,
16886 false,
16887 MultibufferSelectionMode::First,
16888 window,
16889 cx,
16890 );
16891 Navigated::Yes
16892 })
16893 }))
16894 }
16895
16896 /// Opens a multibuffer with the given project locations in it
16897 pub fn open_locations_in_multibuffer(
16898 workspace: &mut Workspace,
16899 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16900 title: String,
16901 split: bool,
16902 multibuffer_selection_mode: MultibufferSelectionMode,
16903 window: &mut Window,
16904 cx: &mut Context<Workspace>,
16905 ) {
16906 if locations.is_empty() {
16907 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16908 return;
16909 }
16910
16911 let capability = workspace.project().read(cx).capability();
16912 let mut ranges = <Vec<Range<Anchor>>>::new();
16913
16914 // a key to find existing multibuffer editors with the same set of locations
16915 // to prevent us from opening more and more multibuffer tabs for searches and the like
16916 let mut key = (title.clone(), vec![]);
16917 let excerpt_buffer = cx.new(|cx| {
16918 let key = &mut key.1;
16919 let mut multibuffer = MultiBuffer::new(capability);
16920 for (buffer, mut ranges_for_buffer) in locations {
16921 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16922 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16923 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16924 PathKey::for_buffer(&buffer, cx),
16925 buffer.clone(),
16926 ranges_for_buffer,
16927 multibuffer_context_lines(cx),
16928 cx,
16929 );
16930 ranges.extend(new_ranges)
16931 }
16932
16933 multibuffer.with_title(title)
16934 });
16935 let existing = workspace.active_pane().update(cx, |pane, cx| {
16936 pane.items()
16937 .filter_map(|item| item.downcast::<Editor>())
16938 .find(|editor| {
16939 editor
16940 .read(cx)
16941 .lookup_key
16942 .as_ref()
16943 .and_then(|it| {
16944 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16945 })
16946 .is_some_and(|it| *it == key)
16947 })
16948 });
16949 let editor = existing.unwrap_or_else(|| {
16950 cx.new(|cx| {
16951 let mut editor = Editor::for_multibuffer(
16952 excerpt_buffer,
16953 Some(workspace.project().clone()),
16954 window,
16955 cx,
16956 );
16957 editor.lookup_key = Some(Box::new(key));
16958 editor
16959 })
16960 });
16961 editor.update(cx, |editor, cx| {
16962 match multibuffer_selection_mode {
16963 MultibufferSelectionMode::First => {
16964 if let Some(first_range) = ranges.first() {
16965 editor.change_selections(
16966 SelectionEffects::no_scroll(),
16967 window,
16968 cx,
16969 |selections| {
16970 selections.clear_disjoint();
16971 selections
16972 .select_anchor_ranges(std::iter::once(first_range.clone()));
16973 },
16974 );
16975 }
16976 editor.highlight_background::<Self>(
16977 &ranges,
16978 |theme| theme.colors().editor_highlighted_line_background,
16979 cx,
16980 );
16981 }
16982 MultibufferSelectionMode::All => {
16983 editor.change_selections(
16984 SelectionEffects::no_scroll(),
16985 window,
16986 cx,
16987 |selections| {
16988 selections.clear_disjoint();
16989 selections.select_anchor_ranges(ranges);
16990 },
16991 );
16992 }
16993 }
16994 editor.register_buffers_with_language_servers(cx);
16995 });
16996
16997 let item = Box::new(editor);
16998 let item_id = item.item_id();
16999
17000 if split {
17001 let pane = workspace.adjacent_pane(window, cx);
17002 workspace.add_item(pane, item, None, true, true, window, cx);
17003 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17004 let (preview_item_id, preview_item_idx) =
17005 workspace.active_pane().read_with(cx, |pane, _| {
17006 (pane.preview_item_id(), pane.preview_item_idx())
17007 });
17008
17009 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17010
17011 if let Some(preview_item_id) = preview_item_id {
17012 workspace.active_pane().update(cx, |pane, cx| {
17013 pane.remove_item(preview_item_id, false, false, window, cx);
17014 });
17015 }
17016 } else {
17017 workspace.add_item_to_active_pane(item, None, true, window, cx);
17018 }
17019 workspace.active_pane().update(cx, |pane, cx| {
17020 pane.set_preview_item_id(Some(item_id), cx);
17021 });
17022 }
17023
17024 pub fn rename(
17025 &mut self,
17026 _: &Rename,
17027 window: &mut Window,
17028 cx: &mut Context<Self>,
17029 ) -> Option<Task<Result<()>>> {
17030 use language::ToOffset as _;
17031
17032 let provider = self.semantics_provider.clone()?;
17033 let selection = self.selections.newest_anchor().clone();
17034 let (cursor_buffer, cursor_buffer_position) = self
17035 .buffer
17036 .read(cx)
17037 .text_anchor_for_position(selection.head(), cx)?;
17038 let (tail_buffer, cursor_buffer_position_end) = self
17039 .buffer
17040 .read(cx)
17041 .text_anchor_for_position(selection.tail(), cx)?;
17042 if tail_buffer != cursor_buffer {
17043 return None;
17044 }
17045
17046 let snapshot = cursor_buffer.read(cx).snapshot();
17047 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17048 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17049 let prepare_rename = provider
17050 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17051 .unwrap_or_else(|| Task::ready(Ok(None)));
17052 drop(snapshot);
17053
17054 Some(cx.spawn_in(window, async move |this, cx| {
17055 let rename_range = if let Some(range) = prepare_rename.await? {
17056 Some(range)
17057 } else {
17058 this.update(cx, |this, cx| {
17059 let buffer = this.buffer.read(cx).snapshot(cx);
17060 let mut buffer_highlights = this
17061 .document_highlights_for_position(selection.head(), &buffer)
17062 .filter(|highlight| {
17063 highlight.start.excerpt_id == selection.head().excerpt_id
17064 && highlight.end.excerpt_id == selection.head().excerpt_id
17065 });
17066 buffer_highlights
17067 .next()
17068 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17069 })?
17070 };
17071 if let Some(rename_range) = rename_range {
17072 this.update_in(cx, |this, window, cx| {
17073 let snapshot = cursor_buffer.read(cx).snapshot();
17074 let rename_buffer_range = rename_range.to_offset(&snapshot);
17075 let cursor_offset_in_rename_range =
17076 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17077 let cursor_offset_in_rename_range_end =
17078 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17079
17080 this.take_rename(false, window, cx);
17081 let buffer = this.buffer.read(cx).read(cx);
17082 let cursor_offset = selection.head().to_offset(&buffer);
17083 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17084 let rename_end = rename_start + rename_buffer_range.len();
17085 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17086 let mut old_highlight_id = None;
17087 let old_name: Arc<str> = buffer
17088 .chunks(rename_start..rename_end, true)
17089 .map(|chunk| {
17090 if old_highlight_id.is_none() {
17091 old_highlight_id = chunk.syntax_highlight_id;
17092 }
17093 chunk.text
17094 })
17095 .collect::<String>()
17096 .into();
17097
17098 drop(buffer);
17099
17100 // Position the selection in the rename editor so that it matches the current selection.
17101 this.show_local_selections = false;
17102 let rename_editor = cx.new(|cx| {
17103 let mut editor = Editor::single_line(window, cx);
17104 editor.buffer.update(cx, |buffer, cx| {
17105 buffer.edit([(0..0, old_name.clone())], None, cx)
17106 });
17107 let rename_selection_range = match cursor_offset_in_rename_range
17108 .cmp(&cursor_offset_in_rename_range_end)
17109 {
17110 Ordering::Equal => {
17111 editor.select_all(&SelectAll, window, cx);
17112 return editor;
17113 }
17114 Ordering::Less => {
17115 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17116 }
17117 Ordering::Greater => {
17118 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17119 }
17120 };
17121 if rename_selection_range.end > old_name.len() {
17122 editor.select_all(&SelectAll, window, cx);
17123 } else {
17124 editor.change_selections(Default::default(), window, cx, |s| {
17125 s.select_ranges([rename_selection_range]);
17126 });
17127 }
17128 editor
17129 });
17130 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17131 if e == &EditorEvent::Focused {
17132 cx.emit(EditorEvent::FocusedIn)
17133 }
17134 })
17135 .detach();
17136
17137 let write_highlights =
17138 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17139 let read_highlights =
17140 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17141 let ranges = write_highlights
17142 .iter()
17143 .flat_map(|(_, ranges)| ranges.iter())
17144 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17145 .cloned()
17146 .collect();
17147
17148 this.highlight_text::<Rename>(
17149 ranges,
17150 HighlightStyle {
17151 fade_out: Some(0.6),
17152 ..Default::default()
17153 },
17154 cx,
17155 );
17156 let rename_focus_handle = rename_editor.focus_handle(cx);
17157 window.focus(&rename_focus_handle);
17158 let block_id = this.insert_blocks(
17159 [BlockProperties {
17160 style: BlockStyle::Flex,
17161 placement: BlockPlacement::Below(range.start),
17162 height: Some(1),
17163 render: Arc::new({
17164 let rename_editor = rename_editor.clone();
17165 move |cx: &mut BlockContext| {
17166 let mut text_style = cx.editor_style.text.clone();
17167 if let Some(highlight_style) = old_highlight_id
17168 .and_then(|h| h.style(&cx.editor_style.syntax))
17169 {
17170 text_style = text_style.highlight(highlight_style);
17171 }
17172 div()
17173 .block_mouse_except_scroll()
17174 .pl(cx.anchor_x)
17175 .child(EditorElement::new(
17176 &rename_editor,
17177 EditorStyle {
17178 background: cx.theme().system().transparent,
17179 local_player: cx.editor_style.local_player,
17180 text: text_style,
17181 scrollbar_width: cx.editor_style.scrollbar_width,
17182 syntax: cx.editor_style.syntax.clone(),
17183 status: cx.editor_style.status.clone(),
17184 inlay_hints_style: HighlightStyle {
17185 font_weight: Some(FontWeight::BOLD),
17186 ..make_inlay_hints_style(cx.app)
17187 },
17188 edit_prediction_styles: make_suggestion_styles(
17189 cx.app,
17190 ),
17191 ..EditorStyle::default()
17192 },
17193 ))
17194 .into_any_element()
17195 }
17196 }),
17197 priority: 0,
17198 }],
17199 Some(Autoscroll::fit()),
17200 cx,
17201 )[0];
17202 this.pending_rename = Some(RenameState {
17203 range,
17204 old_name,
17205 editor: rename_editor,
17206 block_id,
17207 });
17208 })?;
17209 }
17210
17211 Ok(())
17212 }))
17213 }
17214
17215 pub fn confirm_rename(
17216 &mut self,
17217 _: &ConfirmRename,
17218 window: &mut Window,
17219 cx: &mut Context<Self>,
17220 ) -> Option<Task<Result<()>>> {
17221 let rename = self.take_rename(false, window, cx)?;
17222 let workspace = self.workspace()?.downgrade();
17223 let (buffer, start) = self
17224 .buffer
17225 .read(cx)
17226 .text_anchor_for_position(rename.range.start, cx)?;
17227 let (end_buffer, _) = self
17228 .buffer
17229 .read(cx)
17230 .text_anchor_for_position(rename.range.end, cx)?;
17231 if buffer != end_buffer {
17232 return None;
17233 }
17234
17235 let old_name = rename.old_name;
17236 let new_name = rename.editor.read(cx).text(cx);
17237
17238 let rename = self.semantics_provider.as_ref()?.perform_rename(
17239 &buffer,
17240 start,
17241 new_name.clone(),
17242 cx,
17243 )?;
17244
17245 Some(cx.spawn_in(window, async move |editor, cx| {
17246 let project_transaction = rename.await?;
17247 Self::open_project_transaction(
17248 &editor,
17249 workspace,
17250 project_transaction,
17251 format!("Rename: {} → {}", old_name, new_name),
17252 cx,
17253 )
17254 .await?;
17255
17256 editor.update(cx, |editor, cx| {
17257 editor.refresh_document_highlights(cx);
17258 })?;
17259 Ok(())
17260 }))
17261 }
17262
17263 fn take_rename(
17264 &mut self,
17265 moving_cursor: bool,
17266 window: &mut Window,
17267 cx: &mut Context<Self>,
17268 ) -> Option<RenameState> {
17269 let rename = self.pending_rename.take()?;
17270 if rename.editor.focus_handle(cx).is_focused(window) {
17271 window.focus(&self.focus_handle);
17272 }
17273
17274 self.remove_blocks(
17275 [rename.block_id].into_iter().collect(),
17276 Some(Autoscroll::fit()),
17277 cx,
17278 );
17279 self.clear_highlights::<Rename>(cx);
17280 self.show_local_selections = true;
17281
17282 if moving_cursor {
17283 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17284 editor.selections.newest::<usize>(cx).head()
17285 });
17286
17287 // Update the selection to match the position of the selection inside
17288 // the rename editor.
17289 let snapshot = self.buffer.read(cx).read(cx);
17290 let rename_range = rename.range.to_offset(&snapshot);
17291 let cursor_in_editor = snapshot
17292 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17293 .min(rename_range.end);
17294 drop(snapshot);
17295
17296 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17297 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17298 });
17299 } else {
17300 self.refresh_document_highlights(cx);
17301 }
17302
17303 Some(rename)
17304 }
17305
17306 pub fn pending_rename(&self) -> Option<&RenameState> {
17307 self.pending_rename.as_ref()
17308 }
17309
17310 fn format(
17311 &mut self,
17312 _: &Format,
17313 window: &mut Window,
17314 cx: &mut Context<Self>,
17315 ) -> Option<Task<Result<()>>> {
17316 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17317
17318 let project = match &self.project {
17319 Some(project) => project.clone(),
17320 None => return None,
17321 };
17322
17323 Some(self.perform_format(
17324 project,
17325 FormatTrigger::Manual,
17326 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17327 window,
17328 cx,
17329 ))
17330 }
17331
17332 fn format_selections(
17333 &mut self,
17334 _: &FormatSelections,
17335 window: &mut Window,
17336 cx: &mut Context<Self>,
17337 ) -> Option<Task<Result<()>>> {
17338 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17339
17340 let project = match &self.project {
17341 Some(project) => project.clone(),
17342 None => return None,
17343 };
17344
17345 let ranges = self
17346 .selections
17347 .all_adjusted(cx)
17348 .into_iter()
17349 .map(|selection| selection.range())
17350 .collect_vec();
17351
17352 Some(self.perform_format(
17353 project,
17354 FormatTrigger::Manual,
17355 FormatTarget::Ranges(ranges),
17356 window,
17357 cx,
17358 ))
17359 }
17360
17361 fn perform_format(
17362 &mut self,
17363 project: Entity<Project>,
17364 trigger: FormatTrigger,
17365 target: FormatTarget,
17366 window: &mut Window,
17367 cx: &mut Context<Self>,
17368 ) -> Task<Result<()>> {
17369 let buffer = self.buffer.clone();
17370 let (buffers, target) = match target {
17371 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17372 FormatTarget::Ranges(selection_ranges) => {
17373 let multi_buffer = buffer.read(cx);
17374 let snapshot = multi_buffer.read(cx);
17375 let mut buffers = HashSet::default();
17376 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17377 BTreeMap::new();
17378 for selection_range in selection_ranges {
17379 for (buffer, buffer_range, _) in
17380 snapshot.range_to_buffer_ranges(selection_range)
17381 {
17382 let buffer_id = buffer.remote_id();
17383 let start = buffer.anchor_before(buffer_range.start);
17384 let end = buffer.anchor_after(buffer_range.end);
17385 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17386 buffer_id_to_ranges
17387 .entry(buffer_id)
17388 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17389 .or_insert_with(|| vec![start..end]);
17390 }
17391 }
17392 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17393 }
17394 };
17395
17396 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17397 let selections_prev = transaction_id_prev
17398 .and_then(|transaction_id_prev| {
17399 // default to selections as they were after the last edit, if we have them,
17400 // instead of how they are now.
17401 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17402 // will take you back to where you made the last edit, instead of staying where you scrolled
17403 self.selection_history
17404 .transaction(transaction_id_prev)
17405 .map(|t| t.0.clone())
17406 })
17407 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17408
17409 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17410 let format = project.update(cx, |project, cx| {
17411 project.format(buffers, target, true, trigger, cx)
17412 });
17413
17414 cx.spawn_in(window, async move |editor, cx| {
17415 let transaction = futures::select_biased! {
17416 transaction = format.log_err().fuse() => transaction,
17417 () = timeout => {
17418 log::warn!("timed out waiting for formatting");
17419 None
17420 }
17421 };
17422
17423 buffer
17424 .update(cx, |buffer, cx| {
17425 if let Some(transaction) = transaction
17426 && !buffer.is_singleton()
17427 {
17428 buffer.push_transaction(&transaction.0, cx);
17429 }
17430 cx.notify();
17431 })
17432 .ok();
17433
17434 if let Some(transaction_id_now) =
17435 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17436 {
17437 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17438 if has_new_transaction {
17439 _ = editor.update(cx, |editor, _| {
17440 editor
17441 .selection_history
17442 .insert_transaction(transaction_id_now, selections_prev);
17443 });
17444 }
17445 }
17446
17447 Ok(())
17448 })
17449 }
17450
17451 fn organize_imports(
17452 &mut self,
17453 _: &OrganizeImports,
17454 window: &mut Window,
17455 cx: &mut Context<Self>,
17456 ) -> Option<Task<Result<()>>> {
17457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17458 let project = match &self.project {
17459 Some(project) => project.clone(),
17460 None => return None,
17461 };
17462 Some(self.perform_code_action_kind(
17463 project,
17464 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17465 window,
17466 cx,
17467 ))
17468 }
17469
17470 fn perform_code_action_kind(
17471 &mut self,
17472 project: Entity<Project>,
17473 kind: CodeActionKind,
17474 window: &mut Window,
17475 cx: &mut Context<Self>,
17476 ) -> Task<Result<()>> {
17477 let buffer = self.buffer.clone();
17478 let buffers = buffer.read(cx).all_buffers();
17479 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17480 let apply_action = project.update(cx, |project, cx| {
17481 project.apply_code_action_kind(buffers, kind, true, cx)
17482 });
17483 cx.spawn_in(window, async move |_, cx| {
17484 let transaction = futures::select_biased! {
17485 () = timeout => {
17486 log::warn!("timed out waiting for executing code action");
17487 None
17488 }
17489 transaction = apply_action.log_err().fuse() => transaction,
17490 };
17491 buffer
17492 .update(cx, |buffer, cx| {
17493 // check if we need this
17494 if let Some(transaction) = transaction
17495 && !buffer.is_singleton()
17496 {
17497 buffer.push_transaction(&transaction.0, cx);
17498 }
17499 cx.notify();
17500 })
17501 .ok();
17502 Ok(())
17503 })
17504 }
17505
17506 pub fn restart_language_server(
17507 &mut self,
17508 _: &RestartLanguageServer,
17509 _: &mut Window,
17510 cx: &mut Context<Self>,
17511 ) {
17512 if let Some(project) = self.project.clone() {
17513 self.buffer.update(cx, |multi_buffer, cx| {
17514 project.update(cx, |project, cx| {
17515 project.restart_language_servers_for_buffers(
17516 multi_buffer.all_buffers().into_iter().collect(),
17517 HashSet::default(),
17518 cx,
17519 );
17520 });
17521 })
17522 }
17523 }
17524
17525 pub fn stop_language_server(
17526 &mut self,
17527 _: &StopLanguageServer,
17528 _: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) {
17531 if let Some(project) = self.project.clone() {
17532 self.buffer.update(cx, |multi_buffer, cx| {
17533 project.update(cx, |project, cx| {
17534 project.stop_language_servers_for_buffers(
17535 multi_buffer.all_buffers().into_iter().collect(),
17536 HashSet::default(),
17537 cx,
17538 );
17539 cx.emit(project::Event::RefreshInlayHints);
17540 });
17541 });
17542 }
17543 }
17544
17545 fn cancel_language_server_work(
17546 workspace: &mut Workspace,
17547 _: &actions::CancelLanguageServerWork,
17548 _: &mut Window,
17549 cx: &mut Context<Workspace>,
17550 ) {
17551 let project = workspace.project();
17552 let buffers = workspace
17553 .active_item(cx)
17554 .and_then(|item| item.act_as::<Editor>(cx))
17555 .map_or(HashSet::default(), |editor| {
17556 editor.read(cx).buffer.read(cx).all_buffers()
17557 });
17558 project.update(cx, |project, cx| {
17559 project.cancel_language_server_work_for_buffers(buffers, cx);
17560 });
17561 }
17562
17563 fn show_character_palette(
17564 &mut self,
17565 _: &ShowCharacterPalette,
17566 window: &mut Window,
17567 _: &mut Context<Self>,
17568 ) {
17569 window.show_character_palette();
17570 }
17571
17572 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17573 if !self.diagnostics_enabled() {
17574 return;
17575 }
17576
17577 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17578 let buffer = self.buffer.read(cx).snapshot(cx);
17579 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17580 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17581 let is_valid = buffer
17582 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17583 .any(|entry| {
17584 entry.diagnostic.is_primary
17585 && !entry.range.is_empty()
17586 && entry.range.start == primary_range_start
17587 && entry.diagnostic.message == active_diagnostics.active_message
17588 });
17589
17590 if !is_valid {
17591 self.dismiss_diagnostics(cx);
17592 }
17593 }
17594 }
17595
17596 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17597 match &self.active_diagnostics {
17598 ActiveDiagnostic::Group(group) => Some(group),
17599 _ => None,
17600 }
17601 }
17602
17603 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17604 if !self.diagnostics_enabled() {
17605 return;
17606 }
17607 self.dismiss_diagnostics(cx);
17608 self.active_diagnostics = ActiveDiagnostic::All;
17609 }
17610
17611 fn activate_diagnostics(
17612 &mut self,
17613 buffer_id: BufferId,
17614 diagnostic: DiagnosticEntryRef<'_, usize>,
17615 window: &mut Window,
17616 cx: &mut Context<Self>,
17617 ) {
17618 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17619 return;
17620 }
17621 self.dismiss_diagnostics(cx);
17622 let snapshot = self.snapshot(window, cx);
17623 let buffer = self.buffer.read(cx).snapshot(cx);
17624 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17625 return;
17626 };
17627
17628 let diagnostic_group = buffer
17629 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17630 .collect::<Vec<_>>();
17631
17632 let blocks =
17633 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17634
17635 let blocks = self.display_map.update(cx, |display_map, cx| {
17636 display_map.insert_blocks(blocks, cx).into_iter().collect()
17637 });
17638 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17639 active_range: buffer.anchor_before(diagnostic.range.start)
17640 ..buffer.anchor_after(diagnostic.range.end),
17641 active_message: diagnostic.diagnostic.message.clone(),
17642 group_id: diagnostic.diagnostic.group_id,
17643 blocks,
17644 });
17645 cx.notify();
17646 }
17647
17648 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17649 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17650 return;
17651 };
17652
17653 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17654 if let ActiveDiagnostic::Group(group) = prev {
17655 self.display_map.update(cx, |display_map, cx| {
17656 display_map.remove_blocks(group.blocks, cx);
17657 });
17658 cx.notify();
17659 }
17660 }
17661
17662 /// Disable inline diagnostics rendering for this editor.
17663 pub fn disable_inline_diagnostics(&mut self) {
17664 self.inline_diagnostics_enabled = false;
17665 self.inline_diagnostics_update = Task::ready(());
17666 self.inline_diagnostics.clear();
17667 }
17668
17669 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17670 self.diagnostics_enabled = false;
17671 self.dismiss_diagnostics(cx);
17672 self.inline_diagnostics_update = Task::ready(());
17673 self.inline_diagnostics.clear();
17674 }
17675
17676 pub fn disable_word_completions(&mut self) {
17677 self.word_completions_enabled = false;
17678 }
17679
17680 pub fn diagnostics_enabled(&self) -> bool {
17681 self.diagnostics_enabled && self.mode.is_full()
17682 }
17683
17684 pub fn inline_diagnostics_enabled(&self) -> bool {
17685 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17686 }
17687
17688 pub fn show_inline_diagnostics(&self) -> bool {
17689 self.show_inline_diagnostics
17690 }
17691
17692 pub fn toggle_inline_diagnostics(
17693 &mut self,
17694 _: &ToggleInlineDiagnostics,
17695 window: &mut Window,
17696 cx: &mut Context<Editor>,
17697 ) {
17698 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17699 self.refresh_inline_diagnostics(false, window, cx);
17700 }
17701
17702 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17703 self.diagnostics_max_severity = severity;
17704 self.display_map.update(cx, |display_map, _| {
17705 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17706 });
17707 }
17708
17709 pub fn toggle_diagnostics(
17710 &mut self,
17711 _: &ToggleDiagnostics,
17712 window: &mut Window,
17713 cx: &mut Context<Editor>,
17714 ) {
17715 if !self.diagnostics_enabled() {
17716 return;
17717 }
17718
17719 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17720 EditorSettings::get_global(cx)
17721 .diagnostics_max_severity
17722 .filter(|severity| severity != &DiagnosticSeverity::Off)
17723 .unwrap_or(DiagnosticSeverity::Hint)
17724 } else {
17725 DiagnosticSeverity::Off
17726 };
17727 self.set_max_diagnostics_severity(new_severity, cx);
17728 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17729 self.active_diagnostics = ActiveDiagnostic::None;
17730 self.inline_diagnostics_update = Task::ready(());
17731 self.inline_diagnostics.clear();
17732 } else {
17733 self.refresh_inline_diagnostics(false, window, cx);
17734 }
17735
17736 cx.notify();
17737 }
17738
17739 pub fn toggle_minimap(
17740 &mut self,
17741 _: &ToggleMinimap,
17742 window: &mut Window,
17743 cx: &mut Context<Editor>,
17744 ) {
17745 if self.supports_minimap(cx) {
17746 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17747 }
17748 }
17749
17750 fn refresh_inline_diagnostics(
17751 &mut self,
17752 debounce: bool,
17753 window: &mut Window,
17754 cx: &mut Context<Self>,
17755 ) {
17756 let max_severity = ProjectSettings::get_global(cx)
17757 .diagnostics
17758 .inline
17759 .max_severity
17760 .unwrap_or(self.diagnostics_max_severity);
17761
17762 if !self.inline_diagnostics_enabled()
17763 || !self.show_inline_diagnostics
17764 || max_severity == DiagnosticSeverity::Off
17765 {
17766 self.inline_diagnostics_update = Task::ready(());
17767 self.inline_diagnostics.clear();
17768 return;
17769 }
17770
17771 let debounce_ms = ProjectSettings::get_global(cx)
17772 .diagnostics
17773 .inline
17774 .update_debounce_ms;
17775 let debounce = if debounce && debounce_ms > 0 {
17776 Some(Duration::from_millis(debounce_ms))
17777 } else {
17778 None
17779 };
17780 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17781 if let Some(debounce) = debounce {
17782 cx.background_executor().timer(debounce).await;
17783 }
17784 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17785 editor
17786 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17787 .ok()
17788 }) else {
17789 return;
17790 };
17791
17792 let new_inline_diagnostics = cx
17793 .background_spawn(async move {
17794 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17795 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17796 let message = diagnostic_entry
17797 .diagnostic
17798 .message
17799 .split_once('\n')
17800 .map(|(line, _)| line)
17801 .map(SharedString::new)
17802 .unwrap_or_else(|| {
17803 SharedString::new(&*diagnostic_entry.diagnostic.message)
17804 });
17805 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17806 let (Ok(i) | Err(i)) = inline_diagnostics
17807 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17808 inline_diagnostics.insert(
17809 i,
17810 (
17811 start_anchor,
17812 InlineDiagnostic {
17813 message,
17814 group_id: diagnostic_entry.diagnostic.group_id,
17815 start: diagnostic_entry.range.start.to_point(&snapshot),
17816 is_primary: diagnostic_entry.diagnostic.is_primary,
17817 severity: diagnostic_entry.diagnostic.severity,
17818 },
17819 ),
17820 );
17821 }
17822 inline_diagnostics
17823 })
17824 .await;
17825
17826 editor
17827 .update(cx, |editor, cx| {
17828 editor.inline_diagnostics = new_inline_diagnostics;
17829 cx.notify();
17830 })
17831 .ok();
17832 });
17833 }
17834
17835 fn pull_diagnostics(
17836 &mut self,
17837 buffer_id: Option<BufferId>,
17838 window: &Window,
17839 cx: &mut Context<Self>,
17840 ) -> Option<()> {
17841 if !self.mode().is_full() {
17842 return None;
17843 }
17844 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17845 .diagnostics
17846 .lsp_pull_diagnostics;
17847 if !pull_diagnostics_settings.enabled {
17848 return None;
17849 }
17850 let project = self.project()?.downgrade();
17851 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17852 let mut buffers = self.buffer.read(cx).all_buffers();
17853 if let Some(buffer_id) = buffer_id {
17854 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17855 }
17856
17857 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17858 cx.background_executor().timer(debounce).await;
17859
17860 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17861 buffers
17862 .into_iter()
17863 .filter_map(|buffer| {
17864 project
17865 .update(cx, |project, cx| {
17866 project.lsp_store().update(cx, |lsp_store, cx| {
17867 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17868 })
17869 })
17870 .ok()
17871 })
17872 .collect::<FuturesUnordered<_>>()
17873 }) else {
17874 return;
17875 };
17876
17877 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17878 match pull_task {
17879 Ok(()) => {
17880 if editor
17881 .update_in(cx, |editor, window, cx| {
17882 editor.update_diagnostics_state(window, cx);
17883 })
17884 .is_err()
17885 {
17886 return;
17887 }
17888 }
17889 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17890 }
17891 }
17892 });
17893
17894 Some(())
17895 }
17896
17897 pub fn set_selections_from_remote(
17898 &mut self,
17899 selections: Vec<Selection<Anchor>>,
17900 pending_selection: Option<Selection<Anchor>>,
17901 window: &mut Window,
17902 cx: &mut Context<Self>,
17903 ) {
17904 let old_cursor_position = self.selections.newest_anchor().head();
17905 self.selections.change_with(cx, |s| {
17906 s.select_anchors(selections);
17907 if let Some(pending_selection) = pending_selection {
17908 s.set_pending(pending_selection, SelectMode::Character);
17909 } else {
17910 s.clear_pending();
17911 }
17912 });
17913 self.selections_did_change(
17914 false,
17915 &old_cursor_position,
17916 SelectionEffects::default(),
17917 window,
17918 cx,
17919 );
17920 }
17921
17922 pub fn transact(
17923 &mut self,
17924 window: &mut Window,
17925 cx: &mut Context<Self>,
17926 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17927 ) -> Option<TransactionId> {
17928 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17929 this.start_transaction_at(Instant::now(), window, cx);
17930 update(this, window, cx);
17931 this.end_transaction_at(Instant::now(), cx)
17932 })
17933 }
17934
17935 pub fn start_transaction_at(
17936 &mut self,
17937 now: Instant,
17938 window: &mut Window,
17939 cx: &mut Context<Self>,
17940 ) -> Option<TransactionId> {
17941 self.end_selection(window, cx);
17942 if let Some(tx_id) = self
17943 .buffer
17944 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17945 {
17946 self.selection_history
17947 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17948 cx.emit(EditorEvent::TransactionBegun {
17949 transaction_id: tx_id,
17950 });
17951 Some(tx_id)
17952 } else {
17953 None
17954 }
17955 }
17956
17957 pub fn end_transaction_at(
17958 &mut self,
17959 now: Instant,
17960 cx: &mut Context<Self>,
17961 ) -> Option<TransactionId> {
17962 if let Some(transaction_id) = self
17963 .buffer
17964 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17965 {
17966 if let Some((_, end_selections)) =
17967 self.selection_history.transaction_mut(transaction_id)
17968 {
17969 *end_selections = Some(self.selections.disjoint_anchors_arc());
17970 } else {
17971 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17972 }
17973
17974 cx.emit(EditorEvent::Edited { transaction_id });
17975 Some(transaction_id)
17976 } else {
17977 None
17978 }
17979 }
17980
17981 pub fn modify_transaction_selection_history(
17982 &mut self,
17983 transaction_id: TransactionId,
17984 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17985 ) -> bool {
17986 self.selection_history
17987 .transaction_mut(transaction_id)
17988 .map(modify)
17989 .is_some()
17990 }
17991
17992 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17993 if self.selection_mark_mode {
17994 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17995 s.move_with(|_, sel| {
17996 sel.collapse_to(sel.head(), SelectionGoal::None);
17997 });
17998 })
17999 }
18000 self.selection_mark_mode = true;
18001 cx.notify();
18002 }
18003
18004 pub fn swap_selection_ends(
18005 &mut self,
18006 _: &actions::SwapSelectionEnds,
18007 window: &mut Window,
18008 cx: &mut Context<Self>,
18009 ) {
18010 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18011 s.move_with(|_, sel| {
18012 if sel.start != sel.end {
18013 sel.reversed = !sel.reversed
18014 }
18015 });
18016 });
18017 self.request_autoscroll(Autoscroll::newest(), cx);
18018 cx.notify();
18019 }
18020
18021 pub fn toggle_focus(
18022 workspace: &mut Workspace,
18023 _: &actions::ToggleFocus,
18024 window: &mut Window,
18025 cx: &mut Context<Workspace>,
18026 ) {
18027 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18028 return;
18029 };
18030 workspace.activate_item(&item, true, true, window, cx);
18031 }
18032
18033 pub fn toggle_fold(
18034 &mut self,
18035 _: &actions::ToggleFold,
18036 window: &mut Window,
18037 cx: &mut Context<Self>,
18038 ) {
18039 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18040 let selection = self.selections.newest::<Point>(cx);
18041
18042 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18043 let range = if selection.is_empty() {
18044 let point = selection.head().to_display_point(&display_map);
18045 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18046 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18047 .to_point(&display_map);
18048 start..end
18049 } else {
18050 selection.range()
18051 };
18052 if display_map.folds_in_range(range).next().is_some() {
18053 self.unfold_lines(&Default::default(), window, cx)
18054 } else {
18055 self.fold(&Default::default(), window, cx)
18056 }
18057 } else {
18058 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18059 let buffer_ids: HashSet<_> = self
18060 .selections
18061 .disjoint_anchor_ranges()
18062 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18063 .collect();
18064
18065 let should_unfold = buffer_ids
18066 .iter()
18067 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18068
18069 for buffer_id in buffer_ids {
18070 if should_unfold {
18071 self.unfold_buffer(buffer_id, cx);
18072 } else {
18073 self.fold_buffer(buffer_id, cx);
18074 }
18075 }
18076 }
18077 }
18078
18079 pub fn toggle_fold_recursive(
18080 &mut self,
18081 _: &actions::ToggleFoldRecursive,
18082 window: &mut Window,
18083 cx: &mut Context<Self>,
18084 ) {
18085 let selection = self.selections.newest::<Point>(cx);
18086
18087 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18088 let range = if selection.is_empty() {
18089 let point = selection.head().to_display_point(&display_map);
18090 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18091 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18092 .to_point(&display_map);
18093 start..end
18094 } else {
18095 selection.range()
18096 };
18097 if display_map.folds_in_range(range).next().is_some() {
18098 self.unfold_recursive(&Default::default(), window, cx)
18099 } else {
18100 self.fold_recursive(&Default::default(), window, cx)
18101 }
18102 }
18103
18104 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18105 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18106 let mut to_fold = Vec::new();
18107 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18108 let selections = self.selections.all_adjusted(cx);
18109
18110 for selection in selections {
18111 let range = selection.range().sorted();
18112 let buffer_start_row = range.start.row;
18113
18114 if range.start.row != range.end.row {
18115 let mut found = false;
18116 let mut row = range.start.row;
18117 while row <= range.end.row {
18118 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18119 {
18120 found = true;
18121 row = crease.range().end.row + 1;
18122 to_fold.push(crease);
18123 } else {
18124 row += 1
18125 }
18126 }
18127 if found {
18128 continue;
18129 }
18130 }
18131
18132 for row in (0..=range.start.row).rev() {
18133 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18134 && crease.range().end.row >= buffer_start_row
18135 {
18136 to_fold.push(crease);
18137 if row <= range.start.row {
18138 break;
18139 }
18140 }
18141 }
18142 }
18143
18144 self.fold_creases(to_fold, true, window, cx);
18145 } else {
18146 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18147 let buffer_ids = self
18148 .selections
18149 .disjoint_anchor_ranges()
18150 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18151 .collect::<HashSet<_>>();
18152 for buffer_id in buffer_ids {
18153 self.fold_buffer(buffer_id, cx);
18154 }
18155 }
18156 }
18157
18158 pub fn toggle_fold_all(
18159 &mut self,
18160 _: &actions::ToggleFoldAll,
18161 window: &mut Window,
18162 cx: &mut Context<Self>,
18163 ) {
18164 if self.buffer.read(cx).is_singleton() {
18165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18166 let has_folds = display_map
18167 .folds_in_range(0..display_map.buffer_snapshot().len())
18168 .next()
18169 .is_some();
18170
18171 if has_folds {
18172 self.unfold_all(&actions::UnfoldAll, window, cx);
18173 } else {
18174 self.fold_all(&actions::FoldAll, window, cx);
18175 }
18176 } else {
18177 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18178 let should_unfold = buffer_ids
18179 .iter()
18180 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18181
18182 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18183 editor
18184 .update_in(cx, |editor, _, cx| {
18185 for buffer_id in buffer_ids {
18186 if should_unfold {
18187 editor.unfold_buffer(buffer_id, cx);
18188 } else {
18189 editor.fold_buffer(buffer_id, cx);
18190 }
18191 }
18192 })
18193 .ok();
18194 });
18195 }
18196 }
18197
18198 fn fold_at_level(
18199 &mut self,
18200 fold_at: &FoldAtLevel,
18201 window: &mut Window,
18202 cx: &mut Context<Self>,
18203 ) {
18204 if !self.buffer.read(cx).is_singleton() {
18205 return;
18206 }
18207
18208 let fold_at_level = fold_at.0;
18209 let snapshot = self.buffer.read(cx).snapshot(cx);
18210 let mut to_fold = Vec::new();
18211 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18212
18213 let row_ranges_to_keep: Vec<Range<u32>> = self
18214 .selections
18215 .all::<Point>(cx)
18216 .into_iter()
18217 .map(|sel| sel.start.row..sel.end.row)
18218 .collect();
18219
18220 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18221 while start_row < end_row {
18222 match self
18223 .snapshot(window, cx)
18224 .crease_for_buffer_row(MultiBufferRow(start_row))
18225 {
18226 Some(crease) => {
18227 let nested_start_row = crease.range().start.row + 1;
18228 let nested_end_row = crease.range().end.row;
18229
18230 if current_level < fold_at_level {
18231 stack.push((nested_start_row, nested_end_row, current_level + 1));
18232 } else if current_level == fold_at_level {
18233 // Fold iff there is no selection completely contained within the fold region
18234 if !row_ranges_to_keep.iter().any(|selection| {
18235 selection.end >= nested_start_row
18236 && selection.start <= nested_end_row
18237 }) {
18238 to_fold.push(crease);
18239 }
18240 }
18241
18242 start_row = nested_end_row + 1;
18243 }
18244 None => start_row += 1,
18245 }
18246 }
18247 }
18248
18249 self.fold_creases(to_fold, true, window, cx);
18250 }
18251
18252 pub fn fold_at_level_1(
18253 &mut self,
18254 _: &actions::FoldAtLevel1,
18255 window: &mut Window,
18256 cx: &mut Context<Self>,
18257 ) {
18258 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18259 }
18260
18261 pub fn fold_at_level_2(
18262 &mut self,
18263 _: &actions::FoldAtLevel2,
18264 window: &mut Window,
18265 cx: &mut Context<Self>,
18266 ) {
18267 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18268 }
18269
18270 pub fn fold_at_level_3(
18271 &mut self,
18272 _: &actions::FoldAtLevel3,
18273 window: &mut Window,
18274 cx: &mut Context<Self>,
18275 ) {
18276 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18277 }
18278
18279 pub fn fold_at_level_4(
18280 &mut self,
18281 _: &actions::FoldAtLevel4,
18282 window: &mut Window,
18283 cx: &mut Context<Self>,
18284 ) {
18285 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18286 }
18287
18288 pub fn fold_at_level_5(
18289 &mut self,
18290 _: &actions::FoldAtLevel5,
18291 window: &mut Window,
18292 cx: &mut Context<Self>,
18293 ) {
18294 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18295 }
18296
18297 pub fn fold_at_level_6(
18298 &mut self,
18299 _: &actions::FoldAtLevel6,
18300 window: &mut Window,
18301 cx: &mut Context<Self>,
18302 ) {
18303 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18304 }
18305
18306 pub fn fold_at_level_7(
18307 &mut self,
18308 _: &actions::FoldAtLevel7,
18309 window: &mut Window,
18310 cx: &mut Context<Self>,
18311 ) {
18312 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18313 }
18314
18315 pub fn fold_at_level_8(
18316 &mut self,
18317 _: &actions::FoldAtLevel8,
18318 window: &mut Window,
18319 cx: &mut Context<Self>,
18320 ) {
18321 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18322 }
18323
18324 pub fn fold_at_level_9(
18325 &mut self,
18326 _: &actions::FoldAtLevel9,
18327 window: &mut Window,
18328 cx: &mut Context<Self>,
18329 ) {
18330 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18331 }
18332
18333 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18334 if self.buffer.read(cx).is_singleton() {
18335 let mut fold_ranges = Vec::new();
18336 let snapshot = self.buffer.read(cx).snapshot(cx);
18337
18338 for row in 0..snapshot.max_row().0 {
18339 if let Some(foldable_range) = self
18340 .snapshot(window, cx)
18341 .crease_for_buffer_row(MultiBufferRow(row))
18342 {
18343 fold_ranges.push(foldable_range);
18344 }
18345 }
18346
18347 self.fold_creases(fold_ranges, true, window, cx);
18348 } else {
18349 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18350 editor
18351 .update_in(cx, |editor, _, cx| {
18352 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18353 editor.fold_buffer(buffer_id, cx);
18354 }
18355 })
18356 .ok();
18357 });
18358 }
18359 }
18360
18361 pub fn fold_function_bodies(
18362 &mut self,
18363 _: &actions::FoldFunctionBodies,
18364 window: &mut Window,
18365 cx: &mut Context<Self>,
18366 ) {
18367 let snapshot = self.buffer.read(cx).snapshot(cx);
18368
18369 let ranges = snapshot
18370 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18371 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18372 .collect::<Vec<_>>();
18373
18374 let creases = ranges
18375 .into_iter()
18376 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18377 .collect();
18378
18379 self.fold_creases(creases, true, window, cx);
18380 }
18381
18382 pub fn fold_recursive(
18383 &mut self,
18384 _: &actions::FoldRecursive,
18385 window: &mut Window,
18386 cx: &mut Context<Self>,
18387 ) {
18388 let mut to_fold = Vec::new();
18389 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18390 let selections = self.selections.all_adjusted(cx);
18391
18392 for selection in selections {
18393 let range = selection.range().sorted();
18394 let buffer_start_row = range.start.row;
18395
18396 if range.start.row != range.end.row {
18397 let mut found = false;
18398 for row in range.start.row..=range.end.row {
18399 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18400 found = true;
18401 to_fold.push(crease);
18402 }
18403 }
18404 if found {
18405 continue;
18406 }
18407 }
18408
18409 for row in (0..=range.start.row).rev() {
18410 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18411 if crease.range().end.row >= buffer_start_row {
18412 to_fold.push(crease);
18413 } else {
18414 break;
18415 }
18416 }
18417 }
18418 }
18419
18420 self.fold_creases(to_fold, true, window, cx);
18421 }
18422
18423 pub fn fold_at(
18424 &mut self,
18425 buffer_row: MultiBufferRow,
18426 window: &mut Window,
18427 cx: &mut Context<Self>,
18428 ) {
18429 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18430
18431 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18432 let autoscroll = self
18433 .selections
18434 .all::<Point>(cx)
18435 .iter()
18436 .any(|selection| crease.range().overlaps(&selection.range()));
18437
18438 self.fold_creases(vec![crease], autoscroll, window, cx);
18439 }
18440 }
18441
18442 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18443 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18444 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18445 let buffer = display_map.buffer_snapshot();
18446 let selections = self.selections.all::<Point>(cx);
18447 let ranges = selections
18448 .iter()
18449 .map(|s| {
18450 let range = s.display_range(&display_map).sorted();
18451 let mut start = range.start.to_point(&display_map);
18452 let mut end = range.end.to_point(&display_map);
18453 start.column = 0;
18454 end.column = buffer.line_len(MultiBufferRow(end.row));
18455 start..end
18456 })
18457 .collect::<Vec<_>>();
18458
18459 self.unfold_ranges(&ranges, true, true, cx);
18460 } else {
18461 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18462 let buffer_ids = self
18463 .selections
18464 .disjoint_anchor_ranges()
18465 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18466 .collect::<HashSet<_>>();
18467 for buffer_id in buffer_ids {
18468 self.unfold_buffer(buffer_id, cx);
18469 }
18470 }
18471 }
18472
18473 pub fn unfold_recursive(
18474 &mut self,
18475 _: &UnfoldRecursive,
18476 _window: &mut Window,
18477 cx: &mut Context<Self>,
18478 ) {
18479 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18480 let selections = self.selections.all::<Point>(cx);
18481 let ranges = selections
18482 .iter()
18483 .map(|s| {
18484 let mut range = s.display_range(&display_map).sorted();
18485 *range.start.column_mut() = 0;
18486 *range.end.column_mut() = display_map.line_len(range.end.row());
18487 let start = range.start.to_point(&display_map);
18488 let end = range.end.to_point(&display_map);
18489 start..end
18490 })
18491 .collect::<Vec<_>>();
18492
18493 self.unfold_ranges(&ranges, true, true, cx);
18494 }
18495
18496 pub fn unfold_at(
18497 &mut self,
18498 buffer_row: MultiBufferRow,
18499 _window: &mut Window,
18500 cx: &mut Context<Self>,
18501 ) {
18502 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18503
18504 let intersection_range = Point::new(buffer_row.0, 0)
18505 ..Point::new(
18506 buffer_row.0,
18507 display_map.buffer_snapshot().line_len(buffer_row),
18508 );
18509
18510 let autoscroll = self
18511 .selections
18512 .all::<Point>(cx)
18513 .iter()
18514 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18515
18516 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18517 }
18518
18519 pub fn unfold_all(
18520 &mut self,
18521 _: &actions::UnfoldAll,
18522 _window: &mut Window,
18523 cx: &mut Context<Self>,
18524 ) {
18525 if self.buffer.read(cx).is_singleton() {
18526 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18527 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18528 } else {
18529 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18530 editor
18531 .update(cx, |editor, cx| {
18532 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18533 editor.unfold_buffer(buffer_id, cx);
18534 }
18535 })
18536 .ok();
18537 });
18538 }
18539 }
18540
18541 pub fn fold_selected_ranges(
18542 &mut self,
18543 _: &FoldSelectedRanges,
18544 window: &mut Window,
18545 cx: &mut Context<Self>,
18546 ) {
18547 let selections = self.selections.all_adjusted(cx);
18548 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18549 let ranges = selections
18550 .into_iter()
18551 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18552 .collect::<Vec<_>>();
18553 self.fold_creases(ranges, true, window, cx);
18554 }
18555
18556 pub fn fold_ranges<T: ToOffset + Clone>(
18557 &mut self,
18558 ranges: Vec<Range<T>>,
18559 auto_scroll: bool,
18560 window: &mut Window,
18561 cx: &mut Context<Self>,
18562 ) {
18563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18564 let ranges = ranges
18565 .into_iter()
18566 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18567 .collect::<Vec<_>>();
18568 self.fold_creases(ranges, auto_scroll, window, cx);
18569 }
18570
18571 pub fn fold_creases<T: ToOffset + Clone>(
18572 &mut self,
18573 creases: Vec<Crease<T>>,
18574 auto_scroll: bool,
18575 _window: &mut Window,
18576 cx: &mut Context<Self>,
18577 ) {
18578 if creases.is_empty() {
18579 return;
18580 }
18581
18582 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18583
18584 if auto_scroll {
18585 self.request_autoscroll(Autoscroll::fit(), cx);
18586 }
18587
18588 cx.notify();
18589
18590 self.scrollbar_marker_state.dirty = true;
18591 self.folds_did_change(cx);
18592 }
18593
18594 /// Removes any folds whose ranges intersect any of the given ranges.
18595 pub fn unfold_ranges<T: ToOffset + Clone>(
18596 &mut self,
18597 ranges: &[Range<T>],
18598 inclusive: bool,
18599 auto_scroll: bool,
18600 cx: &mut Context<Self>,
18601 ) {
18602 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18603 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18604 });
18605 self.folds_did_change(cx);
18606 }
18607
18608 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18609 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18610 return;
18611 }
18612 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18613 self.display_map.update(cx, |display_map, cx| {
18614 display_map.fold_buffers([buffer_id], cx)
18615 });
18616 cx.emit(EditorEvent::BufferFoldToggled {
18617 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18618 folded: true,
18619 });
18620 cx.notify();
18621 }
18622
18623 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18624 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18625 return;
18626 }
18627 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18628 self.display_map.update(cx, |display_map, cx| {
18629 display_map.unfold_buffers([buffer_id], cx);
18630 });
18631 cx.emit(EditorEvent::BufferFoldToggled {
18632 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18633 folded: false,
18634 });
18635 cx.notify();
18636 }
18637
18638 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18639 self.display_map.read(cx).is_buffer_folded(buffer)
18640 }
18641
18642 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18643 self.display_map.read(cx).folded_buffers()
18644 }
18645
18646 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18647 self.display_map.update(cx, |display_map, cx| {
18648 display_map.disable_header_for_buffer(buffer_id, cx);
18649 });
18650 cx.notify();
18651 }
18652
18653 /// Removes any folds with the given ranges.
18654 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18655 &mut self,
18656 ranges: &[Range<T>],
18657 type_id: TypeId,
18658 auto_scroll: bool,
18659 cx: &mut Context<Self>,
18660 ) {
18661 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18662 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18663 });
18664 self.folds_did_change(cx);
18665 }
18666
18667 fn remove_folds_with<T: ToOffset + Clone>(
18668 &mut self,
18669 ranges: &[Range<T>],
18670 auto_scroll: bool,
18671 cx: &mut Context<Self>,
18672 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18673 ) {
18674 if ranges.is_empty() {
18675 return;
18676 }
18677
18678 let mut buffers_affected = HashSet::default();
18679 let multi_buffer = self.buffer().read(cx);
18680 for range in ranges {
18681 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18682 buffers_affected.insert(buffer.read(cx).remote_id());
18683 };
18684 }
18685
18686 self.display_map.update(cx, update);
18687
18688 if auto_scroll {
18689 self.request_autoscroll(Autoscroll::fit(), cx);
18690 }
18691
18692 cx.notify();
18693 self.scrollbar_marker_state.dirty = true;
18694 self.active_indent_guides_state.dirty = true;
18695 }
18696
18697 pub fn update_renderer_widths(
18698 &mut self,
18699 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18700 cx: &mut Context<Self>,
18701 ) -> bool {
18702 self.display_map
18703 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18704 }
18705
18706 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18707 self.display_map.read(cx).fold_placeholder.clone()
18708 }
18709
18710 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18711 self.buffer.update(cx, |buffer, cx| {
18712 buffer.set_all_diff_hunks_expanded(cx);
18713 });
18714 }
18715
18716 pub fn expand_all_diff_hunks(
18717 &mut self,
18718 _: &ExpandAllDiffHunks,
18719 _window: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) {
18722 self.buffer.update(cx, |buffer, cx| {
18723 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18724 });
18725 }
18726
18727 pub fn toggle_selected_diff_hunks(
18728 &mut self,
18729 _: &ToggleSelectedDiffHunks,
18730 _window: &mut Window,
18731 cx: &mut Context<Self>,
18732 ) {
18733 let ranges: Vec<_> = self
18734 .selections
18735 .disjoint_anchors()
18736 .iter()
18737 .map(|s| s.range())
18738 .collect();
18739 self.toggle_diff_hunks_in_ranges(ranges, cx);
18740 }
18741
18742 pub fn diff_hunks_in_ranges<'a>(
18743 &'a self,
18744 ranges: &'a [Range<Anchor>],
18745 buffer: &'a MultiBufferSnapshot,
18746 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18747 ranges.iter().flat_map(move |range| {
18748 let end_excerpt_id = range.end.excerpt_id;
18749 let range = range.to_point(buffer);
18750 let mut peek_end = range.end;
18751 if range.end.row < buffer.max_row().0 {
18752 peek_end = Point::new(range.end.row + 1, 0);
18753 }
18754 buffer
18755 .diff_hunks_in_range(range.start..peek_end)
18756 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18757 })
18758 }
18759
18760 pub fn has_stageable_diff_hunks_in_ranges(
18761 &self,
18762 ranges: &[Range<Anchor>],
18763 snapshot: &MultiBufferSnapshot,
18764 ) -> bool {
18765 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18766 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18767 }
18768
18769 pub fn toggle_staged_selected_diff_hunks(
18770 &mut self,
18771 _: &::git::ToggleStaged,
18772 _: &mut Window,
18773 cx: &mut Context<Self>,
18774 ) {
18775 let snapshot = self.buffer.read(cx).snapshot(cx);
18776 let ranges: Vec<_> = self
18777 .selections
18778 .disjoint_anchors()
18779 .iter()
18780 .map(|s| s.range())
18781 .collect();
18782 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18783 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18784 }
18785
18786 pub fn set_render_diff_hunk_controls(
18787 &mut self,
18788 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18789 cx: &mut Context<Self>,
18790 ) {
18791 self.render_diff_hunk_controls = render_diff_hunk_controls;
18792 cx.notify();
18793 }
18794
18795 pub fn stage_and_next(
18796 &mut self,
18797 _: &::git::StageAndNext,
18798 window: &mut Window,
18799 cx: &mut Context<Self>,
18800 ) {
18801 self.do_stage_or_unstage_and_next(true, window, cx);
18802 }
18803
18804 pub fn unstage_and_next(
18805 &mut self,
18806 _: &::git::UnstageAndNext,
18807 window: &mut Window,
18808 cx: &mut Context<Self>,
18809 ) {
18810 self.do_stage_or_unstage_and_next(false, window, cx);
18811 }
18812
18813 pub fn stage_or_unstage_diff_hunks(
18814 &mut self,
18815 stage: bool,
18816 ranges: Vec<Range<Anchor>>,
18817 cx: &mut Context<Self>,
18818 ) {
18819 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18820 cx.spawn(async move |this, cx| {
18821 task.await?;
18822 this.update(cx, |this, cx| {
18823 let snapshot = this.buffer.read(cx).snapshot(cx);
18824 let chunk_by = this
18825 .diff_hunks_in_ranges(&ranges, &snapshot)
18826 .chunk_by(|hunk| hunk.buffer_id);
18827 for (buffer_id, hunks) in &chunk_by {
18828 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18829 }
18830 })
18831 })
18832 .detach_and_log_err(cx);
18833 }
18834
18835 fn save_buffers_for_ranges_if_needed(
18836 &mut self,
18837 ranges: &[Range<Anchor>],
18838 cx: &mut Context<Editor>,
18839 ) -> Task<Result<()>> {
18840 let multibuffer = self.buffer.read(cx);
18841 let snapshot = multibuffer.read(cx);
18842 let buffer_ids: HashSet<_> = ranges
18843 .iter()
18844 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18845 .collect();
18846 drop(snapshot);
18847
18848 let mut buffers = HashSet::default();
18849 for buffer_id in buffer_ids {
18850 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18851 let buffer = buffer_entity.read(cx);
18852 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18853 {
18854 buffers.insert(buffer_entity);
18855 }
18856 }
18857 }
18858
18859 if let Some(project) = &self.project {
18860 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18861 } else {
18862 Task::ready(Ok(()))
18863 }
18864 }
18865
18866 fn do_stage_or_unstage_and_next(
18867 &mut self,
18868 stage: bool,
18869 window: &mut Window,
18870 cx: &mut Context<Self>,
18871 ) {
18872 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18873
18874 if ranges.iter().any(|range| range.start != range.end) {
18875 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18876 return;
18877 }
18878
18879 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18880 let snapshot = self.snapshot(window, cx);
18881 let position = self.selections.newest::<Point>(cx).head();
18882 let mut row = snapshot
18883 .buffer_snapshot()
18884 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18885 .find(|hunk| hunk.row_range.start.0 > position.row)
18886 .map(|hunk| hunk.row_range.start);
18887
18888 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18889 // Outside of the project diff editor, wrap around to the beginning.
18890 if !all_diff_hunks_expanded {
18891 row = row.or_else(|| {
18892 snapshot
18893 .buffer_snapshot()
18894 .diff_hunks_in_range(Point::zero()..position)
18895 .find(|hunk| hunk.row_range.end.0 < position.row)
18896 .map(|hunk| hunk.row_range.start)
18897 });
18898 }
18899
18900 if let Some(row) = row {
18901 let destination = Point::new(row.0, 0);
18902 let autoscroll = Autoscroll::center();
18903
18904 self.unfold_ranges(&[destination..destination], false, false, cx);
18905 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18906 s.select_ranges([destination..destination]);
18907 });
18908 }
18909 }
18910
18911 fn do_stage_or_unstage(
18912 &self,
18913 stage: bool,
18914 buffer_id: BufferId,
18915 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18916 cx: &mut App,
18917 ) -> Option<()> {
18918 let project = self.project()?;
18919 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18920 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18921 let buffer_snapshot = buffer.read(cx).snapshot();
18922 let file_exists = buffer_snapshot
18923 .file()
18924 .is_some_and(|file| file.disk_state().exists());
18925 diff.update(cx, |diff, cx| {
18926 diff.stage_or_unstage_hunks(
18927 stage,
18928 &hunks
18929 .map(|hunk| buffer_diff::DiffHunk {
18930 buffer_range: hunk.buffer_range,
18931 diff_base_byte_range: hunk.diff_base_byte_range,
18932 secondary_status: hunk.secondary_status,
18933 range: Point::zero()..Point::zero(), // unused
18934 })
18935 .collect::<Vec<_>>(),
18936 &buffer_snapshot,
18937 file_exists,
18938 cx,
18939 )
18940 });
18941 None
18942 }
18943
18944 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18945 let ranges: Vec<_> = self
18946 .selections
18947 .disjoint_anchors()
18948 .iter()
18949 .map(|s| s.range())
18950 .collect();
18951 self.buffer
18952 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18953 }
18954
18955 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18956 self.buffer.update(cx, |buffer, cx| {
18957 let ranges = vec![Anchor::min()..Anchor::max()];
18958 if !buffer.all_diff_hunks_expanded()
18959 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18960 {
18961 buffer.collapse_diff_hunks(ranges, cx);
18962 true
18963 } else {
18964 false
18965 }
18966 })
18967 }
18968
18969 fn toggle_diff_hunks_in_ranges(
18970 &mut self,
18971 ranges: Vec<Range<Anchor>>,
18972 cx: &mut Context<Editor>,
18973 ) {
18974 self.buffer.update(cx, |buffer, cx| {
18975 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18976 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18977 })
18978 }
18979
18980 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18981 self.buffer.update(cx, |buffer, cx| {
18982 let snapshot = buffer.snapshot(cx);
18983 let excerpt_id = range.end.excerpt_id;
18984 let point_range = range.to_point(&snapshot);
18985 let expand = !buffer.single_hunk_is_expanded(range, cx);
18986 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18987 })
18988 }
18989
18990 pub(crate) fn apply_all_diff_hunks(
18991 &mut self,
18992 _: &ApplyAllDiffHunks,
18993 window: &mut Window,
18994 cx: &mut Context<Self>,
18995 ) {
18996 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18997
18998 let buffers = self.buffer.read(cx).all_buffers();
18999 for branch_buffer in buffers {
19000 branch_buffer.update(cx, |branch_buffer, cx| {
19001 branch_buffer.merge_into_base(Vec::new(), cx);
19002 });
19003 }
19004
19005 if let Some(project) = self.project.clone() {
19006 self.save(
19007 SaveOptions {
19008 format: true,
19009 autosave: false,
19010 },
19011 project,
19012 window,
19013 cx,
19014 )
19015 .detach_and_log_err(cx);
19016 }
19017 }
19018
19019 pub(crate) fn apply_selected_diff_hunks(
19020 &mut self,
19021 _: &ApplyDiffHunk,
19022 window: &mut Window,
19023 cx: &mut Context<Self>,
19024 ) {
19025 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19026 let snapshot = self.snapshot(window, cx);
19027 let hunks = snapshot.hunks_for_ranges(
19028 self.selections
19029 .all(cx)
19030 .into_iter()
19031 .map(|selection| selection.range()),
19032 );
19033 let mut ranges_by_buffer = HashMap::default();
19034 self.transact(window, cx, |editor, _window, cx| {
19035 for hunk in hunks {
19036 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19037 ranges_by_buffer
19038 .entry(buffer.clone())
19039 .or_insert_with(Vec::new)
19040 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19041 }
19042 }
19043
19044 for (buffer, ranges) in ranges_by_buffer {
19045 buffer.update(cx, |buffer, cx| {
19046 buffer.merge_into_base(ranges, cx);
19047 });
19048 }
19049 });
19050
19051 if let Some(project) = self.project.clone() {
19052 self.save(
19053 SaveOptions {
19054 format: true,
19055 autosave: false,
19056 },
19057 project,
19058 window,
19059 cx,
19060 )
19061 .detach_and_log_err(cx);
19062 }
19063 }
19064
19065 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19066 if hovered != self.gutter_hovered {
19067 self.gutter_hovered = hovered;
19068 cx.notify();
19069 }
19070 }
19071
19072 pub fn insert_blocks(
19073 &mut self,
19074 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19075 autoscroll: Option<Autoscroll>,
19076 cx: &mut Context<Self>,
19077 ) -> Vec<CustomBlockId> {
19078 let blocks = self
19079 .display_map
19080 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19081 if let Some(autoscroll) = autoscroll {
19082 self.request_autoscroll(autoscroll, cx);
19083 }
19084 cx.notify();
19085 blocks
19086 }
19087
19088 pub fn resize_blocks(
19089 &mut self,
19090 heights: HashMap<CustomBlockId, u32>,
19091 autoscroll: Option<Autoscroll>,
19092 cx: &mut Context<Self>,
19093 ) {
19094 self.display_map
19095 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19096 if let Some(autoscroll) = autoscroll {
19097 self.request_autoscroll(autoscroll, cx);
19098 }
19099 cx.notify();
19100 }
19101
19102 pub fn replace_blocks(
19103 &mut self,
19104 renderers: HashMap<CustomBlockId, RenderBlock>,
19105 autoscroll: Option<Autoscroll>,
19106 cx: &mut Context<Self>,
19107 ) {
19108 self.display_map
19109 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19110 if let Some(autoscroll) = autoscroll {
19111 self.request_autoscroll(autoscroll, cx);
19112 }
19113 cx.notify();
19114 }
19115
19116 pub fn remove_blocks(
19117 &mut self,
19118 block_ids: HashSet<CustomBlockId>,
19119 autoscroll: Option<Autoscroll>,
19120 cx: &mut Context<Self>,
19121 ) {
19122 self.display_map.update(cx, |display_map, cx| {
19123 display_map.remove_blocks(block_ids, cx)
19124 });
19125 if let Some(autoscroll) = autoscroll {
19126 self.request_autoscroll(autoscroll, cx);
19127 }
19128 cx.notify();
19129 }
19130
19131 pub fn row_for_block(
19132 &self,
19133 block_id: CustomBlockId,
19134 cx: &mut Context<Self>,
19135 ) -> Option<DisplayRow> {
19136 self.display_map
19137 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19138 }
19139
19140 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19141 self.focused_block = Some(focused_block);
19142 }
19143
19144 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19145 self.focused_block.take()
19146 }
19147
19148 pub fn insert_creases(
19149 &mut self,
19150 creases: impl IntoIterator<Item = Crease<Anchor>>,
19151 cx: &mut Context<Self>,
19152 ) -> Vec<CreaseId> {
19153 self.display_map
19154 .update(cx, |map, cx| map.insert_creases(creases, cx))
19155 }
19156
19157 pub fn remove_creases(
19158 &mut self,
19159 ids: impl IntoIterator<Item = CreaseId>,
19160 cx: &mut Context<Self>,
19161 ) -> Vec<(CreaseId, Range<Anchor>)> {
19162 self.display_map
19163 .update(cx, |map, cx| map.remove_creases(ids, cx))
19164 }
19165
19166 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19167 self.display_map
19168 .update(cx, |map, cx| map.snapshot(cx))
19169 .longest_row()
19170 }
19171
19172 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19173 self.display_map
19174 .update(cx, |map, cx| map.snapshot(cx))
19175 .max_point()
19176 }
19177
19178 pub fn text(&self, cx: &App) -> String {
19179 self.buffer.read(cx).read(cx).text()
19180 }
19181
19182 pub fn is_empty(&self, cx: &App) -> bool {
19183 self.buffer.read(cx).read(cx).is_empty()
19184 }
19185
19186 pub fn text_option(&self, cx: &App) -> Option<String> {
19187 let text = self.text(cx);
19188 let text = text.trim();
19189
19190 if text.is_empty() {
19191 return None;
19192 }
19193
19194 Some(text.to_string())
19195 }
19196
19197 pub fn set_text(
19198 &mut self,
19199 text: impl Into<Arc<str>>,
19200 window: &mut Window,
19201 cx: &mut Context<Self>,
19202 ) {
19203 self.transact(window, cx, |this, _, cx| {
19204 this.buffer
19205 .read(cx)
19206 .as_singleton()
19207 .expect("you can only call set_text on editors for singleton buffers")
19208 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19209 });
19210 }
19211
19212 pub fn display_text(&self, cx: &mut App) -> String {
19213 self.display_map
19214 .update(cx, |map, cx| map.snapshot(cx))
19215 .text()
19216 }
19217
19218 fn create_minimap(
19219 &self,
19220 minimap_settings: MinimapSettings,
19221 window: &mut Window,
19222 cx: &mut Context<Self>,
19223 ) -> Option<Entity<Self>> {
19224 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19225 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19226 }
19227
19228 fn initialize_new_minimap(
19229 &self,
19230 minimap_settings: MinimapSettings,
19231 window: &mut Window,
19232 cx: &mut Context<Self>,
19233 ) -> Entity<Self> {
19234 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19235
19236 let mut minimap = Editor::new_internal(
19237 EditorMode::Minimap {
19238 parent: cx.weak_entity(),
19239 },
19240 self.buffer.clone(),
19241 None,
19242 Some(self.display_map.clone()),
19243 window,
19244 cx,
19245 );
19246 minimap.scroll_manager.clone_state(&self.scroll_manager);
19247 minimap.set_text_style_refinement(TextStyleRefinement {
19248 font_size: Some(MINIMAP_FONT_SIZE),
19249 font_weight: Some(MINIMAP_FONT_WEIGHT),
19250 ..Default::default()
19251 });
19252 minimap.update_minimap_configuration(minimap_settings, cx);
19253 cx.new(|_| minimap)
19254 }
19255
19256 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19257 let current_line_highlight = minimap_settings
19258 .current_line_highlight
19259 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19260 self.set_current_line_highlight(Some(current_line_highlight));
19261 }
19262
19263 pub fn minimap(&self) -> Option<&Entity<Self>> {
19264 self.minimap
19265 .as_ref()
19266 .filter(|_| self.minimap_visibility.visible())
19267 }
19268
19269 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19270 let mut wrap_guides = smallvec![];
19271
19272 if self.show_wrap_guides == Some(false) {
19273 return wrap_guides;
19274 }
19275
19276 let settings = self.buffer.read(cx).language_settings(cx);
19277 if settings.show_wrap_guides {
19278 match self.soft_wrap_mode(cx) {
19279 SoftWrap::Column(soft_wrap) => {
19280 wrap_guides.push((soft_wrap as usize, true));
19281 }
19282 SoftWrap::Bounded(soft_wrap) => {
19283 wrap_guides.push((soft_wrap as usize, true));
19284 }
19285 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19286 }
19287 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19288 }
19289
19290 wrap_guides
19291 }
19292
19293 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19294 let settings = self.buffer.read(cx).language_settings(cx);
19295 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19296 match mode {
19297 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19298 SoftWrap::None
19299 }
19300 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19301 language_settings::SoftWrap::PreferredLineLength => {
19302 SoftWrap::Column(settings.preferred_line_length)
19303 }
19304 language_settings::SoftWrap::Bounded => {
19305 SoftWrap::Bounded(settings.preferred_line_length)
19306 }
19307 }
19308 }
19309
19310 pub fn set_soft_wrap_mode(
19311 &mut self,
19312 mode: language_settings::SoftWrap,
19313
19314 cx: &mut Context<Self>,
19315 ) {
19316 self.soft_wrap_mode_override = Some(mode);
19317 cx.notify();
19318 }
19319
19320 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19321 self.hard_wrap = hard_wrap;
19322 cx.notify();
19323 }
19324
19325 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19326 self.text_style_refinement = Some(style);
19327 }
19328
19329 /// called by the Element so we know what style we were most recently rendered with.
19330 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19331 // We intentionally do not inform the display map about the minimap style
19332 // so that wrapping is not recalculated and stays consistent for the editor
19333 // and its linked minimap.
19334 if !self.mode.is_minimap() {
19335 let font = style.text.font();
19336 let font_size = style.text.font_size.to_pixels(window.rem_size());
19337 let display_map = self
19338 .placeholder_display_map
19339 .as_ref()
19340 .filter(|_| self.is_empty(cx))
19341 .unwrap_or(&self.display_map);
19342
19343 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19344 }
19345 self.style = Some(style);
19346 }
19347
19348 pub fn style(&self) -> Option<&EditorStyle> {
19349 self.style.as_ref()
19350 }
19351
19352 // Called by the element. This method is not designed to be called outside of the editor
19353 // element's layout code because it does not notify when rewrapping is computed synchronously.
19354 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19355 if self.is_empty(cx) {
19356 self.placeholder_display_map
19357 .as_ref()
19358 .map_or(false, |display_map| {
19359 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19360 })
19361 } else {
19362 self.display_map
19363 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19364 }
19365 }
19366
19367 pub fn set_soft_wrap(&mut self) {
19368 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19369 }
19370
19371 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19372 if self.soft_wrap_mode_override.is_some() {
19373 self.soft_wrap_mode_override.take();
19374 } else {
19375 let soft_wrap = match self.soft_wrap_mode(cx) {
19376 SoftWrap::GitDiff => return,
19377 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19378 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19379 language_settings::SoftWrap::None
19380 }
19381 };
19382 self.soft_wrap_mode_override = Some(soft_wrap);
19383 }
19384 cx.notify();
19385 }
19386
19387 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19388 let Some(workspace) = self.workspace() else {
19389 return;
19390 };
19391 let fs = workspace.read(cx).app_state().fs.clone();
19392 let current_show = TabBarSettings::get_global(cx).show;
19393 update_settings_file(fs, cx, move |setting, _| {
19394 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19395 });
19396 }
19397
19398 pub fn toggle_indent_guides(
19399 &mut self,
19400 _: &ToggleIndentGuides,
19401 _: &mut Window,
19402 cx: &mut Context<Self>,
19403 ) {
19404 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19405 self.buffer
19406 .read(cx)
19407 .language_settings(cx)
19408 .indent_guides
19409 .enabled
19410 });
19411 self.show_indent_guides = Some(!currently_enabled);
19412 cx.notify();
19413 }
19414
19415 fn should_show_indent_guides(&self) -> Option<bool> {
19416 self.show_indent_guides
19417 }
19418
19419 pub fn toggle_line_numbers(
19420 &mut self,
19421 _: &ToggleLineNumbers,
19422 _: &mut Window,
19423 cx: &mut Context<Self>,
19424 ) {
19425 let mut editor_settings = EditorSettings::get_global(cx).clone();
19426 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19427 EditorSettings::override_global(editor_settings, cx);
19428 }
19429
19430 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19431 if let Some(show_line_numbers) = self.show_line_numbers {
19432 return show_line_numbers;
19433 }
19434 EditorSettings::get_global(cx).gutter.line_numbers
19435 }
19436
19437 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19438 self.use_relative_line_numbers
19439 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19440 }
19441
19442 pub fn toggle_relative_line_numbers(
19443 &mut self,
19444 _: &ToggleRelativeLineNumbers,
19445 _: &mut Window,
19446 cx: &mut Context<Self>,
19447 ) {
19448 let is_relative = self.should_use_relative_line_numbers(cx);
19449 self.set_relative_line_number(Some(!is_relative), cx)
19450 }
19451
19452 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19453 self.use_relative_line_numbers = is_relative;
19454 cx.notify();
19455 }
19456
19457 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19458 self.show_gutter = show_gutter;
19459 cx.notify();
19460 }
19461
19462 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19463 self.show_scrollbars = ScrollbarAxes {
19464 horizontal: show,
19465 vertical: show,
19466 };
19467 cx.notify();
19468 }
19469
19470 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19471 self.show_scrollbars.vertical = show;
19472 cx.notify();
19473 }
19474
19475 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19476 self.show_scrollbars.horizontal = show;
19477 cx.notify();
19478 }
19479
19480 pub fn set_minimap_visibility(
19481 &mut self,
19482 minimap_visibility: MinimapVisibility,
19483 window: &mut Window,
19484 cx: &mut Context<Self>,
19485 ) {
19486 if self.minimap_visibility != minimap_visibility {
19487 if minimap_visibility.visible() && self.minimap.is_none() {
19488 let minimap_settings = EditorSettings::get_global(cx).minimap;
19489 self.minimap =
19490 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19491 }
19492 self.minimap_visibility = minimap_visibility;
19493 cx.notify();
19494 }
19495 }
19496
19497 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19498 self.set_show_scrollbars(false, cx);
19499 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19500 }
19501
19502 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19503 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19504 }
19505
19506 /// Normally the text in full mode and auto height editors is padded on the
19507 /// left side by roughly half a character width for improved hit testing.
19508 ///
19509 /// Use this method to disable this for cases where this is not wanted (e.g.
19510 /// if you want to align the editor text with some other text above or below)
19511 /// or if you want to add this padding to single-line editors.
19512 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19513 self.offset_content = offset_content;
19514 cx.notify();
19515 }
19516
19517 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19518 self.show_line_numbers = Some(show_line_numbers);
19519 cx.notify();
19520 }
19521
19522 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19523 self.disable_expand_excerpt_buttons = true;
19524 cx.notify();
19525 }
19526
19527 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19528 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19529 cx.notify();
19530 }
19531
19532 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19533 self.show_code_actions = Some(show_code_actions);
19534 cx.notify();
19535 }
19536
19537 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19538 self.show_runnables = Some(show_runnables);
19539 cx.notify();
19540 }
19541
19542 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19543 self.show_breakpoints = Some(show_breakpoints);
19544 cx.notify();
19545 }
19546
19547 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19548 if self.display_map.read(cx).masked != masked {
19549 self.display_map.update(cx, |map, _| map.masked = masked);
19550 }
19551 cx.notify()
19552 }
19553
19554 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19555 self.show_wrap_guides = Some(show_wrap_guides);
19556 cx.notify();
19557 }
19558
19559 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19560 self.show_indent_guides = Some(show_indent_guides);
19561 cx.notify();
19562 }
19563
19564 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19565 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19566 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19567 && let Some(dir) = file.abs_path(cx).parent()
19568 {
19569 return Some(dir.to_owned());
19570 }
19571 }
19572
19573 None
19574 }
19575
19576 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19577 self.active_excerpt(cx)?
19578 .1
19579 .read(cx)
19580 .file()
19581 .and_then(|f| f.as_local())
19582 }
19583
19584 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19585 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19586 let buffer = buffer.read(cx);
19587 if let Some(project_path) = buffer.project_path(cx) {
19588 let project = self.project()?.read(cx);
19589 project.absolute_path(&project_path, cx)
19590 } else {
19591 buffer
19592 .file()
19593 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19594 }
19595 })
19596 }
19597
19598 pub fn reveal_in_finder(
19599 &mut self,
19600 _: &RevealInFileManager,
19601 _window: &mut Window,
19602 cx: &mut Context<Self>,
19603 ) {
19604 if let Some(target) = self.target_file(cx) {
19605 cx.reveal_path(&target.abs_path(cx));
19606 }
19607 }
19608
19609 pub fn copy_path(
19610 &mut self,
19611 _: &zed_actions::workspace::CopyPath,
19612 _window: &mut Window,
19613 cx: &mut Context<Self>,
19614 ) {
19615 if let Some(path) = self.target_file_abs_path(cx)
19616 && let Some(path) = path.to_str()
19617 {
19618 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19619 } else {
19620 cx.propagate();
19621 }
19622 }
19623
19624 pub fn copy_relative_path(
19625 &mut self,
19626 _: &zed_actions::workspace::CopyRelativePath,
19627 _window: &mut Window,
19628 cx: &mut Context<Self>,
19629 ) {
19630 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19631 let project = self.project()?.read(cx);
19632 let path = buffer.read(cx).file()?.path();
19633 let path = path.display(project.path_style(cx));
19634 Some(path)
19635 }) {
19636 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19637 } else {
19638 cx.propagate();
19639 }
19640 }
19641
19642 /// Returns the project path for the editor's buffer, if any buffer is
19643 /// opened in the editor.
19644 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19645 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19646 buffer.read(cx).project_path(cx)
19647 } else {
19648 None
19649 }
19650 }
19651
19652 // Returns true if the editor handled a go-to-line request
19653 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19654 maybe!({
19655 let breakpoint_store = self.breakpoint_store.as_ref()?;
19656
19657 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19658 else {
19659 self.clear_row_highlights::<ActiveDebugLine>();
19660 return None;
19661 };
19662
19663 let position = active_stack_frame.position;
19664 let buffer_id = position.buffer_id?;
19665 let snapshot = self
19666 .project
19667 .as_ref()?
19668 .read(cx)
19669 .buffer_for_id(buffer_id, cx)?
19670 .read(cx)
19671 .snapshot();
19672
19673 let mut handled = false;
19674 for (id, ExcerptRange { context, .. }) in
19675 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19676 {
19677 if context.start.cmp(&position, &snapshot).is_ge()
19678 || context.end.cmp(&position, &snapshot).is_lt()
19679 {
19680 continue;
19681 }
19682 let snapshot = self.buffer.read(cx).snapshot(cx);
19683 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19684
19685 handled = true;
19686 self.clear_row_highlights::<ActiveDebugLine>();
19687
19688 self.go_to_line::<ActiveDebugLine>(
19689 multibuffer_anchor,
19690 Some(cx.theme().colors().editor_debugger_active_line_background),
19691 window,
19692 cx,
19693 );
19694
19695 cx.notify();
19696 }
19697
19698 handled.then_some(())
19699 })
19700 .is_some()
19701 }
19702
19703 pub fn copy_file_name_without_extension(
19704 &mut self,
19705 _: &CopyFileNameWithoutExtension,
19706 _: &mut Window,
19707 cx: &mut Context<Self>,
19708 ) {
19709 if let Some(file) = self.target_file(cx)
19710 && let Some(file_stem) = file.path().file_stem()
19711 {
19712 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19713 }
19714 }
19715
19716 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19717 if let Some(file) = self.target_file(cx)
19718 && let Some(name) = file.path().file_name()
19719 {
19720 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19721 }
19722 }
19723
19724 pub fn toggle_git_blame(
19725 &mut self,
19726 _: &::git::Blame,
19727 window: &mut Window,
19728 cx: &mut Context<Self>,
19729 ) {
19730 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19731
19732 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19733 self.start_git_blame(true, window, cx);
19734 }
19735
19736 cx.notify();
19737 }
19738
19739 pub fn toggle_git_blame_inline(
19740 &mut self,
19741 _: &ToggleGitBlameInline,
19742 window: &mut Window,
19743 cx: &mut Context<Self>,
19744 ) {
19745 self.toggle_git_blame_inline_internal(true, window, cx);
19746 cx.notify();
19747 }
19748
19749 pub fn open_git_blame_commit(
19750 &mut self,
19751 _: &OpenGitBlameCommit,
19752 window: &mut Window,
19753 cx: &mut Context<Self>,
19754 ) {
19755 self.open_git_blame_commit_internal(window, cx);
19756 }
19757
19758 fn open_git_blame_commit_internal(
19759 &mut self,
19760 window: &mut Window,
19761 cx: &mut Context<Self>,
19762 ) -> Option<()> {
19763 let blame = self.blame.as_ref()?;
19764 let snapshot = self.snapshot(window, cx);
19765 let cursor = self.selections.newest::<Point>(cx).head();
19766 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19767 let (_, blame_entry) = blame
19768 .update(cx, |blame, cx| {
19769 blame
19770 .blame_for_rows(
19771 &[RowInfo {
19772 buffer_id: Some(buffer.remote_id()),
19773 buffer_row: Some(point.row),
19774 ..Default::default()
19775 }],
19776 cx,
19777 )
19778 .next()
19779 })
19780 .flatten()?;
19781 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19782 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19783 let workspace = self.workspace()?.downgrade();
19784 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19785 None
19786 }
19787
19788 pub fn git_blame_inline_enabled(&self) -> bool {
19789 self.git_blame_inline_enabled
19790 }
19791
19792 pub fn toggle_selection_menu(
19793 &mut self,
19794 _: &ToggleSelectionMenu,
19795 _: &mut Window,
19796 cx: &mut Context<Self>,
19797 ) {
19798 self.show_selection_menu = self
19799 .show_selection_menu
19800 .map(|show_selections_menu| !show_selections_menu)
19801 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19802
19803 cx.notify();
19804 }
19805
19806 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19807 self.show_selection_menu
19808 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19809 }
19810
19811 fn start_git_blame(
19812 &mut self,
19813 user_triggered: bool,
19814 window: &mut Window,
19815 cx: &mut Context<Self>,
19816 ) {
19817 if let Some(project) = self.project() {
19818 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19819 && buffer.read(cx).file().is_none()
19820 {
19821 return;
19822 }
19823
19824 let focused = self.focus_handle(cx).contains_focused(window, cx);
19825
19826 let project = project.clone();
19827 let blame = cx
19828 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19829 self.blame_subscription =
19830 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19831 self.blame = Some(blame);
19832 }
19833 }
19834
19835 fn toggle_git_blame_inline_internal(
19836 &mut self,
19837 user_triggered: bool,
19838 window: &mut Window,
19839 cx: &mut Context<Self>,
19840 ) {
19841 if self.git_blame_inline_enabled {
19842 self.git_blame_inline_enabled = false;
19843 self.show_git_blame_inline = false;
19844 self.show_git_blame_inline_delay_task.take();
19845 } else {
19846 self.git_blame_inline_enabled = true;
19847 self.start_git_blame_inline(user_triggered, window, cx);
19848 }
19849
19850 cx.notify();
19851 }
19852
19853 fn start_git_blame_inline(
19854 &mut self,
19855 user_triggered: bool,
19856 window: &mut Window,
19857 cx: &mut Context<Self>,
19858 ) {
19859 self.start_git_blame(user_triggered, window, cx);
19860
19861 if ProjectSettings::get_global(cx)
19862 .git
19863 .inline_blame_delay()
19864 .is_some()
19865 {
19866 self.start_inline_blame_timer(window, cx);
19867 } else {
19868 self.show_git_blame_inline = true
19869 }
19870 }
19871
19872 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19873 self.blame.as_ref()
19874 }
19875
19876 pub fn show_git_blame_gutter(&self) -> bool {
19877 self.show_git_blame_gutter
19878 }
19879
19880 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19881 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19882 }
19883
19884 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19885 self.show_git_blame_inline
19886 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19887 && !self.newest_selection_head_on_empty_line(cx)
19888 && self.has_blame_entries(cx)
19889 }
19890
19891 fn has_blame_entries(&self, cx: &App) -> bool {
19892 self.blame()
19893 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19894 }
19895
19896 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19897 let cursor_anchor = self.selections.newest_anchor().head();
19898
19899 let snapshot = self.buffer.read(cx).snapshot(cx);
19900 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19901
19902 snapshot.line_len(buffer_row) == 0
19903 }
19904
19905 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19906 let buffer_and_selection = maybe!({
19907 let selection = self.selections.newest::<Point>(cx);
19908 let selection_range = selection.range();
19909
19910 let multi_buffer = self.buffer().read(cx);
19911 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19912 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19913
19914 let (buffer, range, _) = if selection.reversed {
19915 buffer_ranges.first()
19916 } else {
19917 buffer_ranges.last()
19918 }?;
19919
19920 let selection = text::ToPoint::to_point(&range.start, buffer).row
19921 ..text::ToPoint::to_point(&range.end, buffer).row;
19922 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19923 });
19924
19925 let Some((buffer, selection)) = buffer_and_selection else {
19926 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19927 };
19928
19929 let Some(project) = self.project() else {
19930 return Task::ready(Err(anyhow!("editor does not have project")));
19931 };
19932
19933 project.update(cx, |project, cx| {
19934 project.get_permalink_to_line(&buffer, selection, cx)
19935 })
19936 }
19937
19938 pub fn copy_permalink_to_line(
19939 &mut self,
19940 _: &CopyPermalinkToLine,
19941 window: &mut Window,
19942 cx: &mut Context<Self>,
19943 ) {
19944 let permalink_task = self.get_permalink_to_line(cx);
19945 let workspace = self.workspace();
19946
19947 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19948 Ok(permalink) => {
19949 cx.update(|_, cx| {
19950 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19951 })
19952 .ok();
19953 }
19954 Err(err) => {
19955 let message = format!("Failed to copy permalink: {err}");
19956
19957 anyhow::Result::<()>::Err(err).log_err();
19958
19959 if let Some(workspace) = workspace {
19960 workspace
19961 .update_in(cx, |workspace, _, cx| {
19962 struct CopyPermalinkToLine;
19963
19964 workspace.show_toast(
19965 Toast::new(
19966 NotificationId::unique::<CopyPermalinkToLine>(),
19967 message,
19968 ),
19969 cx,
19970 )
19971 })
19972 .ok();
19973 }
19974 }
19975 })
19976 .detach();
19977 }
19978
19979 pub fn copy_file_location(
19980 &mut self,
19981 _: &CopyFileLocation,
19982 _: &mut Window,
19983 cx: &mut Context<Self>,
19984 ) {
19985 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19986 if let Some(file) = self.target_file(cx) {
19987 let path = file.path().display(file.path_style(cx));
19988 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19989 }
19990 }
19991
19992 pub fn open_permalink_to_line(
19993 &mut self,
19994 _: &OpenPermalinkToLine,
19995 window: &mut Window,
19996 cx: &mut Context<Self>,
19997 ) {
19998 let permalink_task = self.get_permalink_to_line(cx);
19999 let workspace = self.workspace();
20000
20001 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20002 Ok(permalink) => {
20003 cx.update(|_, cx| {
20004 cx.open_url(permalink.as_ref());
20005 })
20006 .ok();
20007 }
20008 Err(err) => {
20009 let message = format!("Failed to open permalink: {err}");
20010
20011 anyhow::Result::<()>::Err(err).log_err();
20012
20013 if let Some(workspace) = workspace {
20014 workspace
20015 .update(cx, |workspace, cx| {
20016 struct OpenPermalinkToLine;
20017
20018 workspace.show_toast(
20019 Toast::new(
20020 NotificationId::unique::<OpenPermalinkToLine>(),
20021 message,
20022 ),
20023 cx,
20024 )
20025 })
20026 .ok();
20027 }
20028 }
20029 })
20030 .detach();
20031 }
20032
20033 pub fn insert_uuid_v4(
20034 &mut self,
20035 _: &InsertUuidV4,
20036 window: &mut Window,
20037 cx: &mut Context<Self>,
20038 ) {
20039 self.insert_uuid(UuidVersion::V4, window, cx);
20040 }
20041
20042 pub fn insert_uuid_v7(
20043 &mut self,
20044 _: &InsertUuidV7,
20045 window: &mut Window,
20046 cx: &mut Context<Self>,
20047 ) {
20048 self.insert_uuid(UuidVersion::V7, window, cx);
20049 }
20050
20051 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20052 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20053 self.transact(window, cx, |this, window, cx| {
20054 let edits = this
20055 .selections
20056 .all::<Point>(cx)
20057 .into_iter()
20058 .map(|selection| {
20059 let uuid = match version {
20060 UuidVersion::V4 => uuid::Uuid::new_v4(),
20061 UuidVersion::V7 => uuid::Uuid::now_v7(),
20062 };
20063
20064 (selection.range(), uuid.to_string())
20065 });
20066 this.edit(edits, cx);
20067 this.refresh_edit_prediction(true, false, window, cx);
20068 });
20069 }
20070
20071 pub fn open_selections_in_multibuffer(
20072 &mut self,
20073 _: &OpenSelectionsInMultibuffer,
20074 window: &mut Window,
20075 cx: &mut Context<Self>,
20076 ) {
20077 let multibuffer = self.buffer.read(cx);
20078
20079 let Some(buffer) = multibuffer.as_singleton() else {
20080 return;
20081 };
20082
20083 let Some(workspace) = self.workspace() else {
20084 return;
20085 };
20086
20087 let title = multibuffer.title(cx).to_string();
20088
20089 let locations = self
20090 .selections
20091 .all_anchors(cx)
20092 .iter()
20093 .map(|selection| {
20094 (
20095 buffer.clone(),
20096 (selection.start.text_anchor..selection.end.text_anchor)
20097 .to_point(buffer.read(cx)),
20098 )
20099 })
20100 .into_group_map();
20101
20102 cx.spawn_in(window, async move |_, cx| {
20103 workspace.update_in(cx, |workspace, window, cx| {
20104 Self::open_locations_in_multibuffer(
20105 workspace,
20106 locations,
20107 format!("Selections for '{title}'"),
20108 false,
20109 MultibufferSelectionMode::All,
20110 window,
20111 cx,
20112 );
20113 })
20114 })
20115 .detach();
20116 }
20117
20118 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20119 /// last highlight added will be used.
20120 ///
20121 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20122 pub fn highlight_rows<T: 'static>(
20123 &mut self,
20124 range: Range<Anchor>,
20125 color: Hsla,
20126 options: RowHighlightOptions,
20127 cx: &mut Context<Self>,
20128 ) {
20129 let snapshot = self.buffer().read(cx).snapshot(cx);
20130 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20131 let ix = row_highlights.binary_search_by(|highlight| {
20132 Ordering::Equal
20133 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20134 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20135 });
20136
20137 if let Err(mut ix) = ix {
20138 let index = post_inc(&mut self.highlight_order);
20139
20140 // If this range intersects with the preceding highlight, then merge it with
20141 // the preceding highlight. Otherwise insert a new highlight.
20142 let mut merged = false;
20143 if ix > 0 {
20144 let prev_highlight = &mut row_highlights[ix - 1];
20145 if prev_highlight
20146 .range
20147 .end
20148 .cmp(&range.start, &snapshot)
20149 .is_ge()
20150 {
20151 ix -= 1;
20152 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20153 prev_highlight.range.end = range.end;
20154 }
20155 merged = true;
20156 prev_highlight.index = index;
20157 prev_highlight.color = color;
20158 prev_highlight.options = options;
20159 }
20160 }
20161
20162 if !merged {
20163 row_highlights.insert(
20164 ix,
20165 RowHighlight {
20166 range,
20167 index,
20168 color,
20169 options,
20170 type_id: TypeId::of::<T>(),
20171 },
20172 );
20173 }
20174
20175 // If any of the following highlights intersect with this one, merge them.
20176 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20177 let highlight = &row_highlights[ix];
20178 if next_highlight
20179 .range
20180 .start
20181 .cmp(&highlight.range.end, &snapshot)
20182 .is_le()
20183 {
20184 if next_highlight
20185 .range
20186 .end
20187 .cmp(&highlight.range.end, &snapshot)
20188 .is_gt()
20189 {
20190 row_highlights[ix].range.end = next_highlight.range.end;
20191 }
20192 row_highlights.remove(ix + 1);
20193 } else {
20194 break;
20195 }
20196 }
20197 }
20198 }
20199
20200 /// Remove any highlighted row ranges of the given type that intersect the
20201 /// given ranges.
20202 pub fn remove_highlighted_rows<T: 'static>(
20203 &mut self,
20204 ranges_to_remove: Vec<Range<Anchor>>,
20205 cx: &mut Context<Self>,
20206 ) {
20207 let snapshot = self.buffer().read(cx).snapshot(cx);
20208 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20209 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20210 row_highlights.retain(|highlight| {
20211 while let Some(range_to_remove) = ranges_to_remove.peek() {
20212 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20213 Ordering::Less | Ordering::Equal => {
20214 ranges_to_remove.next();
20215 }
20216 Ordering::Greater => {
20217 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20218 Ordering::Less | Ordering::Equal => {
20219 return false;
20220 }
20221 Ordering::Greater => break,
20222 }
20223 }
20224 }
20225 }
20226
20227 true
20228 })
20229 }
20230
20231 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20232 pub fn clear_row_highlights<T: 'static>(&mut self) {
20233 self.highlighted_rows.remove(&TypeId::of::<T>());
20234 }
20235
20236 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20237 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20238 self.highlighted_rows
20239 .get(&TypeId::of::<T>())
20240 .map_or(&[] as &[_], |vec| vec.as_slice())
20241 .iter()
20242 .map(|highlight| (highlight.range.clone(), highlight.color))
20243 }
20244
20245 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20246 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20247 /// Allows to ignore certain kinds of highlights.
20248 pub fn highlighted_display_rows(
20249 &self,
20250 window: &mut Window,
20251 cx: &mut App,
20252 ) -> BTreeMap<DisplayRow, LineHighlight> {
20253 let snapshot = self.snapshot(window, cx);
20254 let mut used_highlight_orders = HashMap::default();
20255 self.highlighted_rows
20256 .iter()
20257 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20258 .fold(
20259 BTreeMap::<DisplayRow, LineHighlight>::new(),
20260 |mut unique_rows, highlight| {
20261 let start = highlight.range.start.to_display_point(&snapshot);
20262 let end = highlight.range.end.to_display_point(&snapshot);
20263 let start_row = start.row().0;
20264 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20265 && end.column() == 0
20266 {
20267 end.row().0.saturating_sub(1)
20268 } else {
20269 end.row().0
20270 };
20271 for row in start_row..=end_row {
20272 let used_index =
20273 used_highlight_orders.entry(row).or_insert(highlight.index);
20274 if highlight.index >= *used_index {
20275 *used_index = highlight.index;
20276 unique_rows.insert(
20277 DisplayRow(row),
20278 LineHighlight {
20279 include_gutter: highlight.options.include_gutter,
20280 border: None,
20281 background: highlight.color.into(),
20282 type_id: Some(highlight.type_id),
20283 },
20284 );
20285 }
20286 }
20287 unique_rows
20288 },
20289 )
20290 }
20291
20292 pub fn highlighted_display_row_for_autoscroll(
20293 &self,
20294 snapshot: &DisplaySnapshot,
20295 ) -> Option<DisplayRow> {
20296 self.highlighted_rows
20297 .values()
20298 .flat_map(|highlighted_rows| highlighted_rows.iter())
20299 .filter_map(|highlight| {
20300 if highlight.options.autoscroll {
20301 Some(highlight.range.start.to_display_point(snapshot).row())
20302 } else {
20303 None
20304 }
20305 })
20306 .min()
20307 }
20308
20309 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20310 self.highlight_background::<SearchWithinRange>(
20311 ranges,
20312 |colors| colors.colors().editor_document_highlight_read_background,
20313 cx,
20314 )
20315 }
20316
20317 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20318 self.breadcrumb_header = Some(new_header);
20319 }
20320
20321 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20322 self.clear_background_highlights::<SearchWithinRange>(cx);
20323 }
20324
20325 pub fn highlight_background<T: 'static>(
20326 &mut self,
20327 ranges: &[Range<Anchor>],
20328 color_fetcher: fn(&Theme) -> Hsla,
20329 cx: &mut Context<Self>,
20330 ) {
20331 self.background_highlights.insert(
20332 HighlightKey::Type(TypeId::of::<T>()),
20333 (color_fetcher, Arc::from(ranges)),
20334 );
20335 self.scrollbar_marker_state.dirty = true;
20336 cx.notify();
20337 }
20338
20339 pub fn highlight_background_key<T: 'static>(
20340 &mut self,
20341 key: usize,
20342 ranges: &[Range<Anchor>],
20343 color_fetcher: fn(&Theme) -> Hsla,
20344 cx: &mut Context<Self>,
20345 ) {
20346 self.background_highlights.insert(
20347 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20348 (color_fetcher, Arc::from(ranges)),
20349 );
20350 self.scrollbar_marker_state.dirty = true;
20351 cx.notify();
20352 }
20353
20354 pub fn clear_background_highlights<T: 'static>(
20355 &mut self,
20356 cx: &mut Context<Self>,
20357 ) -> Option<BackgroundHighlight> {
20358 let text_highlights = self
20359 .background_highlights
20360 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20361 if !text_highlights.1.is_empty() {
20362 self.scrollbar_marker_state.dirty = true;
20363 cx.notify();
20364 }
20365 Some(text_highlights)
20366 }
20367
20368 pub fn highlight_gutter<T: 'static>(
20369 &mut self,
20370 ranges: impl Into<Vec<Range<Anchor>>>,
20371 color_fetcher: fn(&App) -> Hsla,
20372 cx: &mut Context<Self>,
20373 ) {
20374 self.gutter_highlights
20375 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20376 cx.notify();
20377 }
20378
20379 pub fn clear_gutter_highlights<T: 'static>(
20380 &mut self,
20381 cx: &mut Context<Self>,
20382 ) -> Option<GutterHighlight> {
20383 cx.notify();
20384 self.gutter_highlights.remove(&TypeId::of::<T>())
20385 }
20386
20387 pub fn insert_gutter_highlight<T: 'static>(
20388 &mut self,
20389 range: Range<Anchor>,
20390 color_fetcher: fn(&App) -> Hsla,
20391 cx: &mut Context<Self>,
20392 ) {
20393 let snapshot = self.buffer().read(cx).snapshot(cx);
20394 let mut highlights = self
20395 .gutter_highlights
20396 .remove(&TypeId::of::<T>())
20397 .map(|(_, highlights)| highlights)
20398 .unwrap_or_default();
20399 let ix = highlights.binary_search_by(|highlight| {
20400 Ordering::Equal
20401 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20402 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20403 });
20404 if let Err(ix) = ix {
20405 highlights.insert(ix, range);
20406 }
20407 self.gutter_highlights
20408 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20409 }
20410
20411 pub fn remove_gutter_highlights<T: 'static>(
20412 &mut self,
20413 ranges_to_remove: Vec<Range<Anchor>>,
20414 cx: &mut Context<Self>,
20415 ) {
20416 let snapshot = self.buffer().read(cx).snapshot(cx);
20417 let Some((color_fetcher, mut gutter_highlights)) =
20418 self.gutter_highlights.remove(&TypeId::of::<T>())
20419 else {
20420 return;
20421 };
20422 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20423 gutter_highlights.retain(|highlight| {
20424 while let Some(range_to_remove) = ranges_to_remove.peek() {
20425 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20426 Ordering::Less | Ordering::Equal => {
20427 ranges_to_remove.next();
20428 }
20429 Ordering::Greater => {
20430 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20431 Ordering::Less | Ordering::Equal => {
20432 return false;
20433 }
20434 Ordering::Greater => break,
20435 }
20436 }
20437 }
20438 }
20439
20440 true
20441 });
20442 self.gutter_highlights
20443 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20444 }
20445
20446 #[cfg(feature = "test-support")]
20447 pub fn all_text_highlights(
20448 &self,
20449 window: &mut Window,
20450 cx: &mut Context<Self>,
20451 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20452 let snapshot = self.snapshot(window, cx);
20453 self.display_map.update(cx, |display_map, _| {
20454 display_map
20455 .all_text_highlights()
20456 .map(|highlight| {
20457 let (style, ranges) = highlight.as_ref();
20458 (
20459 *style,
20460 ranges
20461 .iter()
20462 .map(|range| range.clone().to_display_points(&snapshot))
20463 .collect(),
20464 )
20465 })
20466 .collect()
20467 })
20468 }
20469
20470 #[cfg(feature = "test-support")]
20471 pub fn all_text_background_highlights(
20472 &self,
20473 window: &mut Window,
20474 cx: &mut Context<Self>,
20475 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20476 let snapshot = self.snapshot(window, cx);
20477 let buffer = &snapshot.buffer_snapshot();
20478 let start = buffer.anchor_before(0);
20479 let end = buffer.anchor_after(buffer.len());
20480 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20481 }
20482
20483 #[cfg(any(test, feature = "test-support"))]
20484 pub fn sorted_background_highlights_in_range(
20485 &self,
20486 search_range: Range<Anchor>,
20487 display_snapshot: &DisplaySnapshot,
20488 theme: &Theme,
20489 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20490 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20491 res.sort_by(|a, b| {
20492 a.0.start
20493 .cmp(&b.0.start)
20494 .then_with(|| a.0.end.cmp(&b.0.end))
20495 .then_with(|| a.1.cmp(&b.1))
20496 });
20497 res
20498 }
20499
20500 #[cfg(feature = "test-support")]
20501 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20502 let snapshot = self.buffer().read(cx).snapshot(cx);
20503
20504 let highlights = self
20505 .background_highlights
20506 .get(&HighlightKey::Type(TypeId::of::<
20507 items::BufferSearchHighlights,
20508 >()));
20509
20510 if let Some((_color, ranges)) = highlights {
20511 ranges
20512 .iter()
20513 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20514 .collect_vec()
20515 } else {
20516 vec![]
20517 }
20518 }
20519
20520 fn document_highlights_for_position<'a>(
20521 &'a self,
20522 position: Anchor,
20523 buffer: &'a MultiBufferSnapshot,
20524 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20525 let read_highlights = self
20526 .background_highlights
20527 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20528 .map(|h| &h.1);
20529 let write_highlights = self
20530 .background_highlights
20531 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20532 .map(|h| &h.1);
20533 let left_position = position.bias_left(buffer);
20534 let right_position = position.bias_right(buffer);
20535 read_highlights
20536 .into_iter()
20537 .chain(write_highlights)
20538 .flat_map(move |ranges| {
20539 let start_ix = match ranges.binary_search_by(|probe| {
20540 let cmp = probe.end.cmp(&left_position, buffer);
20541 if cmp.is_ge() {
20542 Ordering::Greater
20543 } else {
20544 Ordering::Less
20545 }
20546 }) {
20547 Ok(i) | Err(i) => i,
20548 };
20549
20550 ranges[start_ix..]
20551 .iter()
20552 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20553 })
20554 }
20555
20556 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20557 self.background_highlights
20558 .get(&HighlightKey::Type(TypeId::of::<T>()))
20559 .is_some_and(|(_, highlights)| !highlights.is_empty())
20560 }
20561
20562 /// Returns all background highlights for a given range.
20563 ///
20564 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20565 pub fn background_highlights_in_range(
20566 &self,
20567 search_range: Range<Anchor>,
20568 display_snapshot: &DisplaySnapshot,
20569 theme: &Theme,
20570 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20571 let mut results = Vec::new();
20572 for (color_fetcher, ranges) in self.background_highlights.values() {
20573 let color = color_fetcher(theme);
20574 let start_ix = match ranges.binary_search_by(|probe| {
20575 let cmp = probe
20576 .end
20577 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20578 if cmp.is_gt() {
20579 Ordering::Greater
20580 } else {
20581 Ordering::Less
20582 }
20583 }) {
20584 Ok(i) | Err(i) => i,
20585 };
20586 for range in &ranges[start_ix..] {
20587 if range
20588 .start
20589 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20590 .is_ge()
20591 {
20592 break;
20593 }
20594
20595 let start = range.start.to_display_point(display_snapshot);
20596 let end = range.end.to_display_point(display_snapshot);
20597 results.push((start..end, color))
20598 }
20599 }
20600 results
20601 }
20602
20603 pub fn gutter_highlights_in_range(
20604 &self,
20605 search_range: Range<Anchor>,
20606 display_snapshot: &DisplaySnapshot,
20607 cx: &App,
20608 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20609 let mut results = Vec::new();
20610 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20611 let color = color_fetcher(cx);
20612 let start_ix = match ranges.binary_search_by(|probe| {
20613 let cmp = probe
20614 .end
20615 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20616 if cmp.is_gt() {
20617 Ordering::Greater
20618 } else {
20619 Ordering::Less
20620 }
20621 }) {
20622 Ok(i) | Err(i) => i,
20623 };
20624 for range in &ranges[start_ix..] {
20625 if range
20626 .start
20627 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20628 .is_ge()
20629 {
20630 break;
20631 }
20632
20633 let start = range.start.to_display_point(display_snapshot);
20634 let end = range.end.to_display_point(display_snapshot);
20635 results.push((start..end, color))
20636 }
20637 }
20638 results
20639 }
20640
20641 /// Get the text ranges corresponding to the redaction query
20642 pub fn redacted_ranges(
20643 &self,
20644 search_range: Range<Anchor>,
20645 display_snapshot: &DisplaySnapshot,
20646 cx: &App,
20647 ) -> Vec<Range<DisplayPoint>> {
20648 display_snapshot
20649 .buffer_snapshot()
20650 .redacted_ranges(search_range, |file| {
20651 if let Some(file) = file {
20652 file.is_private()
20653 && EditorSettings::get(
20654 Some(SettingsLocation {
20655 worktree_id: file.worktree_id(cx),
20656 path: file.path().as_ref(),
20657 }),
20658 cx,
20659 )
20660 .redact_private_values
20661 } else {
20662 false
20663 }
20664 })
20665 .map(|range| {
20666 range.start.to_display_point(display_snapshot)
20667 ..range.end.to_display_point(display_snapshot)
20668 })
20669 .collect()
20670 }
20671
20672 pub fn highlight_text_key<T: 'static>(
20673 &mut self,
20674 key: usize,
20675 ranges: Vec<Range<Anchor>>,
20676 style: HighlightStyle,
20677 cx: &mut Context<Self>,
20678 ) {
20679 self.display_map.update(cx, |map, _| {
20680 map.highlight_text(
20681 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20682 ranges,
20683 style,
20684 );
20685 });
20686 cx.notify();
20687 }
20688
20689 pub fn highlight_text<T: 'static>(
20690 &mut self,
20691 ranges: Vec<Range<Anchor>>,
20692 style: HighlightStyle,
20693 cx: &mut Context<Self>,
20694 ) {
20695 self.display_map.update(cx, |map, _| {
20696 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20697 });
20698 cx.notify();
20699 }
20700
20701 pub(crate) fn highlight_inlays<T: 'static>(
20702 &mut self,
20703 highlights: Vec<InlayHighlight>,
20704 style: HighlightStyle,
20705 cx: &mut Context<Self>,
20706 ) {
20707 self.display_map.update(cx, |map, _| {
20708 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20709 });
20710 cx.notify();
20711 }
20712
20713 pub fn text_highlights<'a, T: 'static>(
20714 &'a self,
20715 cx: &'a App,
20716 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20717 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20718 }
20719
20720 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20721 let cleared = self
20722 .display_map
20723 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20724 if cleared {
20725 cx.notify();
20726 }
20727 }
20728
20729 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20730 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20731 && self.focus_handle.is_focused(window)
20732 }
20733
20734 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20735 self.show_cursor_when_unfocused = is_enabled;
20736 cx.notify();
20737 }
20738
20739 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20740 cx.notify();
20741 }
20742
20743 fn on_debug_session_event(
20744 &mut self,
20745 _session: Entity<Session>,
20746 event: &SessionEvent,
20747 cx: &mut Context<Self>,
20748 ) {
20749 if let SessionEvent::InvalidateInlineValue = event {
20750 self.refresh_inline_values(cx);
20751 }
20752 }
20753
20754 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20755 let Some(project) = self.project.clone() else {
20756 return;
20757 };
20758
20759 if !self.inline_value_cache.enabled {
20760 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20761 self.splice_inlays(&inlays, Vec::new(), cx);
20762 return;
20763 }
20764
20765 let current_execution_position = self
20766 .highlighted_rows
20767 .get(&TypeId::of::<ActiveDebugLine>())
20768 .and_then(|lines| lines.last().map(|line| line.range.end));
20769
20770 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20771 let inline_values = editor
20772 .update(cx, |editor, cx| {
20773 let Some(current_execution_position) = current_execution_position else {
20774 return Some(Task::ready(Ok(Vec::new())));
20775 };
20776
20777 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20778 let snapshot = buffer.snapshot(cx);
20779
20780 let excerpt = snapshot.excerpt_containing(
20781 current_execution_position..current_execution_position,
20782 )?;
20783
20784 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20785 })?;
20786
20787 let range =
20788 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20789
20790 project.inline_values(buffer, range, cx)
20791 })
20792 .ok()
20793 .flatten()?
20794 .await
20795 .context("refreshing debugger inlays")
20796 .log_err()?;
20797
20798 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20799
20800 for (buffer_id, inline_value) in inline_values
20801 .into_iter()
20802 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20803 {
20804 buffer_inline_values
20805 .entry(buffer_id)
20806 .or_default()
20807 .push(inline_value);
20808 }
20809
20810 editor
20811 .update(cx, |editor, cx| {
20812 let snapshot = editor.buffer.read(cx).snapshot(cx);
20813 let mut new_inlays = Vec::default();
20814
20815 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20816 let buffer_id = buffer_snapshot.remote_id();
20817 buffer_inline_values
20818 .get(&buffer_id)
20819 .into_iter()
20820 .flatten()
20821 .for_each(|hint| {
20822 let inlay = Inlay::debugger(
20823 post_inc(&mut editor.next_inlay_id),
20824 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20825 hint.text(),
20826 );
20827 if !inlay.text().chars().contains(&'\n') {
20828 new_inlays.push(inlay);
20829 }
20830 });
20831 }
20832
20833 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20834 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20835
20836 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20837 })
20838 .ok()?;
20839 Some(())
20840 });
20841 }
20842
20843 fn on_buffer_event(
20844 &mut self,
20845 multibuffer: &Entity<MultiBuffer>,
20846 event: &multi_buffer::Event,
20847 window: &mut Window,
20848 cx: &mut Context<Self>,
20849 ) {
20850 match event {
20851 multi_buffer::Event::Edited {
20852 singleton_buffer_edited,
20853 edited_buffer,
20854 } => {
20855 self.scrollbar_marker_state.dirty = true;
20856 self.active_indent_guides_state.dirty = true;
20857 self.refresh_active_diagnostics(cx);
20858 self.refresh_code_actions(window, cx);
20859 self.refresh_selected_text_highlights(true, window, cx);
20860 self.refresh_single_line_folds(window, cx);
20861 refresh_matching_bracket_highlights(self, cx);
20862 if self.has_active_edit_prediction() {
20863 self.update_visible_edit_prediction(window, cx);
20864 }
20865 if let Some(project) = self.project.as_ref()
20866 && let Some(edited_buffer) = edited_buffer
20867 {
20868 project.update(cx, |project, cx| {
20869 self.registered_buffers
20870 .entry(edited_buffer.read(cx).remote_id())
20871 .or_insert_with(|| {
20872 project.register_buffer_with_language_servers(edited_buffer, cx)
20873 });
20874 });
20875 }
20876 cx.emit(EditorEvent::BufferEdited);
20877 cx.emit(SearchEvent::MatchesInvalidated);
20878
20879 if let Some(buffer) = edited_buffer {
20880 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20881 }
20882
20883 if *singleton_buffer_edited {
20884 if let Some(buffer) = edited_buffer
20885 && buffer.read(cx).file().is_none()
20886 {
20887 cx.emit(EditorEvent::TitleChanged);
20888 }
20889 if let Some(project) = &self.project {
20890 #[allow(clippy::mutable_key_type)]
20891 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20892 multibuffer
20893 .all_buffers()
20894 .into_iter()
20895 .filter_map(|buffer| {
20896 buffer.update(cx, |buffer, cx| {
20897 let language = buffer.language()?;
20898 let should_discard = project.update(cx, |project, cx| {
20899 project.is_local()
20900 && !project.has_language_servers_for(buffer, cx)
20901 });
20902 should_discard.not().then_some(language.clone())
20903 })
20904 })
20905 .collect::<HashSet<_>>()
20906 });
20907 if !languages_affected.is_empty() {
20908 self.refresh_inlay_hints(
20909 InlayHintRefreshReason::BufferEdited(languages_affected),
20910 cx,
20911 );
20912 }
20913 }
20914 }
20915
20916 let Some(project) = &self.project else { return };
20917 let (telemetry, is_via_ssh) = {
20918 let project = project.read(cx);
20919 let telemetry = project.client().telemetry().clone();
20920 let is_via_ssh = project.is_via_remote_server();
20921 (telemetry, is_via_ssh)
20922 };
20923 refresh_linked_ranges(self, window, cx);
20924 telemetry.log_edit_event("editor", is_via_ssh);
20925 }
20926 multi_buffer::Event::ExcerptsAdded {
20927 buffer,
20928 predecessor,
20929 excerpts,
20930 } => {
20931 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20932 let buffer_id = buffer.read(cx).remote_id();
20933 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20934 && let Some(project) = &self.project
20935 {
20936 update_uncommitted_diff_for_buffer(
20937 cx.entity(),
20938 project,
20939 [buffer.clone()],
20940 self.buffer.clone(),
20941 cx,
20942 )
20943 .detach();
20944 }
20945 if self.active_diagnostics != ActiveDiagnostic::All {
20946 self.update_lsp_data(false, Some(buffer_id), window, cx);
20947 }
20948 cx.emit(EditorEvent::ExcerptsAdded {
20949 buffer: buffer.clone(),
20950 predecessor: *predecessor,
20951 excerpts: excerpts.clone(),
20952 });
20953 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20954 }
20955 multi_buffer::Event::ExcerptsRemoved {
20956 ids,
20957 removed_buffer_ids,
20958 } => {
20959 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20960 let buffer = self.buffer.read(cx);
20961 self.registered_buffers
20962 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20963 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20964 cx.emit(EditorEvent::ExcerptsRemoved {
20965 ids: ids.clone(),
20966 removed_buffer_ids: removed_buffer_ids.clone(),
20967 });
20968 }
20969 multi_buffer::Event::ExcerptsEdited {
20970 excerpt_ids,
20971 buffer_ids,
20972 } => {
20973 self.display_map.update(cx, |map, cx| {
20974 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20975 });
20976 cx.emit(EditorEvent::ExcerptsEdited {
20977 ids: excerpt_ids.clone(),
20978 });
20979 }
20980 multi_buffer::Event::ExcerptsExpanded { ids } => {
20981 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20982 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20983 }
20984 multi_buffer::Event::Reparsed(buffer_id) => {
20985 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20986 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20987
20988 cx.emit(EditorEvent::Reparsed(*buffer_id));
20989 }
20990 multi_buffer::Event::DiffHunksToggled => {
20991 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20992 }
20993 multi_buffer::Event::LanguageChanged(buffer_id) => {
20994 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20995 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20996 cx.emit(EditorEvent::Reparsed(*buffer_id));
20997 cx.notify();
20998 }
20999 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21000 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21001 multi_buffer::Event::FileHandleChanged
21002 | multi_buffer::Event::Reloaded
21003 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21004 multi_buffer::Event::DiagnosticsUpdated => {
21005 self.update_diagnostics_state(window, cx);
21006 }
21007 _ => {}
21008 };
21009 }
21010
21011 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21012 if !self.diagnostics_enabled() {
21013 return;
21014 }
21015 self.refresh_active_diagnostics(cx);
21016 self.refresh_inline_diagnostics(true, window, cx);
21017 self.scrollbar_marker_state.dirty = true;
21018 cx.notify();
21019 }
21020
21021 pub fn start_temporary_diff_override(&mut self) {
21022 self.load_diff_task.take();
21023 self.temporary_diff_override = true;
21024 }
21025
21026 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21027 self.temporary_diff_override = false;
21028 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21029 self.buffer.update(cx, |buffer, cx| {
21030 buffer.set_all_diff_hunks_collapsed(cx);
21031 });
21032
21033 if let Some(project) = self.project.clone() {
21034 self.load_diff_task = Some(
21035 update_uncommitted_diff_for_buffer(
21036 cx.entity(),
21037 &project,
21038 self.buffer.read(cx).all_buffers(),
21039 self.buffer.clone(),
21040 cx,
21041 )
21042 .shared(),
21043 );
21044 }
21045 }
21046
21047 fn on_display_map_changed(
21048 &mut self,
21049 _: Entity<DisplayMap>,
21050 _: &mut Window,
21051 cx: &mut Context<Self>,
21052 ) {
21053 cx.notify();
21054 }
21055
21056 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21057 if self.diagnostics_enabled() {
21058 let new_severity = EditorSettings::get_global(cx)
21059 .diagnostics_max_severity
21060 .unwrap_or(DiagnosticSeverity::Hint);
21061 self.set_max_diagnostics_severity(new_severity, cx);
21062 }
21063 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21064 self.update_edit_prediction_settings(cx);
21065 self.refresh_edit_prediction(true, false, window, cx);
21066 self.refresh_inline_values(cx);
21067 self.refresh_inlay_hints(
21068 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21069 self.selections.newest_anchor().head(),
21070 &self.buffer.read(cx).snapshot(cx),
21071 cx,
21072 )),
21073 cx,
21074 );
21075
21076 let old_cursor_shape = self.cursor_shape;
21077 let old_show_breadcrumbs = self.show_breadcrumbs;
21078
21079 {
21080 let editor_settings = EditorSettings::get_global(cx);
21081 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21082 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21083 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21084 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21085 }
21086
21087 if old_cursor_shape != self.cursor_shape {
21088 cx.emit(EditorEvent::CursorShapeChanged);
21089 }
21090
21091 if old_show_breadcrumbs != self.show_breadcrumbs {
21092 cx.emit(EditorEvent::BreadcrumbsChanged);
21093 }
21094
21095 let project_settings = ProjectSettings::get_global(cx);
21096 self.serialize_dirty_buffers =
21097 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21098
21099 if self.mode.is_full() {
21100 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21101 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21102 if self.show_inline_diagnostics != show_inline_diagnostics {
21103 self.show_inline_diagnostics = show_inline_diagnostics;
21104 self.refresh_inline_diagnostics(false, window, cx);
21105 }
21106
21107 if self.git_blame_inline_enabled != inline_blame_enabled {
21108 self.toggle_git_blame_inline_internal(false, window, cx);
21109 }
21110
21111 let minimap_settings = EditorSettings::get_global(cx).minimap;
21112 if self.minimap_visibility != MinimapVisibility::Disabled {
21113 if self.minimap_visibility.settings_visibility()
21114 != minimap_settings.minimap_enabled()
21115 {
21116 self.set_minimap_visibility(
21117 MinimapVisibility::for_mode(self.mode(), cx),
21118 window,
21119 cx,
21120 );
21121 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21122 minimap_entity.update(cx, |minimap_editor, cx| {
21123 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21124 })
21125 }
21126 }
21127 }
21128
21129 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21130 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21131 }) {
21132 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21133 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21134 }
21135 self.refresh_colors(false, None, window, cx);
21136 }
21137
21138 cx.notify();
21139 }
21140
21141 pub fn set_searchable(&mut self, searchable: bool) {
21142 self.searchable = searchable;
21143 }
21144
21145 pub fn searchable(&self) -> bool {
21146 self.searchable
21147 }
21148
21149 fn open_proposed_changes_editor(
21150 &mut self,
21151 _: &OpenProposedChangesEditor,
21152 window: &mut Window,
21153 cx: &mut Context<Self>,
21154 ) {
21155 let Some(workspace) = self.workspace() else {
21156 cx.propagate();
21157 return;
21158 };
21159
21160 let selections = self.selections.all::<usize>(cx);
21161 let multi_buffer = self.buffer.read(cx);
21162 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21163 let mut new_selections_by_buffer = HashMap::default();
21164 for selection in selections {
21165 for (buffer, range, _) in
21166 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21167 {
21168 let mut range = range.to_point(buffer);
21169 range.start.column = 0;
21170 range.end.column = buffer.line_len(range.end.row);
21171 new_selections_by_buffer
21172 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21173 .or_insert(Vec::new())
21174 .push(range)
21175 }
21176 }
21177
21178 let proposed_changes_buffers = new_selections_by_buffer
21179 .into_iter()
21180 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21181 .collect::<Vec<_>>();
21182 let proposed_changes_editor = cx.new(|cx| {
21183 ProposedChangesEditor::new(
21184 "Proposed changes",
21185 proposed_changes_buffers,
21186 self.project.clone(),
21187 window,
21188 cx,
21189 )
21190 });
21191
21192 window.defer(cx, move |window, cx| {
21193 workspace.update(cx, |workspace, cx| {
21194 workspace.active_pane().update(cx, |pane, cx| {
21195 pane.add_item(
21196 Box::new(proposed_changes_editor),
21197 true,
21198 true,
21199 None,
21200 window,
21201 cx,
21202 );
21203 });
21204 });
21205 });
21206 }
21207
21208 pub fn open_excerpts_in_split(
21209 &mut self,
21210 _: &OpenExcerptsSplit,
21211 window: &mut Window,
21212 cx: &mut Context<Self>,
21213 ) {
21214 self.open_excerpts_common(None, true, window, cx)
21215 }
21216
21217 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21218 self.open_excerpts_common(None, false, window, cx)
21219 }
21220
21221 fn open_excerpts_common(
21222 &mut self,
21223 jump_data: Option<JumpData>,
21224 split: bool,
21225 window: &mut Window,
21226 cx: &mut Context<Self>,
21227 ) {
21228 let Some(workspace) = self.workspace() else {
21229 cx.propagate();
21230 return;
21231 };
21232
21233 if self.buffer.read(cx).is_singleton() {
21234 cx.propagate();
21235 return;
21236 }
21237
21238 let mut new_selections_by_buffer = HashMap::default();
21239 match &jump_data {
21240 Some(JumpData::MultiBufferPoint {
21241 excerpt_id,
21242 position,
21243 anchor,
21244 line_offset_from_top,
21245 }) => {
21246 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21247 if let Some(buffer) = multi_buffer_snapshot
21248 .buffer_id_for_excerpt(*excerpt_id)
21249 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21250 {
21251 let buffer_snapshot = buffer.read(cx).snapshot();
21252 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21253 language::ToPoint::to_point(anchor, &buffer_snapshot)
21254 } else {
21255 buffer_snapshot.clip_point(*position, Bias::Left)
21256 };
21257 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21258 new_selections_by_buffer.insert(
21259 buffer,
21260 (
21261 vec![jump_to_offset..jump_to_offset],
21262 Some(*line_offset_from_top),
21263 ),
21264 );
21265 }
21266 }
21267 Some(JumpData::MultiBufferRow {
21268 row,
21269 line_offset_from_top,
21270 }) => {
21271 let point = MultiBufferPoint::new(row.0, 0);
21272 if let Some((buffer, buffer_point, _)) =
21273 self.buffer.read(cx).point_to_buffer_point(point, cx)
21274 {
21275 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21276 new_selections_by_buffer
21277 .entry(buffer)
21278 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21279 .0
21280 .push(buffer_offset..buffer_offset)
21281 }
21282 }
21283 None => {
21284 let selections = self.selections.all::<usize>(cx);
21285 let multi_buffer = self.buffer.read(cx);
21286 for selection in selections {
21287 for (snapshot, range, _, anchor) in multi_buffer
21288 .snapshot(cx)
21289 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21290 {
21291 if let Some(anchor) = anchor {
21292 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21293 else {
21294 continue;
21295 };
21296 let offset = text::ToOffset::to_offset(
21297 &anchor.text_anchor,
21298 &buffer_handle.read(cx).snapshot(),
21299 );
21300 let range = offset..offset;
21301 new_selections_by_buffer
21302 .entry(buffer_handle)
21303 .or_insert((Vec::new(), None))
21304 .0
21305 .push(range)
21306 } else {
21307 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21308 else {
21309 continue;
21310 };
21311 new_selections_by_buffer
21312 .entry(buffer_handle)
21313 .or_insert((Vec::new(), None))
21314 .0
21315 .push(range)
21316 }
21317 }
21318 }
21319 }
21320 }
21321
21322 new_selections_by_buffer
21323 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21324
21325 if new_selections_by_buffer.is_empty() {
21326 return;
21327 }
21328
21329 // We defer the pane interaction because we ourselves are a workspace item
21330 // and activating a new item causes the pane to call a method on us reentrantly,
21331 // which panics if we're on the stack.
21332 window.defer(cx, move |window, cx| {
21333 workspace.update(cx, |workspace, cx| {
21334 let pane = if split {
21335 workspace.adjacent_pane(window, cx)
21336 } else {
21337 workspace.active_pane().clone()
21338 };
21339
21340 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21341 let editor = buffer
21342 .read(cx)
21343 .file()
21344 .is_none()
21345 .then(|| {
21346 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21347 // so `workspace.open_project_item` will never find them, always opening a new editor.
21348 // Instead, we try to activate the existing editor in the pane first.
21349 let (editor, pane_item_index) =
21350 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21351 let editor = item.downcast::<Editor>()?;
21352 let singleton_buffer =
21353 editor.read(cx).buffer().read(cx).as_singleton()?;
21354 if singleton_buffer == buffer {
21355 Some((editor, i))
21356 } else {
21357 None
21358 }
21359 })?;
21360 pane.update(cx, |pane, cx| {
21361 pane.activate_item(pane_item_index, true, true, window, cx)
21362 });
21363 Some(editor)
21364 })
21365 .flatten()
21366 .unwrap_or_else(|| {
21367 workspace.open_project_item::<Self>(
21368 pane.clone(),
21369 buffer,
21370 true,
21371 true,
21372 window,
21373 cx,
21374 )
21375 });
21376
21377 editor.update(cx, |editor, cx| {
21378 let autoscroll = match scroll_offset {
21379 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21380 None => Autoscroll::newest(),
21381 };
21382 let nav_history = editor.nav_history.take();
21383 editor.change_selections(
21384 SelectionEffects::scroll(autoscroll),
21385 window,
21386 cx,
21387 |s| {
21388 s.select_ranges(ranges);
21389 },
21390 );
21391 editor.nav_history = nav_history;
21392 });
21393 }
21394 })
21395 });
21396 }
21397
21398 // For now, don't allow opening excerpts in buffers that aren't backed by
21399 // regular project files.
21400 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21401 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21402 }
21403
21404 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21405 let snapshot = self.buffer.read(cx).read(cx);
21406 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21407 Some(
21408 ranges
21409 .iter()
21410 .map(move |range| {
21411 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21412 })
21413 .collect(),
21414 )
21415 }
21416
21417 fn selection_replacement_ranges(
21418 &self,
21419 range: Range<OffsetUtf16>,
21420 cx: &mut App,
21421 ) -> Vec<Range<OffsetUtf16>> {
21422 let selections = self.selections.all::<OffsetUtf16>(cx);
21423 let newest_selection = selections
21424 .iter()
21425 .max_by_key(|selection| selection.id)
21426 .unwrap();
21427 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21428 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21429 let snapshot = self.buffer.read(cx).read(cx);
21430 selections
21431 .into_iter()
21432 .map(|mut selection| {
21433 selection.start.0 =
21434 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21435 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21436 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21437 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21438 })
21439 .collect()
21440 }
21441
21442 fn report_editor_event(
21443 &self,
21444 reported_event: ReportEditorEvent,
21445 file_extension: Option<String>,
21446 cx: &App,
21447 ) {
21448 if cfg!(any(test, feature = "test-support")) {
21449 return;
21450 }
21451
21452 let Some(project) = &self.project else { return };
21453
21454 // If None, we are in a file without an extension
21455 let file = self
21456 .buffer
21457 .read(cx)
21458 .as_singleton()
21459 .and_then(|b| b.read(cx).file());
21460 let file_extension = file_extension.or(file
21461 .as_ref()
21462 .and_then(|file| Path::new(file.file_name(cx)).extension())
21463 .and_then(|e| e.to_str())
21464 .map(|a| a.to_string()));
21465
21466 let vim_mode = vim_enabled(cx);
21467
21468 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21469 let copilot_enabled = edit_predictions_provider
21470 == language::language_settings::EditPredictionProvider::Copilot;
21471 let copilot_enabled_for_language = self
21472 .buffer
21473 .read(cx)
21474 .language_settings(cx)
21475 .show_edit_predictions;
21476
21477 let project = project.read(cx);
21478 let event_type = reported_event.event_type();
21479
21480 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21481 telemetry::event!(
21482 event_type,
21483 type = if auto_saved {"autosave"} else {"manual"},
21484 file_extension,
21485 vim_mode,
21486 copilot_enabled,
21487 copilot_enabled_for_language,
21488 edit_predictions_provider,
21489 is_via_ssh = project.is_via_remote_server(),
21490 );
21491 } else {
21492 telemetry::event!(
21493 event_type,
21494 file_extension,
21495 vim_mode,
21496 copilot_enabled,
21497 copilot_enabled_for_language,
21498 edit_predictions_provider,
21499 is_via_ssh = project.is_via_remote_server(),
21500 );
21501 };
21502 }
21503
21504 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21505 /// with each line being an array of {text, highlight} objects.
21506 fn copy_highlight_json(
21507 &mut self,
21508 _: &CopyHighlightJson,
21509 window: &mut Window,
21510 cx: &mut Context<Self>,
21511 ) {
21512 #[derive(Serialize)]
21513 struct Chunk<'a> {
21514 text: String,
21515 highlight: Option<&'a str>,
21516 }
21517
21518 let snapshot = self.buffer.read(cx).snapshot(cx);
21519 let range = self
21520 .selected_text_range(false, window, cx)
21521 .and_then(|selection| {
21522 if selection.range.is_empty() {
21523 None
21524 } else {
21525 Some(selection.range)
21526 }
21527 })
21528 .unwrap_or_else(|| 0..snapshot.len());
21529
21530 let chunks = snapshot.chunks(range, true);
21531 let mut lines = Vec::new();
21532 let mut line: VecDeque<Chunk> = VecDeque::new();
21533
21534 let Some(style) = self.style.as_ref() else {
21535 return;
21536 };
21537
21538 for chunk in chunks {
21539 let highlight = chunk
21540 .syntax_highlight_id
21541 .and_then(|id| id.name(&style.syntax));
21542 let mut chunk_lines = chunk.text.split('\n').peekable();
21543 while let Some(text) = chunk_lines.next() {
21544 let mut merged_with_last_token = false;
21545 if let Some(last_token) = line.back_mut()
21546 && last_token.highlight == highlight
21547 {
21548 last_token.text.push_str(text);
21549 merged_with_last_token = true;
21550 }
21551
21552 if !merged_with_last_token {
21553 line.push_back(Chunk {
21554 text: text.into(),
21555 highlight,
21556 });
21557 }
21558
21559 if chunk_lines.peek().is_some() {
21560 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21561 line.pop_front();
21562 }
21563 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21564 line.pop_back();
21565 }
21566
21567 lines.push(mem::take(&mut line));
21568 }
21569 }
21570 }
21571
21572 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21573 return;
21574 };
21575 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21576 }
21577
21578 pub fn open_context_menu(
21579 &mut self,
21580 _: &OpenContextMenu,
21581 window: &mut Window,
21582 cx: &mut Context<Self>,
21583 ) {
21584 self.request_autoscroll(Autoscroll::newest(), cx);
21585 let position = self.selections.newest_display(cx).start;
21586 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21587 }
21588
21589 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21590 &self.inlay_hint_cache
21591 }
21592
21593 pub fn replay_insert_event(
21594 &mut self,
21595 text: &str,
21596 relative_utf16_range: Option<Range<isize>>,
21597 window: &mut Window,
21598 cx: &mut Context<Self>,
21599 ) {
21600 if !self.input_enabled {
21601 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21602 return;
21603 }
21604 if let Some(relative_utf16_range) = relative_utf16_range {
21605 let selections = self.selections.all::<OffsetUtf16>(cx);
21606 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21607 let new_ranges = selections.into_iter().map(|range| {
21608 let start = OffsetUtf16(
21609 range
21610 .head()
21611 .0
21612 .saturating_add_signed(relative_utf16_range.start),
21613 );
21614 let end = OffsetUtf16(
21615 range
21616 .head()
21617 .0
21618 .saturating_add_signed(relative_utf16_range.end),
21619 );
21620 start..end
21621 });
21622 s.select_ranges(new_ranges);
21623 });
21624 }
21625
21626 self.handle_input(text, window, cx);
21627 }
21628
21629 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21630 let Some(provider) = self.semantics_provider.as_ref() else {
21631 return false;
21632 };
21633
21634 let mut supports = false;
21635 self.buffer().update(cx, |this, cx| {
21636 this.for_each_buffer(|buffer| {
21637 supports |= provider.supports_inlay_hints(buffer, cx);
21638 });
21639 });
21640
21641 supports
21642 }
21643
21644 pub fn is_focused(&self, window: &Window) -> bool {
21645 self.focus_handle.is_focused(window)
21646 }
21647
21648 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21649 cx.emit(EditorEvent::Focused);
21650
21651 if let Some(descendant) = self
21652 .last_focused_descendant
21653 .take()
21654 .and_then(|descendant| descendant.upgrade())
21655 {
21656 window.focus(&descendant);
21657 } else {
21658 if let Some(blame) = self.blame.as_ref() {
21659 blame.update(cx, GitBlame::focus)
21660 }
21661
21662 self.blink_manager.update(cx, BlinkManager::enable);
21663 self.show_cursor_names(window, cx);
21664 self.buffer.update(cx, |buffer, cx| {
21665 buffer.finalize_last_transaction(cx);
21666 if self.leader_id.is_none() {
21667 buffer.set_active_selections(
21668 &self.selections.disjoint_anchors_arc(),
21669 self.selections.line_mode(),
21670 self.cursor_shape,
21671 cx,
21672 );
21673 }
21674 });
21675 }
21676 }
21677
21678 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21679 cx.emit(EditorEvent::FocusedIn)
21680 }
21681
21682 fn handle_focus_out(
21683 &mut self,
21684 event: FocusOutEvent,
21685 _window: &mut Window,
21686 cx: &mut Context<Self>,
21687 ) {
21688 if event.blurred != self.focus_handle {
21689 self.last_focused_descendant = Some(event.blurred);
21690 }
21691 self.selection_drag_state = SelectionDragState::None;
21692 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21693 }
21694
21695 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21696 self.blink_manager.update(cx, BlinkManager::disable);
21697 self.buffer
21698 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21699
21700 if let Some(blame) = self.blame.as_ref() {
21701 blame.update(cx, GitBlame::blur)
21702 }
21703 if !self.hover_state.focused(window, cx) {
21704 hide_hover(self, cx);
21705 }
21706 if !self
21707 .context_menu
21708 .borrow()
21709 .as_ref()
21710 .is_some_and(|context_menu| context_menu.focused(window, cx))
21711 {
21712 self.hide_context_menu(window, cx);
21713 }
21714 self.take_active_edit_prediction(cx);
21715 cx.emit(EditorEvent::Blurred);
21716 cx.notify();
21717 }
21718
21719 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21720 let mut pending: String = window
21721 .pending_input_keystrokes()
21722 .into_iter()
21723 .flatten()
21724 .filter_map(|keystroke| {
21725 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21726 keystroke.key_char.clone()
21727 } else {
21728 None
21729 }
21730 })
21731 .collect();
21732
21733 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21734 pending = "".to_string();
21735 }
21736
21737 let existing_pending = self
21738 .text_highlights::<PendingInput>(cx)
21739 .map(|(_, ranges)| ranges.to_vec());
21740 if existing_pending.is_none() && pending.is_empty() {
21741 return;
21742 }
21743 let transaction =
21744 self.transact(window, cx, |this, window, cx| {
21745 let selections = this.selections.all::<usize>(cx);
21746 let edits = selections
21747 .iter()
21748 .map(|selection| (selection.end..selection.end, pending.clone()));
21749 this.edit(edits, cx);
21750 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21751 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21752 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21753 }));
21754 });
21755 if let Some(existing_ranges) = existing_pending {
21756 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21757 this.edit(edits, cx);
21758 }
21759 });
21760
21761 let snapshot = self.snapshot(window, cx);
21762 let ranges = self
21763 .selections
21764 .all::<usize>(cx)
21765 .into_iter()
21766 .map(|selection| {
21767 snapshot.buffer_snapshot().anchor_after(selection.end)
21768 ..snapshot
21769 .buffer_snapshot()
21770 .anchor_before(selection.end + pending.len())
21771 })
21772 .collect();
21773
21774 if pending.is_empty() {
21775 self.clear_highlights::<PendingInput>(cx);
21776 } else {
21777 self.highlight_text::<PendingInput>(
21778 ranges,
21779 HighlightStyle {
21780 underline: Some(UnderlineStyle {
21781 thickness: px(1.),
21782 color: None,
21783 wavy: false,
21784 }),
21785 ..Default::default()
21786 },
21787 cx,
21788 );
21789 }
21790
21791 self.ime_transaction = self.ime_transaction.or(transaction);
21792 if let Some(transaction) = self.ime_transaction {
21793 self.buffer.update(cx, |buffer, cx| {
21794 buffer.group_until_transaction(transaction, cx);
21795 });
21796 }
21797
21798 if self.text_highlights::<PendingInput>(cx).is_none() {
21799 self.ime_transaction.take();
21800 }
21801 }
21802
21803 pub fn register_action_renderer(
21804 &mut self,
21805 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21806 ) -> Subscription {
21807 let id = self.next_editor_action_id.post_inc();
21808 self.editor_actions
21809 .borrow_mut()
21810 .insert(id, Box::new(listener));
21811
21812 let editor_actions = self.editor_actions.clone();
21813 Subscription::new(move || {
21814 editor_actions.borrow_mut().remove(&id);
21815 })
21816 }
21817
21818 pub fn register_action<A: Action>(
21819 &mut self,
21820 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21821 ) -> Subscription {
21822 let id = self.next_editor_action_id.post_inc();
21823 let listener = Arc::new(listener);
21824 self.editor_actions.borrow_mut().insert(
21825 id,
21826 Box::new(move |_, window, _| {
21827 let listener = listener.clone();
21828 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21829 let action = action.downcast_ref().unwrap();
21830 if phase == DispatchPhase::Bubble {
21831 listener(action, window, cx)
21832 }
21833 })
21834 }),
21835 );
21836
21837 let editor_actions = self.editor_actions.clone();
21838 Subscription::new(move || {
21839 editor_actions.borrow_mut().remove(&id);
21840 })
21841 }
21842
21843 pub fn file_header_size(&self) -> u32 {
21844 FILE_HEADER_HEIGHT
21845 }
21846
21847 pub fn restore(
21848 &mut self,
21849 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21850 window: &mut Window,
21851 cx: &mut Context<Self>,
21852 ) {
21853 let workspace = self.workspace();
21854 let project = self.project();
21855 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21856 let mut tasks = Vec::new();
21857 for (buffer_id, changes) in revert_changes {
21858 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21859 buffer.update(cx, |buffer, cx| {
21860 buffer.edit(
21861 changes
21862 .into_iter()
21863 .map(|(range, text)| (range, text.to_string())),
21864 None,
21865 cx,
21866 );
21867 });
21868
21869 if let Some(project) =
21870 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21871 {
21872 project.update(cx, |project, cx| {
21873 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21874 })
21875 }
21876 }
21877 }
21878 tasks
21879 });
21880 cx.spawn_in(window, async move |_, cx| {
21881 for (buffer, task) in save_tasks {
21882 let result = task.await;
21883 if result.is_err() {
21884 let Some(path) = buffer
21885 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21886 .ok()
21887 else {
21888 continue;
21889 };
21890 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21891 let Some(task) = cx
21892 .update_window_entity(workspace, |workspace, window, cx| {
21893 workspace
21894 .open_path_preview(path, None, false, false, false, window, cx)
21895 })
21896 .ok()
21897 else {
21898 continue;
21899 };
21900 task.await.log_err();
21901 }
21902 }
21903 }
21904 })
21905 .detach();
21906 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21907 selections.refresh()
21908 });
21909 }
21910
21911 pub fn to_pixel_point(
21912 &self,
21913 source: multi_buffer::Anchor,
21914 editor_snapshot: &EditorSnapshot,
21915 window: &mut Window,
21916 ) -> Option<gpui::Point<Pixels>> {
21917 let source_point = source.to_display_point(editor_snapshot);
21918 self.display_to_pixel_point(source_point, editor_snapshot, window)
21919 }
21920
21921 pub fn display_to_pixel_point(
21922 &self,
21923 source: DisplayPoint,
21924 editor_snapshot: &EditorSnapshot,
21925 window: &mut Window,
21926 ) -> Option<gpui::Point<Pixels>> {
21927 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21928 let text_layout_details = self.text_layout_details(window);
21929 let scroll_top = text_layout_details
21930 .scroll_anchor
21931 .scroll_position(editor_snapshot)
21932 .y;
21933
21934 if source.row().as_f64() < scroll_top.floor() {
21935 return None;
21936 }
21937 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21938 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21939 Some(gpui::Point::new(source_x, source_y))
21940 }
21941
21942 pub fn has_visible_completions_menu(&self) -> bool {
21943 !self.edit_prediction_preview_is_active()
21944 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21945 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21946 })
21947 }
21948
21949 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21950 if self.mode.is_minimap() {
21951 return;
21952 }
21953 self.addons
21954 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21955 }
21956
21957 pub fn unregister_addon<T: Addon>(&mut self) {
21958 self.addons.remove(&std::any::TypeId::of::<T>());
21959 }
21960
21961 pub fn addon<T: Addon>(&self) -> Option<&T> {
21962 let type_id = std::any::TypeId::of::<T>();
21963 self.addons
21964 .get(&type_id)
21965 .and_then(|item| item.to_any().downcast_ref::<T>())
21966 }
21967
21968 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21969 let type_id = std::any::TypeId::of::<T>();
21970 self.addons
21971 .get_mut(&type_id)
21972 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21973 }
21974
21975 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21976 let text_layout_details = self.text_layout_details(window);
21977 let style = &text_layout_details.editor_style;
21978 let font_id = window.text_system().resolve_font(&style.text.font());
21979 let font_size = style.text.font_size.to_pixels(window.rem_size());
21980 let line_height = style.text.line_height_in_pixels(window.rem_size());
21981 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21982 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21983
21984 CharacterDimensions {
21985 em_width,
21986 em_advance,
21987 line_height,
21988 }
21989 }
21990
21991 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21992 self.load_diff_task.clone()
21993 }
21994
21995 fn read_metadata_from_db(
21996 &mut self,
21997 item_id: u64,
21998 workspace_id: WorkspaceId,
21999 window: &mut Window,
22000 cx: &mut Context<Editor>,
22001 ) {
22002 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22003 && !self.mode.is_minimap()
22004 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22005 {
22006 let buffer_snapshot = OnceCell::new();
22007
22008 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22009 && !folds.is_empty()
22010 {
22011 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22012 self.fold_ranges(
22013 folds
22014 .into_iter()
22015 .map(|(start, end)| {
22016 snapshot.clip_offset(start, Bias::Left)
22017 ..snapshot.clip_offset(end, Bias::Right)
22018 })
22019 .collect(),
22020 false,
22021 window,
22022 cx,
22023 );
22024 }
22025
22026 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22027 && !selections.is_empty()
22028 {
22029 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22030 // skip adding the initial selection to selection history
22031 self.selection_history.mode = SelectionHistoryMode::Skipping;
22032 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22033 s.select_ranges(selections.into_iter().map(|(start, end)| {
22034 snapshot.clip_offset(start, Bias::Left)
22035 ..snapshot.clip_offset(end, Bias::Right)
22036 }));
22037 });
22038 self.selection_history.mode = SelectionHistoryMode::Normal;
22039 };
22040 }
22041
22042 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22043 }
22044
22045 fn update_lsp_data(
22046 &mut self,
22047 ignore_cache: bool,
22048 for_buffer: Option<BufferId>,
22049 window: &mut Window,
22050 cx: &mut Context<'_, Self>,
22051 ) {
22052 self.pull_diagnostics(for_buffer, window, cx);
22053 self.refresh_colors(ignore_cache, for_buffer, window, cx);
22054 }
22055}
22056
22057fn edit_for_markdown_paste<'a>(
22058 buffer: &MultiBufferSnapshot,
22059 range: Range<usize>,
22060 to_insert: &'a str,
22061 url: Option<url::Url>,
22062) -> (Range<usize>, Cow<'a, str>) {
22063 if url.is_none() {
22064 return (range, Cow::Borrowed(to_insert));
22065 };
22066
22067 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22068
22069 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22070 Cow::Borrowed(to_insert)
22071 } else {
22072 Cow::Owned(format!("[{old_text}]({to_insert})"))
22073 };
22074 (range, new_text)
22075}
22076
22077fn vim_enabled(cx: &App) -> bool {
22078 vim_mode_setting::VimModeSetting::try_get(cx)
22079 .map(|vim_mode| vim_mode.0)
22080 .unwrap_or(false)
22081}
22082
22083fn process_completion_for_edit(
22084 completion: &Completion,
22085 intent: CompletionIntent,
22086 buffer: &Entity<Buffer>,
22087 cursor_position: &text::Anchor,
22088 cx: &mut Context<Editor>,
22089) -> CompletionEdit {
22090 let buffer = buffer.read(cx);
22091 let buffer_snapshot = buffer.snapshot();
22092 let (snippet, new_text) = if completion.is_snippet() {
22093 let mut snippet_source = completion.new_text.clone();
22094 // Workaround for typescript language server issues so that methods don't expand within
22095 // strings and functions with type expressions. The previous point is used because the query
22096 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22097 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22098 let previous_point = if previous_point.column > 0 {
22099 cursor_position.to_previous_offset(&buffer_snapshot)
22100 } else {
22101 cursor_position.to_offset(&buffer_snapshot)
22102 };
22103 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22104 && scope.prefers_label_for_snippet_in_completion()
22105 && let Some(label) = completion.label()
22106 && matches!(
22107 completion.kind(),
22108 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22109 )
22110 {
22111 snippet_source = label;
22112 }
22113 match Snippet::parse(&snippet_source).log_err() {
22114 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22115 None => (None, completion.new_text.clone()),
22116 }
22117 } else {
22118 (None, completion.new_text.clone())
22119 };
22120
22121 let mut range_to_replace = {
22122 let replace_range = &completion.replace_range;
22123 if let CompletionSource::Lsp {
22124 insert_range: Some(insert_range),
22125 ..
22126 } = &completion.source
22127 {
22128 debug_assert_eq!(
22129 insert_range.start, replace_range.start,
22130 "insert_range and replace_range should start at the same position"
22131 );
22132 debug_assert!(
22133 insert_range
22134 .start
22135 .cmp(cursor_position, &buffer_snapshot)
22136 .is_le(),
22137 "insert_range should start before or at cursor position"
22138 );
22139 debug_assert!(
22140 replace_range
22141 .start
22142 .cmp(cursor_position, &buffer_snapshot)
22143 .is_le(),
22144 "replace_range should start before or at cursor position"
22145 );
22146
22147 let should_replace = match intent {
22148 CompletionIntent::CompleteWithInsert => false,
22149 CompletionIntent::CompleteWithReplace => true,
22150 CompletionIntent::Complete | CompletionIntent::Compose => {
22151 let insert_mode =
22152 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22153 .completions
22154 .lsp_insert_mode;
22155 match insert_mode {
22156 LspInsertMode::Insert => false,
22157 LspInsertMode::Replace => true,
22158 LspInsertMode::ReplaceSubsequence => {
22159 let mut text_to_replace = buffer.chars_for_range(
22160 buffer.anchor_before(replace_range.start)
22161 ..buffer.anchor_after(replace_range.end),
22162 );
22163 let mut current_needle = text_to_replace.next();
22164 for haystack_ch in completion.label.text.chars() {
22165 if let Some(needle_ch) = current_needle
22166 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22167 {
22168 current_needle = text_to_replace.next();
22169 }
22170 }
22171 current_needle.is_none()
22172 }
22173 LspInsertMode::ReplaceSuffix => {
22174 if replace_range
22175 .end
22176 .cmp(cursor_position, &buffer_snapshot)
22177 .is_gt()
22178 {
22179 let range_after_cursor = *cursor_position..replace_range.end;
22180 let text_after_cursor = buffer
22181 .text_for_range(
22182 buffer.anchor_before(range_after_cursor.start)
22183 ..buffer.anchor_after(range_after_cursor.end),
22184 )
22185 .collect::<String>()
22186 .to_ascii_lowercase();
22187 completion
22188 .label
22189 .text
22190 .to_ascii_lowercase()
22191 .ends_with(&text_after_cursor)
22192 } else {
22193 true
22194 }
22195 }
22196 }
22197 }
22198 };
22199
22200 if should_replace {
22201 replace_range.clone()
22202 } else {
22203 insert_range.clone()
22204 }
22205 } else {
22206 replace_range.clone()
22207 }
22208 };
22209
22210 if range_to_replace
22211 .end
22212 .cmp(cursor_position, &buffer_snapshot)
22213 .is_lt()
22214 {
22215 range_to_replace.end = *cursor_position;
22216 }
22217
22218 CompletionEdit {
22219 new_text,
22220 replace_range: range_to_replace.to_offset(buffer),
22221 snippet,
22222 }
22223}
22224
22225struct CompletionEdit {
22226 new_text: String,
22227 replace_range: Range<usize>,
22228 snippet: Option<Snippet>,
22229}
22230
22231fn insert_extra_newline_brackets(
22232 buffer: &MultiBufferSnapshot,
22233 range: Range<usize>,
22234 language: &language::LanguageScope,
22235) -> bool {
22236 let leading_whitespace_len = buffer
22237 .reversed_chars_at(range.start)
22238 .take_while(|c| c.is_whitespace() && *c != '\n')
22239 .map(|c| c.len_utf8())
22240 .sum::<usize>();
22241 let trailing_whitespace_len = buffer
22242 .chars_at(range.end)
22243 .take_while(|c| c.is_whitespace() && *c != '\n')
22244 .map(|c| c.len_utf8())
22245 .sum::<usize>();
22246 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22247
22248 language.brackets().any(|(pair, enabled)| {
22249 let pair_start = pair.start.trim_end();
22250 let pair_end = pair.end.trim_start();
22251
22252 enabled
22253 && pair.newline
22254 && buffer.contains_str_at(range.end, pair_end)
22255 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22256 })
22257}
22258
22259fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22260 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22261 [(buffer, range, _)] => (*buffer, range.clone()),
22262 _ => return false,
22263 };
22264 let pair = {
22265 let mut result: Option<BracketMatch> = None;
22266
22267 for pair in buffer
22268 .all_bracket_ranges(range.clone())
22269 .filter(move |pair| {
22270 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22271 })
22272 {
22273 let len = pair.close_range.end - pair.open_range.start;
22274
22275 if let Some(existing) = &result {
22276 let existing_len = existing.close_range.end - existing.open_range.start;
22277 if len > existing_len {
22278 continue;
22279 }
22280 }
22281
22282 result = Some(pair);
22283 }
22284
22285 result
22286 };
22287 let Some(pair) = pair else {
22288 return false;
22289 };
22290 pair.newline_only
22291 && buffer
22292 .chars_for_range(pair.open_range.end..range.start)
22293 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22294 .all(|c| c.is_whitespace() && c != '\n')
22295}
22296
22297fn update_uncommitted_diff_for_buffer(
22298 editor: Entity<Editor>,
22299 project: &Entity<Project>,
22300 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22301 buffer: Entity<MultiBuffer>,
22302 cx: &mut App,
22303) -> Task<()> {
22304 let mut tasks = Vec::new();
22305 project.update(cx, |project, cx| {
22306 for buffer in buffers {
22307 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22308 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22309 }
22310 }
22311 });
22312 cx.spawn(async move |cx| {
22313 let diffs = future::join_all(tasks).await;
22314 if editor
22315 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22316 .unwrap_or(false)
22317 {
22318 return;
22319 }
22320
22321 buffer
22322 .update(cx, |buffer, cx| {
22323 for diff in diffs.into_iter().flatten() {
22324 buffer.add_diff(diff, cx);
22325 }
22326 })
22327 .ok();
22328 })
22329}
22330
22331fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22332 let tab_size = tab_size.get() as usize;
22333 let mut width = offset;
22334
22335 for ch in text.chars() {
22336 width += if ch == '\t' {
22337 tab_size - (width % tab_size)
22338 } else {
22339 1
22340 };
22341 }
22342
22343 width - offset
22344}
22345
22346#[cfg(test)]
22347mod tests {
22348 use super::*;
22349
22350 #[test]
22351 fn test_string_size_with_expanded_tabs() {
22352 let nz = |val| NonZeroU32::new(val).unwrap();
22353 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22354 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22355 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22356 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22357 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22358 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22359 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22360 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22361 }
22362}
22363
22364/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22365struct WordBreakingTokenizer<'a> {
22366 input: &'a str,
22367}
22368
22369impl<'a> WordBreakingTokenizer<'a> {
22370 fn new(input: &'a str) -> Self {
22371 Self { input }
22372 }
22373}
22374
22375fn is_char_ideographic(ch: char) -> bool {
22376 use unicode_script::Script::*;
22377 use unicode_script::UnicodeScript;
22378 matches!(ch.script(), Han | Tangut | Yi)
22379}
22380
22381fn is_grapheme_ideographic(text: &str) -> bool {
22382 text.chars().any(is_char_ideographic)
22383}
22384
22385fn is_grapheme_whitespace(text: &str) -> bool {
22386 text.chars().any(|x| x.is_whitespace())
22387}
22388
22389fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22390 text.chars()
22391 .next()
22392 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22393}
22394
22395#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22396enum WordBreakToken<'a> {
22397 Word { token: &'a str, grapheme_len: usize },
22398 InlineWhitespace { token: &'a str, grapheme_len: usize },
22399 Newline,
22400}
22401
22402impl<'a> Iterator for WordBreakingTokenizer<'a> {
22403 /// Yields a span, the count of graphemes in the token, and whether it was
22404 /// whitespace. Note that it also breaks at word boundaries.
22405 type Item = WordBreakToken<'a>;
22406
22407 fn next(&mut self) -> Option<Self::Item> {
22408 use unicode_segmentation::UnicodeSegmentation;
22409 if self.input.is_empty() {
22410 return None;
22411 }
22412
22413 let mut iter = self.input.graphemes(true).peekable();
22414 let mut offset = 0;
22415 let mut grapheme_len = 0;
22416 if let Some(first_grapheme) = iter.next() {
22417 let is_newline = first_grapheme == "\n";
22418 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22419 offset += first_grapheme.len();
22420 grapheme_len += 1;
22421 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22422 if let Some(grapheme) = iter.peek().copied()
22423 && should_stay_with_preceding_ideograph(grapheme)
22424 {
22425 offset += grapheme.len();
22426 grapheme_len += 1;
22427 }
22428 } else {
22429 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22430 let mut next_word_bound = words.peek().copied();
22431 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22432 next_word_bound = words.next();
22433 }
22434 while let Some(grapheme) = iter.peek().copied() {
22435 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22436 break;
22437 };
22438 if is_grapheme_whitespace(grapheme) != is_whitespace
22439 || (grapheme == "\n") != is_newline
22440 {
22441 break;
22442 };
22443 offset += grapheme.len();
22444 grapheme_len += 1;
22445 iter.next();
22446 }
22447 }
22448 let token = &self.input[..offset];
22449 self.input = &self.input[offset..];
22450 if token == "\n" {
22451 Some(WordBreakToken::Newline)
22452 } else if is_whitespace {
22453 Some(WordBreakToken::InlineWhitespace {
22454 token,
22455 grapheme_len,
22456 })
22457 } else {
22458 Some(WordBreakToken::Word {
22459 token,
22460 grapheme_len,
22461 })
22462 }
22463 } else {
22464 None
22465 }
22466 }
22467}
22468
22469#[test]
22470fn test_word_breaking_tokenizer() {
22471 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22472 ("", &[]),
22473 (" ", &[whitespace(" ", 2)]),
22474 ("Ʒ", &[word("Ʒ", 1)]),
22475 ("Ǽ", &[word("Ǽ", 1)]),
22476 ("⋑", &[word("⋑", 1)]),
22477 ("⋑⋑", &[word("⋑⋑", 2)]),
22478 (
22479 "原理,进而",
22480 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22481 ),
22482 (
22483 "hello world",
22484 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22485 ),
22486 (
22487 "hello, world",
22488 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22489 ),
22490 (
22491 " hello world",
22492 &[
22493 whitespace(" ", 2),
22494 word("hello", 5),
22495 whitespace(" ", 1),
22496 word("world", 5),
22497 ],
22498 ),
22499 (
22500 "这是什么 \n 钢笔",
22501 &[
22502 word("这", 1),
22503 word("是", 1),
22504 word("什", 1),
22505 word("么", 1),
22506 whitespace(" ", 1),
22507 newline(),
22508 whitespace(" ", 1),
22509 word("钢", 1),
22510 word("笔", 1),
22511 ],
22512 ),
22513 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22514 ];
22515
22516 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22517 WordBreakToken::Word {
22518 token,
22519 grapheme_len,
22520 }
22521 }
22522
22523 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22524 WordBreakToken::InlineWhitespace {
22525 token,
22526 grapheme_len,
22527 }
22528 }
22529
22530 fn newline() -> WordBreakToken<'static> {
22531 WordBreakToken::Newline
22532 }
22533
22534 for (input, result) in tests {
22535 assert_eq!(
22536 WordBreakingTokenizer::new(input)
22537 .collect::<Vec<_>>()
22538 .as_slice(),
22539 *result,
22540 );
22541 }
22542}
22543
22544fn wrap_with_prefix(
22545 first_line_prefix: String,
22546 subsequent_lines_prefix: String,
22547 unwrapped_text: String,
22548 wrap_column: usize,
22549 tab_size: NonZeroU32,
22550 preserve_existing_whitespace: bool,
22551) -> String {
22552 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22553 let subsequent_lines_prefix_len =
22554 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22555 let mut wrapped_text = String::new();
22556 let mut current_line = first_line_prefix;
22557 let mut is_first_line = true;
22558
22559 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22560 let mut current_line_len = first_line_prefix_len;
22561 let mut in_whitespace = false;
22562 for token in tokenizer {
22563 let have_preceding_whitespace = in_whitespace;
22564 match token {
22565 WordBreakToken::Word {
22566 token,
22567 grapheme_len,
22568 } => {
22569 in_whitespace = false;
22570 let current_prefix_len = if is_first_line {
22571 first_line_prefix_len
22572 } else {
22573 subsequent_lines_prefix_len
22574 };
22575 if current_line_len + grapheme_len > wrap_column
22576 && current_line_len != current_prefix_len
22577 {
22578 wrapped_text.push_str(current_line.trim_end());
22579 wrapped_text.push('\n');
22580 is_first_line = false;
22581 current_line = subsequent_lines_prefix.clone();
22582 current_line_len = subsequent_lines_prefix_len;
22583 }
22584 current_line.push_str(token);
22585 current_line_len += grapheme_len;
22586 }
22587 WordBreakToken::InlineWhitespace {
22588 mut token,
22589 mut grapheme_len,
22590 } => {
22591 in_whitespace = true;
22592 if have_preceding_whitespace && !preserve_existing_whitespace {
22593 continue;
22594 }
22595 if !preserve_existing_whitespace {
22596 // Keep a single whitespace grapheme as-is
22597 if let Some(first) =
22598 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22599 {
22600 token = first;
22601 } else {
22602 token = " ";
22603 }
22604 grapheme_len = 1;
22605 }
22606 let current_prefix_len = if is_first_line {
22607 first_line_prefix_len
22608 } else {
22609 subsequent_lines_prefix_len
22610 };
22611 if current_line_len + grapheme_len > wrap_column {
22612 wrapped_text.push_str(current_line.trim_end());
22613 wrapped_text.push('\n');
22614 is_first_line = false;
22615 current_line = subsequent_lines_prefix.clone();
22616 current_line_len = subsequent_lines_prefix_len;
22617 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22618 current_line.push_str(token);
22619 current_line_len += grapheme_len;
22620 }
22621 }
22622 WordBreakToken::Newline => {
22623 in_whitespace = true;
22624 let current_prefix_len = if is_first_line {
22625 first_line_prefix_len
22626 } else {
22627 subsequent_lines_prefix_len
22628 };
22629 if preserve_existing_whitespace {
22630 wrapped_text.push_str(current_line.trim_end());
22631 wrapped_text.push('\n');
22632 is_first_line = false;
22633 current_line = subsequent_lines_prefix.clone();
22634 current_line_len = subsequent_lines_prefix_len;
22635 } else if have_preceding_whitespace {
22636 continue;
22637 } else if current_line_len + 1 > wrap_column
22638 && current_line_len != current_prefix_len
22639 {
22640 wrapped_text.push_str(current_line.trim_end());
22641 wrapped_text.push('\n');
22642 is_first_line = false;
22643 current_line = subsequent_lines_prefix.clone();
22644 current_line_len = subsequent_lines_prefix_len;
22645 } else if current_line_len != current_prefix_len {
22646 current_line.push(' ');
22647 current_line_len += 1;
22648 }
22649 }
22650 }
22651 }
22652
22653 if !current_line.is_empty() {
22654 wrapped_text.push_str(¤t_line);
22655 }
22656 wrapped_text
22657}
22658
22659#[test]
22660fn test_wrap_with_prefix() {
22661 assert_eq!(
22662 wrap_with_prefix(
22663 "# ".to_string(),
22664 "# ".to_string(),
22665 "abcdefg".to_string(),
22666 4,
22667 NonZeroU32::new(4).unwrap(),
22668 false,
22669 ),
22670 "# abcdefg"
22671 );
22672 assert_eq!(
22673 wrap_with_prefix(
22674 "".to_string(),
22675 "".to_string(),
22676 "\thello world".to_string(),
22677 8,
22678 NonZeroU32::new(4).unwrap(),
22679 false,
22680 ),
22681 "hello\nworld"
22682 );
22683 assert_eq!(
22684 wrap_with_prefix(
22685 "// ".to_string(),
22686 "// ".to_string(),
22687 "xx \nyy zz aa bb cc".to_string(),
22688 12,
22689 NonZeroU32::new(4).unwrap(),
22690 false,
22691 ),
22692 "// xx yy zz\n// aa bb cc"
22693 );
22694 assert_eq!(
22695 wrap_with_prefix(
22696 String::new(),
22697 String::new(),
22698 "这是什么 \n 钢笔".to_string(),
22699 3,
22700 NonZeroU32::new(4).unwrap(),
22701 false,
22702 ),
22703 "这是什\n么 钢\n笔"
22704 );
22705 assert_eq!(
22706 wrap_with_prefix(
22707 String::new(),
22708 String::new(),
22709 format!("foo{}bar", '\u{2009}'), // thin space
22710 80,
22711 NonZeroU32::new(4).unwrap(),
22712 false,
22713 ),
22714 format!("foo{}bar", '\u{2009}')
22715 );
22716}
22717
22718pub trait CollaborationHub {
22719 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22720 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22721 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22722}
22723
22724impl CollaborationHub for Entity<Project> {
22725 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22726 self.read(cx).collaborators()
22727 }
22728
22729 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22730 self.read(cx).user_store().read(cx).participant_indices()
22731 }
22732
22733 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22734 let this = self.read(cx);
22735 let user_ids = this.collaborators().values().map(|c| c.user_id);
22736 this.user_store().read(cx).participant_names(user_ids, cx)
22737 }
22738}
22739
22740pub trait SemanticsProvider {
22741 fn hover(
22742 &self,
22743 buffer: &Entity<Buffer>,
22744 position: text::Anchor,
22745 cx: &mut App,
22746 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22747
22748 fn inline_values(
22749 &self,
22750 buffer_handle: Entity<Buffer>,
22751 range: Range<text::Anchor>,
22752 cx: &mut App,
22753 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22754
22755 fn inlay_hints(
22756 &self,
22757 buffer_handle: Entity<Buffer>,
22758 range: Range<text::Anchor>,
22759 cx: &mut App,
22760 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22761
22762 fn resolve_inlay_hint(
22763 &self,
22764 hint: InlayHint,
22765 buffer_handle: Entity<Buffer>,
22766 server_id: LanguageServerId,
22767 cx: &mut App,
22768 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22769
22770 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22771
22772 fn document_highlights(
22773 &self,
22774 buffer: &Entity<Buffer>,
22775 position: text::Anchor,
22776 cx: &mut App,
22777 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22778
22779 fn definitions(
22780 &self,
22781 buffer: &Entity<Buffer>,
22782 position: text::Anchor,
22783 kind: GotoDefinitionKind,
22784 cx: &mut App,
22785 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22786
22787 fn range_for_rename(
22788 &self,
22789 buffer: &Entity<Buffer>,
22790 position: text::Anchor,
22791 cx: &mut App,
22792 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22793
22794 fn perform_rename(
22795 &self,
22796 buffer: &Entity<Buffer>,
22797 position: text::Anchor,
22798 new_name: String,
22799 cx: &mut App,
22800 ) -> Option<Task<Result<ProjectTransaction>>>;
22801}
22802
22803pub trait CompletionProvider {
22804 fn completions(
22805 &self,
22806 excerpt_id: ExcerptId,
22807 buffer: &Entity<Buffer>,
22808 buffer_position: text::Anchor,
22809 trigger: CompletionContext,
22810 window: &mut Window,
22811 cx: &mut Context<Editor>,
22812 ) -> Task<Result<Vec<CompletionResponse>>>;
22813
22814 fn resolve_completions(
22815 &self,
22816 _buffer: Entity<Buffer>,
22817 _completion_indices: Vec<usize>,
22818 _completions: Rc<RefCell<Box<[Completion]>>>,
22819 _cx: &mut Context<Editor>,
22820 ) -> Task<Result<bool>> {
22821 Task::ready(Ok(false))
22822 }
22823
22824 fn apply_additional_edits_for_completion(
22825 &self,
22826 _buffer: Entity<Buffer>,
22827 _completions: Rc<RefCell<Box<[Completion]>>>,
22828 _completion_index: usize,
22829 _push_to_history: bool,
22830 _cx: &mut Context<Editor>,
22831 ) -> Task<Result<Option<language::Transaction>>> {
22832 Task::ready(Ok(None))
22833 }
22834
22835 fn is_completion_trigger(
22836 &self,
22837 buffer: &Entity<Buffer>,
22838 position: language::Anchor,
22839 text: &str,
22840 trigger_in_words: bool,
22841 menu_is_open: bool,
22842 cx: &mut Context<Editor>,
22843 ) -> bool;
22844
22845 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22846
22847 fn sort_completions(&self) -> bool {
22848 true
22849 }
22850
22851 fn filter_completions(&self) -> bool {
22852 true
22853 }
22854}
22855
22856pub trait CodeActionProvider {
22857 fn id(&self) -> Arc<str>;
22858
22859 fn code_actions(
22860 &self,
22861 buffer: &Entity<Buffer>,
22862 range: Range<text::Anchor>,
22863 window: &mut Window,
22864 cx: &mut App,
22865 ) -> Task<Result<Vec<CodeAction>>>;
22866
22867 fn apply_code_action(
22868 &self,
22869 buffer_handle: Entity<Buffer>,
22870 action: CodeAction,
22871 excerpt_id: ExcerptId,
22872 push_to_history: bool,
22873 window: &mut Window,
22874 cx: &mut App,
22875 ) -> Task<Result<ProjectTransaction>>;
22876}
22877
22878impl CodeActionProvider for Entity<Project> {
22879 fn id(&self) -> Arc<str> {
22880 "project".into()
22881 }
22882
22883 fn code_actions(
22884 &self,
22885 buffer: &Entity<Buffer>,
22886 range: Range<text::Anchor>,
22887 _window: &mut Window,
22888 cx: &mut App,
22889 ) -> Task<Result<Vec<CodeAction>>> {
22890 self.update(cx, |project, cx| {
22891 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22892 let code_actions = project.code_actions(buffer, range, None, cx);
22893 cx.background_spawn(async move {
22894 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22895 Ok(code_lens_actions
22896 .context("code lens fetch")?
22897 .into_iter()
22898 .flatten()
22899 .chain(
22900 code_actions
22901 .context("code action fetch")?
22902 .into_iter()
22903 .flatten(),
22904 )
22905 .collect())
22906 })
22907 })
22908 }
22909
22910 fn apply_code_action(
22911 &self,
22912 buffer_handle: Entity<Buffer>,
22913 action: CodeAction,
22914 _excerpt_id: ExcerptId,
22915 push_to_history: bool,
22916 _window: &mut Window,
22917 cx: &mut App,
22918 ) -> Task<Result<ProjectTransaction>> {
22919 self.update(cx, |project, cx| {
22920 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22921 })
22922 }
22923}
22924
22925fn snippet_completions(
22926 project: &Project,
22927 buffer: &Entity<Buffer>,
22928 buffer_position: text::Anchor,
22929 cx: &mut App,
22930) -> Task<Result<CompletionResponse>> {
22931 let languages = buffer.read(cx).languages_at(buffer_position);
22932 let snippet_store = project.snippets().read(cx);
22933
22934 let scopes: Vec<_> = languages
22935 .iter()
22936 .filter_map(|language| {
22937 let language_name = language.lsp_id();
22938 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22939
22940 if snippets.is_empty() {
22941 None
22942 } else {
22943 Some((language.default_scope(), snippets))
22944 }
22945 })
22946 .collect();
22947
22948 if scopes.is_empty() {
22949 return Task::ready(Ok(CompletionResponse {
22950 completions: vec![],
22951 display_options: CompletionDisplayOptions::default(),
22952 is_incomplete: false,
22953 }));
22954 }
22955
22956 let snapshot = buffer.read(cx).text_snapshot();
22957 let executor = cx.background_executor().clone();
22958
22959 cx.background_spawn(async move {
22960 let mut is_incomplete = false;
22961 let mut completions: Vec<Completion> = Vec::new();
22962 for (scope, snippets) in scopes.into_iter() {
22963 let classifier =
22964 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22965
22966 const MAX_WORD_PREFIX_LEN: usize = 128;
22967 let last_word: String = snapshot
22968 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22969 .take(MAX_WORD_PREFIX_LEN)
22970 .take_while(|c| classifier.is_word(*c))
22971 .collect::<String>()
22972 .chars()
22973 .rev()
22974 .collect();
22975
22976 if last_word.is_empty() {
22977 return Ok(CompletionResponse {
22978 completions: vec![],
22979 display_options: CompletionDisplayOptions::default(),
22980 is_incomplete: true,
22981 });
22982 }
22983
22984 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22985 let to_lsp = |point: &text::Anchor| {
22986 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22987 point_to_lsp(end)
22988 };
22989 let lsp_end = to_lsp(&buffer_position);
22990
22991 let candidates = snippets
22992 .iter()
22993 .enumerate()
22994 .flat_map(|(ix, snippet)| {
22995 snippet
22996 .prefix
22997 .iter()
22998 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22999 })
23000 .collect::<Vec<StringMatchCandidate>>();
23001
23002 const MAX_RESULTS: usize = 100;
23003 let mut matches = fuzzy::match_strings(
23004 &candidates,
23005 &last_word,
23006 last_word.chars().any(|c| c.is_uppercase()),
23007 true,
23008 MAX_RESULTS,
23009 &Default::default(),
23010 executor.clone(),
23011 )
23012 .await;
23013
23014 if matches.len() >= MAX_RESULTS {
23015 is_incomplete = true;
23016 }
23017
23018 // Remove all candidates where the query's start does not match the start of any word in the candidate
23019 if let Some(query_start) = last_word.chars().next() {
23020 matches.retain(|string_match| {
23021 split_words(&string_match.string).any(|word| {
23022 // Check that the first codepoint of the word as lowercase matches the first
23023 // codepoint of the query as lowercase
23024 word.chars()
23025 .flat_map(|codepoint| codepoint.to_lowercase())
23026 .zip(query_start.to_lowercase())
23027 .all(|(word_cp, query_cp)| word_cp == query_cp)
23028 })
23029 });
23030 }
23031
23032 let matched_strings = matches
23033 .into_iter()
23034 .map(|m| m.string)
23035 .collect::<HashSet<_>>();
23036
23037 completions.extend(snippets.iter().filter_map(|snippet| {
23038 let matching_prefix = snippet
23039 .prefix
23040 .iter()
23041 .find(|prefix| matched_strings.contains(*prefix))?;
23042 let start = as_offset - last_word.len();
23043 let start = snapshot.anchor_before(start);
23044 let range = start..buffer_position;
23045 let lsp_start = to_lsp(&start);
23046 let lsp_range = lsp::Range {
23047 start: lsp_start,
23048 end: lsp_end,
23049 };
23050 Some(Completion {
23051 replace_range: range,
23052 new_text: snippet.body.clone(),
23053 source: CompletionSource::Lsp {
23054 insert_range: None,
23055 server_id: LanguageServerId(usize::MAX),
23056 resolved: true,
23057 lsp_completion: Box::new(lsp::CompletionItem {
23058 label: snippet.prefix.first().unwrap().clone(),
23059 kind: Some(CompletionItemKind::SNIPPET),
23060 label_details: snippet.description.as_ref().map(|description| {
23061 lsp::CompletionItemLabelDetails {
23062 detail: Some(description.clone()),
23063 description: None,
23064 }
23065 }),
23066 insert_text_format: Some(InsertTextFormat::SNIPPET),
23067 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23068 lsp::InsertReplaceEdit {
23069 new_text: snippet.body.clone(),
23070 insert: lsp_range,
23071 replace: lsp_range,
23072 },
23073 )),
23074 filter_text: Some(snippet.body.clone()),
23075 sort_text: Some(char::MAX.to_string()),
23076 ..lsp::CompletionItem::default()
23077 }),
23078 lsp_defaults: None,
23079 },
23080 label: CodeLabel {
23081 text: matching_prefix.clone(),
23082 runs: Vec::new(),
23083 filter_range: 0..matching_prefix.len(),
23084 },
23085 icon_path: None,
23086 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23087 single_line: snippet.name.clone().into(),
23088 plain_text: snippet
23089 .description
23090 .clone()
23091 .map(|description| description.into()),
23092 }),
23093 insert_text_mode: None,
23094 confirm: None,
23095 })
23096 }))
23097 }
23098
23099 Ok(CompletionResponse {
23100 completions,
23101 display_options: CompletionDisplayOptions::default(),
23102 is_incomplete,
23103 })
23104 })
23105}
23106
23107impl CompletionProvider for Entity<Project> {
23108 fn completions(
23109 &self,
23110 _excerpt_id: ExcerptId,
23111 buffer: &Entity<Buffer>,
23112 buffer_position: text::Anchor,
23113 options: CompletionContext,
23114 _window: &mut Window,
23115 cx: &mut Context<Editor>,
23116 ) -> Task<Result<Vec<CompletionResponse>>> {
23117 self.update(cx, |project, cx| {
23118 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23119 let project_completions = project.completions(buffer, buffer_position, options, cx);
23120 cx.background_spawn(async move {
23121 let mut responses = project_completions.await?;
23122 let snippets = snippets.await?;
23123 if !snippets.completions.is_empty() {
23124 responses.push(snippets);
23125 }
23126 Ok(responses)
23127 })
23128 })
23129 }
23130
23131 fn resolve_completions(
23132 &self,
23133 buffer: Entity<Buffer>,
23134 completion_indices: Vec<usize>,
23135 completions: Rc<RefCell<Box<[Completion]>>>,
23136 cx: &mut Context<Editor>,
23137 ) -> Task<Result<bool>> {
23138 self.update(cx, |project, cx| {
23139 project.lsp_store().update(cx, |lsp_store, cx| {
23140 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23141 })
23142 })
23143 }
23144
23145 fn apply_additional_edits_for_completion(
23146 &self,
23147 buffer: Entity<Buffer>,
23148 completions: Rc<RefCell<Box<[Completion]>>>,
23149 completion_index: usize,
23150 push_to_history: bool,
23151 cx: &mut Context<Editor>,
23152 ) -> Task<Result<Option<language::Transaction>>> {
23153 self.update(cx, |project, cx| {
23154 project.lsp_store().update(cx, |lsp_store, cx| {
23155 lsp_store.apply_additional_edits_for_completion(
23156 buffer,
23157 completions,
23158 completion_index,
23159 push_to_history,
23160 cx,
23161 )
23162 })
23163 })
23164 }
23165
23166 fn is_completion_trigger(
23167 &self,
23168 buffer: &Entity<Buffer>,
23169 position: language::Anchor,
23170 text: &str,
23171 trigger_in_words: bool,
23172 menu_is_open: bool,
23173 cx: &mut Context<Editor>,
23174 ) -> bool {
23175 let mut chars = text.chars();
23176 let char = if let Some(char) = chars.next() {
23177 char
23178 } else {
23179 return false;
23180 };
23181 if chars.next().is_some() {
23182 return false;
23183 }
23184
23185 let buffer = buffer.read(cx);
23186 let snapshot = buffer.snapshot();
23187 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23188 return false;
23189 }
23190 let classifier = snapshot
23191 .char_classifier_at(position)
23192 .scope_context(Some(CharScopeContext::Completion));
23193 if trigger_in_words && classifier.is_word(char) {
23194 return true;
23195 }
23196
23197 buffer.completion_triggers().contains(text)
23198 }
23199}
23200
23201impl SemanticsProvider for Entity<Project> {
23202 fn hover(
23203 &self,
23204 buffer: &Entity<Buffer>,
23205 position: text::Anchor,
23206 cx: &mut App,
23207 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23208 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23209 }
23210
23211 fn document_highlights(
23212 &self,
23213 buffer: &Entity<Buffer>,
23214 position: text::Anchor,
23215 cx: &mut App,
23216 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23217 Some(self.update(cx, |project, cx| {
23218 project.document_highlights(buffer, position, cx)
23219 }))
23220 }
23221
23222 fn definitions(
23223 &self,
23224 buffer: &Entity<Buffer>,
23225 position: text::Anchor,
23226 kind: GotoDefinitionKind,
23227 cx: &mut App,
23228 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23229 Some(self.update(cx, |project, cx| match kind {
23230 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23231 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23232 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23233 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23234 }))
23235 }
23236
23237 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23238 self.update(cx, |project, cx| {
23239 if project
23240 .active_debug_session(cx)
23241 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23242 {
23243 return true;
23244 }
23245
23246 buffer.update(cx, |buffer, cx| {
23247 project.any_language_server_supports_inlay_hints(buffer, cx)
23248 })
23249 })
23250 }
23251
23252 fn inline_values(
23253 &self,
23254 buffer_handle: Entity<Buffer>,
23255 range: Range<text::Anchor>,
23256 cx: &mut App,
23257 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23258 self.update(cx, |project, cx| {
23259 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23260
23261 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23262 })
23263 }
23264
23265 fn inlay_hints(
23266 &self,
23267 buffer_handle: Entity<Buffer>,
23268 range: Range<text::Anchor>,
23269 cx: &mut App,
23270 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23271 Some(self.update(cx, |project, cx| {
23272 project.inlay_hints(buffer_handle, range, cx)
23273 }))
23274 }
23275
23276 fn resolve_inlay_hint(
23277 &self,
23278 hint: InlayHint,
23279 buffer_handle: Entity<Buffer>,
23280 server_id: LanguageServerId,
23281 cx: &mut App,
23282 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23283 Some(self.update(cx, |project, cx| {
23284 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23285 }))
23286 }
23287
23288 fn range_for_rename(
23289 &self,
23290 buffer: &Entity<Buffer>,
23291 position: text::Anchor,
23292 cx: &mut App,
23293 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23294 Some(self.update(cx, |project, cx| {
23295 let buffer = buffer.clone();
23296 let task = project.prepare_rename(buffer.clone(), position, cx);
23297 cx.spawn(async move |_, cx| {
23298 Ok(match task.await? {
23299 PrepareRenameResponse::Success(range) => Some(range),
23300 PrepareRenameResponse::InvalidPosition => None,
23301 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23302 // Fallback on using TreeSitter info to determine identifier range
23303 buffer.read_with(cx, |buffer, _| {
23304 let snapshot = buffer.snapshot();
23305 let (range, kind) = snapshot.surrounding_word(position, None);
23306 if kind != Some(CharKind::Word) {
23307 return None;
23308 }
23309 Some(
23310 snapshot.anchor_before(range.start)
23311 ..snapshot.anchor_after(range.end),
23312 )
23313 })?
23314 }
23315 })
23316 })
23317 }))
23318 }
23319
23320 fn perform_rename(
23321 &self,
23322 buffer: &Entity<Buffer>,
23323 position: text::Anchor,
23324 new_name: String,
23325 cx: &mut App,
23326 ) -> Option<Task<Result<ProjectTransaction>>> {
23327 Some(self.update(cx, |project, cx| {
23328 project.perform_rename(buffer.clone(), position, new_name, cx)
23329 }))
23330 }
23331}
23332
23333fn inlay_hint_settings(
23334 location: Anchor,
23335 snapshot: &MultiBufferSnapshot,
23336 cx: &mut Context<Editor>,
23337) -> InlayHintSettings {
23338 let file = snapshot.file_at(location);
23339 let language = snapshot.language_at(location).map(|l| l.name());
23340 language_settings(language, file, cx).inlay_hints
23341}
23342
23343fn consume_contiguous_rows(
23344 contiguous_row_selections: &mut Vec<Selection<Point>>,
23345 selection: &Selection<Point>,
23346 display_map: &DisplaySnapshot,
23347 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23348) -> (MultiBufferRow, MultiBufferRow) {
23349 contiguous_row_selections.push(selection.clone());
23350 let start_row = starting_row(selection, display_map);
23351 let mut end_row = ending_row(selection, display_map);
23352
23353 while let Some(next_selection) = selections.peek() {
23354 if next_selection.start.row <= end_row.0 {
23355 end_row = ending_row(next_selection, display_map);
23356 contiguous_row_selections.push(selections.next().unwrap().clone());
23357 } else {
23358 break;
23359 }
23360 }
23361 (start_row, end_row)
23362}
23363
23364fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23365 if selection.start.column > 0 {
23366 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23367 } else {
23368 MultiBufferRow(selection.start.row)
23369 }
23370}
23371
23372fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23373 if next_selection.end.column > 0 || next_selection.is_empty() {
23374 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23375 } else {
23376 MultiBufferRow(next_selection.end.row)
23377 }
23378}
23379
23380impl EditorSnapshot {
23381 pub fn remote_selections_in_range<'a>(
23382 &'a self,
23383 range: &'a Range<Anchor>,
23384 collaboration_hub: &dyn CollaborationHub,
23385 cx: &'a App,
23386 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23387 let participant_names = collaboration_hub.user_names(cx);
23388 let participant_indices = collaboration_hub.user_participant_indices(cx);
23389 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23390 let collaborators_by_replica_id = collaborators_by_peer_id
23391 .values()
23392 .map(|collaborator| (collaborator.replica_id, collaborator))
23393 .collect::<HashMap<_, _>>();
23394 self.buffer_snapshot()
23395 .selections_in_range(range, false)
23396 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23397 if replica_id == AGENT_REPLICA_ID {
23398 Some(RemoteSelection {
23399 replica_id,
23400 selection,
23401 cursor_shape,
23402 line_mode,
23403 collaborator_id: CollaboratorId::Agent,
23404 user_name: Some("Agent".into()),
23405 color: cx.theme().players().agent(),
23406 })
23407 } else {
23408 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23409 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23410 let user_name = participant_names.get(&collaborator.user_id).cloned();
23411 Some(RemoteSelection {
23412 replica_id,
23413 selection,
23414 cursor_shape,
23415 line_mode,
23416 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23417 user_name,
23418 color: if let Some(index) = participant_index {
23419 cx.theme().players().color_for_participant(index.0)
23420 } else {
23421 cx.theme().players().absent()
23422 },
23423 })
23424 }
23425 })
23426 }
23427
23428 pub fn hunks_for_ranges(
23429 &self,
23430 ranges: impl IntoIterator<Item = Range<Point>>,
23431 ) -> Vec<MultiBufferDiffHunk> {
23432 let mut hunks = Vec::new();
23433 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23434 HashMap::default();
23435 for query_range in ranges {
23436 let query_rows =
23437 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23438 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23439 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23440 ) {
23441 // Include deleted hunks that are adjacent to the query range, because
23442 // otherwise they would be missed.
23443 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23444 if hunk.status().is_deleted() {
23445 intersects_range |= hunk.row_range.start == query_rows.end;
23446 intersects_range |= hunk.row_range.end == query_rows.start;
23447 }
23448 if intersects_range {
23449 if !processed_buffer_rows
23450 .entry(hunk.buffer_id)
23451 .or_default()
23452 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23453 {
23454 continue;
23455 }
23456 hunks.push(hunk);
23457 }
23458 }
23459 }
23460
23461 hunks
23462 }
23463
23464 fn display_diff_hunks_for_rows<'a>(
23465 &'a self,
23466 display_rows: Range<DisplayRow>,
23467 folded_buffers: &'a HashSet<BufferId>,
23468 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23469 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23470 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23471
23472 self.buffer_snapshot()
23473 .diff_hunks_in_range(buffer_start..buffer_end)
23474 .filter_map(|hunk| {
23475 if folded_buffers.contains(&hunk.buffer_id) {
23476 return None;
23477 }
23478
23479 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23480 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23481
23482 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23483 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23484
23485 let display_hunk = if hunk_display_start.column() != 0 {
23486 DisplayDiffHunk::Folded {
23487 display_row: hunk_display_start.row(),
23488 }
23489 } else {
23490 let mut end_row = hunk_display_end.row();
23491 if hunk_display_end.column() > 0 {
23492 end_row.0 += 1;
23493 }
23494 let is_created_file = hunk.is_created_file();
23495 DisplayDiffHunk::Unfolded {
23496 status: hunk.status(),
23497 diff_base_byte_range: hunk.diff_base_byte_range,
23498 display_row_range: hunk_display_start.row()..end_row,
23499 multi_buffer_range: Anchor::range_in_buffer(
23500 hunk.excerpt_id,
23501 hunk.buffer_id,
23502 hunk.buffer_range,
23503 ),
23504 is_created_file,
23505 }
23506 };
23507
23508 Some(display_hunk)
23509 })
23510 }
23511
23512 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23513 self.display_snapshot
23514 .buffer_snapshot()
23515 .language_at(position)
23516 }
23517
23518 pub fn is_focused(&self) -> bool {
23519 self.is_focused
23520 }
23521
23522 pub fn placeholder_text(&self) -> Option<String> {
23523 self.placeholder_display_snapshot
23524 .as_ref()
23525 .map(|display_map| display_map.text())
23526 }
23527
23528 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23529 self.scroll_anchor.scroll_position(&self.display_snapshot)
23530 }
23531
23532 fn gutter_dimensions(
23533 &self,
23534 font_id: FontId,
23535 font_size: Pixels,
23536 max_line_number_width: Pixels,
23537 cx: &App,
23538 ) -> Option<GutterDimensions> {
23539 if !self.show_gutter {
23540 return None;
23541 }
23542
23543 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23544 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23545
23546 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23547 matches!(
23548 ProjectSettings::get_global(cx).git.git_gutter,
23549 GitGutterSetting::TrackedFiles
23550 )
23551 });
23552 let gutter_settings = EditorSettings::get_global(cx).gutter;
23553 let show_line_numbers = self
23554 .show_line_numbers
23555 .unwrap_or(gutter_settings.line_numbers);
23556 let line_gutter_width = if show_line_numbers {
23557 // Avoid flicker-like gutter resizes when the line number gains another digit by
23558 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23559 let min_width_for_number_on_gutter =
23560 ch_advance * gutter_settings.min_line_number_digits as f32;
23561 max_line_number_width.max(min_width_for_number_on_gutter)
23562 } else {
23563 0.0.into()
23564 };
23565
23566 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23567 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23568
23569 let git_blame_entries_width =
23570 self.git_blame_gutter_max_author_length
23571 .map(|max_author_length| {
23572 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23573 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23574
23575 /// The number of characters to dedicate to gaps and margins.
23576 const SPACING_WIDTH: usize = 4;
23577
23578 let max_char_count = max_author_length.min(renderer.max_author_length())
23579 + ::git::SHORT_SHA_LENGTH
23580 + MAX_RELATIVE_TIMESTAMP.len()
23581 + SPACING_WIDTH;
23582
23583 ch_advance * max_char_count
23584 });
23585
23586 let is_singleton = self.buffer_snapshot().is_singleton();
23587
23588 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23589 left_padding += if !is_singleton {
23590 ch_width * 4.0
23591 } else if show_runnables || show_breakpoints {
23592 ch_width * 3.0
23593 } else if show_git_gutter && show_line_numbers {
23594 ch_width * 2.0
23595 } else if show_git_gutter || show_line_numbers {
23596 ch_width
23597 } else {
23598 px(0.)
23599 };
23600
23601 let shows_folds = is_singleton && gutter_settings.folds;
23602
23603 let right_padding = if shows_folds && show_line_numbers {
23604 ch_width * 4.0
23605 } else if shows_folds || (!is_singleton && show_line_numbers) {
23606 ch_width * 3.0
23607 } else if show_line_numbers {
23608 ch_width
23609 } else {
23610 px(0.)
23611 };
23612
23613 Some(GutterDimensions {
23614 left_padding,
23615 right_padding,
23616 width: line_gutter_width + left_padding + right_padding,
23617 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23618 git_blame_entries_width,
23619 })
23620 }
23621
23622 pub fn render_crease_toggle(
23623 &self,
23624 buffer_row: MultiBufferRow,
23625 row_contains_cursor: bool,
23626 editor: Entity<Editor>,
23627 window: &mut Window,
23628 cx: &mut App,
23629 ) -> Option<AnyElement> {
23630 let folded = self.is_line_folded(buffer_row);
23631 let mut is_foldable = false;
23632
23633 if let Some(crease) = self
23634 .crease_snapshot
23635 .query_row(buffer_row, self.buffer_snapshot())
23636 {
23637 is_foldable = true;
23638 match crease {
23639 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23640 if let Some(render_toggle) = render_toggle {
23641 let toggle_callback =
23642 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23643 if folded {
23644 editor.update(cx, |editor, cx| {
23645 editor.fold_at(buffer_row, window, cx)
23646 });
23647 } else {
23648 editor.update(cx, |editor, cx| {
23649 editor.unfold_at(buffer_row, window, cx)
23650 });
23651 }
23652 });
23653 return Some((render_toggle)(
23654 buffer_row,
23655 folded,
23656 toggle_callback,
23657 window,
23658 cx,
23659 ));
23660 }
23661 }
23662 }
23663 }
23664
23665 is_foldable |= self.starts_indent(buffer_row);
23666
23667 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23668 Some(
23669 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23670 .toggle_state(folded)
23671 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23672 if folded {
23673 this.unfold_at(buffer_row, window, cx);
23674 } else {
23675 this.fold_at(buffer_row, window, cx);
23676 }
23677 }))
23678 .into_any_element(),
23679 )
23680 } else {
23681 None
23682 }
23683 }
23684
23685 pub fn render_crease_trailer(
23686 &self,
23687 buffer_row: MultiBufferRow,
23688 window: &mut Window,
23689 cx: &mut App,
23690 ) -> Option<AnyElement> {
23691 let folded = self.is_line_folded(buffer_row);
23692 if let Crease::Inline { render_trailer, .. } = self
23693 .crease_snapshot
23694 .query_row(buffer_row, self.buffer_snapshot())?
23695 {
23696 let render_trailer = render_trailer.as_ref()?;
23697 Some(render_trailer(buffer_row, folded, window, cx))
23698 } else {
23699 None
23700 }
23701 }
23702}
23703
23704impl Deref for EditorSnapshot {
23705 type Target = DisplaySnapshot;
23706
23707 fn deref(&self) -> &Self::Target {
23708 &self.display_snapshot
23709 }
23710}
23711
23712#[derive(Clone, Debug, PartialEq, Eq)]
23713pub enum EditorEvent {
23714 InputIgnored {
23715 text: Arc<str>,
23716 },
23717 InputHandled {
23718 utf16_range_to_replace: Option<Range<isize>>,
23719 text: Arc<str>,
23720 },
23721 ExcerptsAdded {
23722 buffer: Entity<Buffer>,
23723 predecessor: ExcerptId,
23724 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23725 },
23726 ExcerptsRemoved {
23727 ids: Vec<ExcerptId>,
23728 removed_buffer_ids: Vec<BufferId>,
23729 },
23730 BufferFoldToggled {
23731 ids: Vec<ExcerptId>,
23732 folded: bool,
23733 },
23734 ExcerptsEdited {
23735 ids: Vec<ExcerptId>,
23736 },
23737 ExcerptsExpanded {
23738 ids: Vec<ExcerptId>,
23739 },
23740 BufferEdited,
23741 Edited {
23742 transaction_id: clock::Lamport,
23743 },
23744 Reparsed(BufferId),
23745 Focused,
23746 FocusedIn,
23747 Blurred,
23748 DirtyChanged,
23749 Saved,
23750 TitleChanged,
23751 SelectionsChanged {
23752 local: bool,
23753 },
23754 ScrollPositionChanged {
23755 local: bool,
23756 autoscroll: bool,
23757 },
23758 TransactionUndone {
23759 transaction_id: clock::Lamport,
23760 },
23761 TransactionBegun {
23762 transaction_id: clock::Lamport,
23763 },
23764 CursorShapeChanged,
23765 BreadcrumbsChanged,
23766 PushedToNavHistory {
23767 anchor: Anchor,
23768 is_deactivate: bool,
23769 },
23770}
23771
23772impl EventEmitter<EditorEvent> for Editor {}
23773
23774impl Focusable for Editor {
23775 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23776 self.focus_handle.clone()
23777 }
23778}
23779
23780impl Render for Editor {
23781 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23782 let settings = ThemeSettings::get_global(cx);
23783
23784 let mut text_style = match self.mode {
23785 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23786 color: cx.theme().colors().editor_foreground,
23787 font_family: settings.ui_font.family.clone(),
23788 font_features: settings.ui_font.features.clone(),
23789 font_fallbacks: settings.ui_font.fallbacks.clone(),
23790 font_size: rems(0.875).into(),
23791 font_weight: settings.ui_font.weight,
23792 line_height: relative(settings.buffer_line_height.value()),
23793 ..Default::default()
23794 },
23795 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23796 color: cx.theme().colors().editor_foreground,
23797 font_family: settings.buffer_font.family.clone(),
23798 font_features: settings.buffer_font.features.clone(),
23799 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23800 font_size: settings.buffer_font_size(cx).into(),
23801 font_weight: settings.buffer_font.weight,
23802 line_height: relative(settings.buffer_line_height.value()),
23803 ..Default::default()
23804 },
23805 };
23806 if let Some(text_style_refinement) = &self.text_style_refinement {
23807 text_style.refine(text_style_refinement)
23808 }
23809
23810 let background = match self.mode {
23811 EditorMode::SingleLine => cx.theme().system().transparent,
23812 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23813 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23814 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23815 };
23816
23817 EditorElement::new(
23818 &cx.entity(),
23819 EditorStyle {
23820 background,
23821 border: cx.theme().colors().border,
23822 local_player: cx.theme().players().local(),
23823 text: text_style,
23824 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23825 syntax: cx.theme().syntax().clone(),
23826 status: cx.theme().status().clone(),
23827 inlay_hints_style: make_inlay_hints_style(cx),
23828 edit_prediction_styles: make_suggestion_styles(cx),
23829 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23830 show_underlines: self.diagnostics_enabled(),
23831 },
23832 )
23833 }
23834}
23835
23836impl EntityInputHandler for Editor {
23837 fn text_for_range(
23838 &mut self,
23839 range_utf16: Range<usize>,
23840 adjusted_range: &mut Option<Range<usize>>,
23841 _: &mut Window,
23842 cx: &mut Context<Self>,
23843 ) -> Option<String> {
23844 let snapshot = self.buffer.read(cx).read(cx);
23845 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23846 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23847 if (start.0..end.0) != range_utf16 {
23848 adjusted_range.replace(start.0..end.0);
23849 }
23850 Some(snapshot.text_for_range(start..end).collect())
23851 }
23852
23853 fn selected_text_range(
23854 &mut self,
23855 ignore_disabled_input: bool,
23856 _: &mut Window,
23857 cx: &mut Context<Self>,
23858 ) -> Option<UTF16Selection> {
23859 // Prevent the IME menu from appearing when holding down an alphabetic key
23860 // while input is disabled.
23861 if !ignore_disabled_input && !self.input_enabled {
23862 return None;
23863 }
23864
23865 let selection = self.selections.newest::<OffsetUtf16>(cx);
23866 let range = selection.range();
23867
23868 Some(UTF16Selection {
23869 range: range.start.0..range.end.0,
23870 reversed: selection.reversed,
23871 })
23872 }
23873
23874 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23875 let snapshot = self.buffer.read(cx).read(cx);
23876 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23877 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23878 }
23879
23880 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23881 self.clear_highlights::<InputComposition>(cx);
23882 self.ime_transaction.take();
23883 }
23884
23885 fn replace_text_in_range(
23886 &mut self,
23887 range_utf16: Option<Range<usize>>,
23888 text: &str,
23889 window: &mut Window,
23890 cx: &mut Context<Self>,
23891 ) {
23892 if !self.input_enabled {
23893 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23894 return;
23895 }
23896
23897 self.transact(window, cx, |this, window, cx| {
23898 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23899 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23900 Some(this.selection_replacement_ranges(range_utf16, cx))
23901 } else {
23902 this.marked_text_ranges(cx)
23903 };
23904
23905 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23906 let newest_selection_id = this.selections.newest_anchor().id;
23907 this.selections
23908 .all::<OffsetUtf16>(cx)
23909 .iter()
23910 .zip(ranges_to_replace.iter())
23911 .find_map(|(selection, range)| {
23912 if selection.id == newest_selection_id {
23913 Some(
23914 (range.start.0 as isize - selection.head().0 as isize)
23915 ..(range.end.0 as isize - selection.head().0 as isize),
23916 )
23917 } else {
23918 None
23919 }
23920 })
23921 });
23922
23923 cx.emit(EditorEvent::InputHandled {
23924 utf16_range_to_replace: range_to_replace,
23925 text: text.into(),
23926 });
23927
23928 if let Some(new_selected_ranges) = new_selected_ranges {
23929 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23930 selections.select_ranges(new_selected_ranges)
23931 });
23932 this.backspace(&Default::default(), window, cx);
23933 }
23934
23935 this.handle_input(text, window, cx);
23936 });
23937
23938 if let Some(transaction) = self.ime_transaction {
23939 self.buffer.update(cx, |buffer, cx| {
23940 buffer.group_until_transaction(transaction, cx);
23941 });
23942 }
23943
23944 self.unmark_text(window, cx);
23945 }
23946
23947 fn replace_and_mark_text_in_range(
23948 &mut self,
23949 range_utf16: Option<Range<usize>>,
23950 text: &str,
23951 new_selected_range_utf16: Option<Range<usize>>,
23952 window: &mut Window,
23953 cx: &mut Context<Self>,
23954 ) {
23955 if !self.input_enabled {
23956 return;
23957 }
23958
23959 let transaction = self.transact(window, cx, |this, window, cx| {
23960 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23961 let snapshot = this.buffer.read(cx).read(cx);
23962 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23963 for marked_range in &mut marked_ranges {
23964 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23965 marked_range.start.0 += relative_range_utf16.start;
23966 marked_range.start =
23967 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23968 marked_range.end =
23969 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23970 }
23971 }
23972 Some(marked_ranges)
23973 } else if let Some(range_utf16) = range_utf16 {
23974 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23975 Some(this.selection_replacement_ranges(range_utf16, cx))
23976 } else {
23977 None
23978 };
23979
23980 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23981 let newest_selection_id = this.selections.newest_anchor().id;
23982 this.selections
23983 .all::<OffsetUtf16>(cx)
23984 .iter()
23985 .zip(ranges_to_replace.iter())
23986 .find_map(|(selection, range)| {
23987 if selection.id == newest_selection_id {
23988 Some(
23989 (range.start.0 as isize - selection.head().0 as isize)
23990 ..(range.end.0 as isize - selection.head().0 as isize),
23991 )
23992 } else {
23993 None
23994 }
23995 })
23996 });
23997
23998 cx.emit(EditorEvent::InputHandled {
23999 utf16_range_to_replace: range_to_replace,
24000 text: text.into(),
24001 });
24002
24003 if let Some(ranges) = ranges_to_replace {
24004 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24005 s.select_ranges(ranges)
24006 });
24007 }
24008
24009 let marked_ranges = {
24010 let snapshot = this.buffer.read(cx).read(cx);
24011 this.selections
24012 .disjoint_anchors_arc()
24013 .iter()
24014 .map(|selection| {
24015 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24016 })
24017 .collect::<Vec<_>>()
24018 };
24019
24020 if text.is_empty() {
24021 this.unmark_text(window, cx);
24022 } else {
24023 this.highlight_text::<InputComposition>(
24024 marked_ranges.clone(),
24025 HighlightStyle {
24026 underline: Some(UnderlineStyle {
24027 thickness: px(1.),
24028 color: None,
24029 wavy: false,
24030 }),
24031 ..Default::default()
24032 },
24033 cx,
24034 );
24035 }
24036
24037 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24038 let use_autoclose = this.use_autoclose;
24039 let use_auto_surround = this.use_auto_surround;
24040 this.set_use_autoclose(false);
24041 this.set_use_auto_surround(false);
24042 this.handle_input(text, window, cx);
24043 this.set_use_autoclose(use_autoclose);
24044 this.set_use_auto_surround(use_auto_surround);
24045
24046 if let Some(new_selected_range) = new_selected_range_utf16 {
24047 let snapshot = this.buffer.read(cx).read(cx);
24048 let new_selected_ranges = marked_ranges
24049 .into_iter()
24050 .map(|marked_range| {
24051 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24052 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24053 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24054 snapshot.clip_offset_utf16(new_start, Bias::Left)
24055 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24056 })
24057 .collect::<Vec<_>>();
24058
24059 drop(snapshot);
24060 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24061 selections.select_ranges(new_selected_ranges)
24062 });
24063 }
24064 });
24065
24066 self.ime_transaction = self.ime_transaction.or(transaction);
24067 if let Some(transaction) = self.ime_transaction {
24068 self.buffer.update(cx, |buffer, cx| {
24069 buffer.group_until_transaction(transaction, cx);
24070 });
24071 }
24072
24073 if self.text_highlights::<InputComposition>(cx).is_none() {
24074 self.ime_transaction.take();
24075 }
24076 }
24077
24078 fn bounds_for_range(
24079 &mut self,
24080 range_utf16: Range<usize>,
24081 element_bounds: gpui::Bounds<Pixels>,
24082 window: &mut Window,
24083 cx: &mut Context<Self>,
24084 ) -> Option<gpui::Bounds<Pixels>> {
24085 let text_layout_details = self.text_layout_details(window);
24086 let CharacterDimensions {
24087 em_width,
24088 em_advance,
24089 line_height,
24090 } = self.character_dimensions(window);
24091
24092 let snapshot = self.snapshot(window, cx);
24093 let scroll_position = snapshot.scroll_position();
24094 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24095
24096 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24097 let x = Pixels::from(
24098 ScrollOffset::from(
24099 snapshot.x_for_display_point(start, &text_layout_details)
24100 + self.gutter_dimensions.full_width(),
24101 ) - scroll_left,
24102 );
24103 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24104
24105 Some(Bounds {
24106 origin: element_bounds.origin + point(x, y),
24107 size: size(em_width, line_height),
24108 })
24109 }
24110
24111 fn character_index_for_point(
24112 &mut self,
24113 point: gpui::Point<Pixels>,
24114 _window: &mut Window,
24115 _cx: &mut Context<Self>,
24116 ) -> Option<usize> {
24117 let position_map = self.last_position_map.as_ref()?;
24118 if !position_map.text_hitbox.contains(&point) {
24119 return None;
24120 }
24121 let display_point = position_map.point_for_position(point).previous_valid;
24122 let anchor = position_map
24123 .snapshot
24124 .display_point_to_anchor(display_point, Bias::Left);
24125 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24126 Some(utf16_offset.0)
24127 }
24128}
24129
24130trait SelectionExt {
24131 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24132 fn spanned_rows(
24133 &self,
24134 include_end_if_at_line_start: bool,
24135 map: &DisplaySnapshot,
24136 ) -> Range<MultiBufferRow>;
24137}
24138
24139impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24140 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24141 let start = self
24142 .start
24143 .to_point(map.buffer_snapshot())
24144 .to_display_point(map);
24145 let end = self
24146 .end
24147 .to_point(map.buffer_snapshot())
24148 .to_display_point(map);
24149 if self.reversed {
24150 end..start
24151 } else {
24152 start..end
24153 }
24154 }
24155
24156 fn spanned_rows(
24157 &self,
24158 include_end_if_at_line_start: bool,
24159 map: &DisplaySnapshot,
24160 ) -> Range<MultiBufferRow> {
24161 let start = self.start.to_point(map.buffer_snapshot());
24162 let mut end = self.end.to_point(map.buffer_snapshot());
24163 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24164 end.row -= 1;
24165 }
24166
24167 let buffer_start = map.prev_line_boundary(start).0;
24168 let buffer_end = map.next_line_boundary(end).0;
24169 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24170 }
24171}
24172
24173impl<T: InvalidationRegion> InvalidationStack<T> {
24174 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24175 where
24176 S: Clone + ToOffset,
24177 {
24178 while let Some(region) = self.last() {
24179 let all_selections_inside_invalidation_ranges =
24180 if selections.len() == region.ranges().len() {
24181 selections
24182 .iter()
24183 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24184 .all(|(selection, invalidation_range)| {
24185 let head = selection.head().to_offset(buffer);
24186 invalidation_range.start <= head && invalidation_range.end >= head
24187 })
24188 } else {
24189 false
24190 };
24191
24192 if all_selections_inside_invalidation_ranges {
24193 break;
24194 } else {
24195 self.pop();
24196 }
24197 }
24198 }
24199}
24200
24201impl<T> Default for InvalidationStack<T> {
24202 fn default() -> Self {
24203 Self(Default::default())
24204 }
24205}
24206
24207impl<T> Deref for InvalidationStack<T> {
24208 type Target = Vec<T>;
24209
24210 fn deref(&self) -> &Self::Target {
24211 &self.0
24212 }
24213}
24214
24215impl<T> DerefMut for InvalidationStack<T> {
24216 fn deref_mut(&mut self) -> &mut Self::Target {
24217 &mut self.0
24218 }
24219}
24220
24221impl InvalidationRegion for SnippetState {
24222 fn ranges(&self) -> &[Range<Anchor>] {
24223 &self.ranges[self.active_index]
24224 }
24225}
24226
24227fn edit_prediction_edit_text(
24228 current_snapshot: &BufferSnapshot,
24229 edits: &[(Range<Anchor>, String)],
24230 edit_preview: &EditPreview,
24231 include_deletions: bool,
24232 cx: &App,
24233) -> HighlightedText {
24234 let edits = edits
24235 .iter()
24236 .map(|(anchor, text)| {
24237 (
24238 anchor.start.text_anchor..anchor.end.text_anchor,
24239 text.clone(),
24240 )
24241 })
24242 .collect::<Vec<_>>();
24243
24244 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24245}
24246
24247fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24248 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24249 // Just show the raw edit text with basic styling
24250 let mut text = String::new();
24251 let mut highlights = Vec::new();
24252
24253 let insertion_highlight_style = HighlightStyle {
24254 color: Some(cx.theme().colors().text),
24255 ..Default::default()
24256 };
24257
24258 for (_, edit_text) in edits {
24259 let start_offset = text.len();
24260 text.push_str(edit_text);
24261 let end_offset = text.len();
24262
24263 if start_offset < end_offset {
24264 highlights.push((start_offset..end_offset, insertion_highlight_style));
24265 }
24266 }
24267
24268 HighlightedText {
24269 text: text.into(),
24270 highlights,
24271 }
24272}
24273
24274pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24275 match severity {
24276 lsp::DiagnosticSeverity::ERROR => colors.error,
24277 lsp::DiagnosticSeverity::WARNING => colors.warning,
24278 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24279 lsp::DiagnosticSeverity::HINT => colors.info,
24280 _ => colors.ignored,
24281 }
24282}
24283
24284pub fn styled_runs_for_code_label<'a>(
24285 label: &'a CodeLabel,
24286 syntax_theme: &'a theme::SyntaxTheme,
24287) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24288 let fade_out = HighlightStyle {
24289 fade_out: Some(0.35),
24290 ..Default::default()
24291 };
24292
24293 let mut prev_end = label.filter_range.end;
24294 label
24295 .runs
24296 .iter()
24297 .enumerate()
24298 .flat_map(move |(ix, (range, highlight_id))| {
24299 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24300 style
24301 } else {
24302 return Default::default();
24303 };
24304 let muted_style = style.highlight(fade_out);
24305
24306 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24307 if range.start >= label.filter_range.end {
24308 if range.start > prev_end {
24309 runs.push((prev_end..range.start, fade_out));
24310 }
24311 runs.push((range.clone(), muted_style));
24312 } else if range.end <= label.filter_range.end {
24313 runs.push((range.clone(), style));
24314 } else {
24315 runs.push((range.start..label.filter_range.end, style));
24316 runs.push((label.filter_range.end..range.end, muted_style));
24317 }
24318 prev_end = cmp::max(prev_end, range.end);
24319
24320 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24321 runs.push((prev_end..label.text.len(), fade_out));
24322 }
24323
24324 runs
24325 })
24326}
24327
24328pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24329 let mut prev_index = 0;
24330 let mut prev_codepoint: Option<char> = None;
24331 text.char_indices()
24332 .chain([(text.len(), '\0')])
24333 .filter_map(move |(index, codepoint)| {
24334 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24335 let is_boundary = index == text.len()
24336 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24337 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24338 if is_boundary {
24339 let chunk = &text[prev_index..index];
24340 prev_index = index;
24341 Some(chunk)
24342 } else {
24343 None
24344 }
24345 })
24346}
24347
24348pub trait RangeToAnchorExt: Sized {
24349 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24350
24351 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24352 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24353 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24354 }
24355}
24356
24357impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24358 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24359 let start_offset = self.start.to_offset(snapshot);
24360 let end_offset = self.end.to_offset(snapshot);
24361 if start_offset == end_offset {
24362 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24363 } else {
24364 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24365 }
24366 }
24367}
24368
24369pub trait RowExt {
24370 fn as_f64(&self) -> f64;
24371
24372 fn next_row(&self) -> Self;
24373
24374 fn previous_row(&self) -> Self;
24375
24376 fn minus(&self, other: Self) -> u32;
24377}
24378
24379impl RowExt for DisplayRow {
24380 fn as_f64(&self) -> f64 {
24381 self.0 as _
24382 }
24383
24384 fn next_row(&self) -> Self {
24385 Self(self.0 + 1)
24386 }
24387
24388 fn previous_row(&self) -> Self {
24389 Self(self.0.saturating_sub(1))
24390 }
24391
24392 fn minus(&self, other: Self) -> u32 {
24393 self.0 - other.0
24394 }
24395}
24396
24397impl RowExt for MultiBufferRow {
24398 fn as_f64(&self) -> f64 {
24399 self.0 as _
24400 }
24401
24402 fn next_row(&self) -> Self {
24403 Self(self.0 + 1)
24404 }
24405
24406 fn previous_row(&self) -> Self {
24407 Self(self.0.saturating_sub(1))
24408 }
24409
24410 fn minus(&self, other: Self) -> u32 {
24411 self.0 - other.0
24412 }
24413}
24414
24415trait RowRangeExt {
24416 type Row;
24417
24418 fn len(&self) -> usize;
24419
24420 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24421}
24422
24423impl RowRangeExt for Range<MultiBufferRow> {
24424 type Row = MultiBufferRow;
24425
24426 fn len(&self) -> usize {
24427 (self.end.0 - self.start.0) as usize
24428 }
24429
24430 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24431 (self.start.0..self.end.0).map(MultiBufferRow)
24432 }
24433}
24434
24435impl RowRangeExt for Range<DisplayRow> {
24436 type Row = DisplayRow;
24437
24438 fn len(&self) -> usize {
24439 (self.end.0 - self.start.0) as usize
24440 }
24441
24442 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24443 (self.start.0..self.end.0).map(DisplayRow)
24444 }
24445}
24446
24447/// If select range has more than one line, we
24448/// just point the cursor to range.start.
24449fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24450 if range.start.row == range.end.row {
24451 range
24452 } else {
24453 range.start..range.start
24454 }
24455}
24456pub struct KillRing(ClipboardItem);
24457impl Global for KillRing {}
24458
24459const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24460
24461enum BreakpointPromptEditAction {
24462 Log,
24463 Condition,
24464 HitCondition,
24465}
24466
24467struct BreakpointPromptEditor {
24468 pub(crate) prompt: Entity<Editor>,
24469 editor: WeakEntity<Editor>,
24470 breakpoint_anchor: Anchor,
24471 breakpoint: Breakpoint,
24472 edit_action: BreakpointPromptEditAction,
24473 block_ids: HashSet<CustomBlockId>,
24474 editor_margins: Arc<Mutex<EditorMargins>>,
24475 _subscriptions: Vec<Subscription>,
24476}
24477
24478impl BreakpointPromptEditor {
24479 const MAX_LINES: u8 = 4;
24480
24481 fn new(
24482 editor: WeakEntity<Editor>,
24483 breakpoint_anchor: Anchor,
24484 breakpoint: Breakpoint,
24485 edit_action: BreakpointPromptEditAction,
24486 window: &mut Window,
24487 cx: &mut Context<Self>,
24488 ) -> Self {
24489 let base_text = match edit_action {
24490 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24491 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24492 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24493 }
24494 .map(|msg| msg.to_string())
24495 .unwrap_or_default();
24496
24497 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24498 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24499
24500 let prompt = cx.new(|cx| {
24501 let mut prompt = Editor::new(
24502 EditorMode::AutoHeight {
24503 min_lines: 1,
24504 max_lines: Some(Self::MAX_LINES as usize),
24505 },
24506 buffer,
24507 None,
24508 window,
24509 cx,
24510 );
24511 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24512 prompt.set_show_cursor_when_unfocused(false, cx);
24513 prompt.set_placeholder_text(
24514 match edit_action {
24515 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24516 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24517 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24518 },
24519 window,
24520 cx,
24521 );
24522
24523 prompt
24524 });
24525
24526 Self {
24527 prompt,
24528 editor,
24529 breakpoint_anchor,
24530 breakpoint,
24531 edit_action,
24532 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24533 block_ids: Default::default(),
24534 _subscriptions: vec![],
24535 }
24536 }
24537
24538 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24539 self.block_ids.extend(block_ids)
24540 }
24541
24542 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24543 if let Some(editor) = self.editor.upgrade() {
24544 let message = self
24545 .prompt
24546 .read(cx)
24547 .buffer
24548 .read(cx)
24549 .as_singleton()
24550 .expect("A multi buffer in breakpoint prompt isn't possible")
24551 .read(cx)
24552 .as_rope()
24553 .to_string();
24554
24555 editor.update(cx, |editor, cx| {
24556 editor.edit_breakpoint_at_anchor(
24557 self.breakpoint_anchor,
24558 self.breakpoint.clone(),
24559 match self.edit_action {
24560 BreakpointPromptEditAction::Log => {
24561 BreakpointEditAction::EditLogMessage(message.into())
24562 }
24563 BreakpointPromptEditAction::Condition => {
24564 BreakpointEditAction::EditCondition(message.into())
24565 }
24566 BreakpointPromptEditAction::HitCondition => {
24567 BreakpointEditAction::EditHitCondition(message.into())
24568 }
24569 },
24570 cx,
24571 );
24572
24573 editor.remove_blocks(self.block_ids.clone(), None, cx);
24574 cx.focus_self(window);
24575 });
24576 }
24577 }
24578
24579 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24580 self.editor
24581 .update(cx, |editor, cx| {
24582 editor.remove_blocks(self.block_ids.clone(), None, cx);
24583 window.focus(&editor.focus_handle);
24584 })
24585 .log_err();
24586 }
24587
24588 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24589 let settings = ThemeSettings::get_global(cx);
24590 let text_style = TextStyle {
24591 color: if self.prompt.read(cx).read_only(cx) {
24592 cx.theme().colors().text_disabled
24593 } else {
24594 cx.theme().colors().text
24595 },
24596 font_family: settings.buffer_font.family.clone(),
24597 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24598 font_size: settings.buffer_font_size(cx).into(),
24599 font_weight: settings.buffer_font.weight,
24600 line_height: relative(settings.buffer_line_height.value()),
24601 ..Default::default()
24602 };
24603 EditorElement::new(
24604 &self.prompt,
24605 EditorStyle {
24606 background: cx.theme().colors().editor_background,
24607 local_player: cx.theme().players().local(),
24608 text: text_style,
24609 ..Default::default()
24610 },
24611 )
24612 }
24613}
24614
24615impl Render for BreakpointPromptEditor {
24616 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24617 let editor_margins = *self.editor_margins.lock();
24618 let gutter_dimensions = editor_margins.gutter;
24619 h_flex()
24620 .key_context("Editor")
24621 .bg(cx.theme().colors().editor_background)
24622 .border_y_1()
24623 .border_color(cx.theme().status().info_border)
24624 .size_full()
24625 .py(window.line_height() / 2.5)
24626 .on_action(cx.listener(Self::confirm))
24627 .on_action(cx.listener(Self::cancel))
24628 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24629 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24630 }
24631}
24632
24633impl Focusable for BreakpointPromptEditor {
24634 fn focus_handle(&self, cx: &App) -> FocusHandle {
24635 self.prompt.focus_handle(cx)
24636 }
24637}
24638
24639fn all_edits_insertions_or_deletions(
24640 edits: &Vec<(Range<Anchor>, String)>,
24641 snapshot: &MultiBufferSnapshot,
24642) -> bool {
24643 let mut all_insertions = true;
24644 let mut all_deletions = true;
24645
24646 for (range, new_text) in edits.iter() {
24647 let range_is_empty = range.to_offset(snapshot).is_empty();
24648 let text_is_empty = new_text.is_empty();
24649
24650 if range_is_empty != text_is_empty {
24651 if range_is_empty {
24652 all_deletions = false;
24653 } else {
24654 all_insertions = false;
24655 }
24656 } else {
24657 return false;
24658 }
24659
24660 if !all_insertions && !all_deletions {
24661 return false;
24662 }
24663 }
24664 all_insertions || all_deletions
24665}
24666
24667struct MissingEditPredictionKeybindingTooltip;
24668
24669impl Render for MissingEditPredictionKeybindingTooltip {
24670 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24671 ui::tooltip_container(cx, |container, cx| {
24672 container
24673 .flex_shrink_0()
24674 .max_w_80()
24675 .min_h(rems_from_px(124.))
24676 .justify_between()
24677 .child(
24678 v_flex()
24679 .flex_1()
24680 .text_ui_sm(cx)
24681 .child(Label::new("Conflict with Accept Keybinding"))
24682 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24683 )
24684 .child(
24685 h_flex()
24686 .pb_1()
24687 .gap_1()
24688 .items_end()
24689 .w_full()
24690 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24691 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24692 }))
24693 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24694 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24695 })),
24696 )
24697 })
24698 }
24699}
24700
24701#[derive(Debug, Clone, Copy, PartialEq)]
24702pub struct LineHighlight {
24703 pub background: Background,
24704 pub border: Option<gpui::Hsla>,
24705 pub include_gutter: bool,
24706 pub type_id: Option<TypeId>,
24707}
24708
24709struct LineManipulationResult {
24710 pub new_text: String,
24711 pub line_count_before: usize,
24712 pub line_count_after: usize,
24713}
24714
24715fn render_diff_hunk_controls(
24716 row: u32,
24717 status: &DiffHunkStatus,
24718 hunk_range: Range<Anchor>,
24719 is_created_file: bool,
24720 line_height: Pixels,
24721 editor: &Entity<Editor>,
24722 _window: &mut Window,
24723 cx: &mut App,
24724) -> AnyElement {
24725 h_flex()
24726 .h(line_height)
24727 .mr_1()
24728 .gap_1()
24729 .px_0p5()
24730 .pb_1()
24731 .border_x_1()
24732 .border_b_1()
24733 .border_color(cx.theme().colors().border_variant)
24734 .rounded_b_lg()
24735 .bg(cx.theme().colors().editor_background)
24736 .gap_1()
24737 .block_mouse_except_scroll()
24738 .shadow_md()
24739 .child(if status.has_secondary_hunk() {
24740 Button::new(("stage", row as u64), "Stage")
24741 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24742 .tooltip({
24743 let focus_handle = editor.focus_handle(cx);
24744 move |window, cx| {
24745 Tooltip::for_action_in(
24746 "Stage Hunk",
24747 &::git::ToggleStaged,
24748 &focus_handle,
24749 window,
24750 cx,
24751 )
24752 }
24753 })
24754 .on_click({
24755 let editor = editor.clone();
24756 move |_event, _window, cx| {
24757 editor.update(cx, |editor, cx| {
24758 editor.stage_or_unstage_diff_hunks(
24759 true,
24760 vec![hunk_range.start..hunk_range.start],
24761 cx,
24762 );
24763 });
24764 }
24765 })
24766 } else {
24767 Button::new(("unstage", row as u64), "Unstage")
24768 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24769 .tooltip({
24770 let focus_handle = editor.focus_handle(cx);
24771 move |window, cx| {
24772 Tooltip::for_action_in(
24773 "Unstage Hunk",
24774 &::git::ToggleStaged,
24775 &focus_handle,
24776 window,
24777 cx,
24778 )
24779 }
24780 })
24781 .on_click({
24782 let editor = editor.clone();
24783 move |_event, _window, cx| {
24784 editor.update(cx, |editor, cx| {
24785 editor.stage_or_unstage_diff_hunks(
24786 false,
24787 vec![hunk_range.start..hunk_range.start],
24788 cx,
24789 );
24790 });
24791 }
24792 })
24793 })
24794 .child(
24795 Button::new(("restore", row as u64), "Restore")
24796 .tooltip({
24797 let focus_handle = editor.focus_handle(cx);
24798 move |window, cx| {
24799 Tooltip::for_action_in(
24800 "Restore Hunk",
24801 &::git::Restore,
24802 &focus_handle,
24803 window,
24804 cx,
24805 )
24806 }
24807 })
24808 .on_click({
24809 let editor = editor.clone();
24810 move |_event, window, cx| {
24811 editor.update(cx, |editor, cx| {
24812 let snapshot = editor.snapshot(window, cx);
24813 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24814 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24815 });
24816 }
24817 })
24818 .disabled(is_created_file),
24819 )
24820 .when(
24821 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24822 |el| {
24823 el.child(
24824 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24825 .shape(IconButtonShape::Square)
24826 .icon_size(IconSize::Small)
24827 // .disabled(!has_multiple_hunks)
24828 .tooltip({
24829 let focus_handle = editor.focus_handle(cx);
24830 move |window, cx| {
24831 Tooltip::for_action_in(
24832 "Next Hunk",
24833 &GoToHunk,
24834 &focus_handle,
24835 window,
24836 cx,
24837 )
24838 }
24839 })
24840 .on_click({
24841 let editor = editor.clone();
24842 move |_event, window, cx| {
24843 editor.update(cx, |editor, cx| {
24844 let snapshot = editor.snapshot(window, cx);
24845 let position =
24846 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24847 editor.go_to_hunk_before_or_after_position(
24848 &snapshot,
24849 position,
24850 Direction::Next,
24851 window,
24852 cx,
24853 );
24854 editor.expand_selected_diff_hunks(cx);
24855 });
24856 }
24857 }),
24858 )
24859 .child(
24860 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24861 .shape(IconButtonShape::Square)
24862 .icon_size(IconSize::Small)
24863 // .disabled(!has_multiple_hunks)
24864 .tooltip({
24865 let focus_handle = editor.focus_handle(cx);
24866 move |window, cx| {
24867 Tooltip::for_action_in(
24868 "Previous Hunk",
24869 &GoToPreviousHunk,
24870 &focus_handle,
24871 window,
24872 cx,
24873 )
24874 }
24875 })
24876 .on_click({
24877 let editor = editor.clone();
24878 move |_event, window, cx| {
24879 editor.update(cx, |editor, cx| {
24880 let snapshot = editor.snapshot(window, cx);
24881 let point =
24882 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24883 editor.go_to_hunk_before_or_after_position(
24884 &snapshot,
24885 point,
24886 Direction::Prev,
24887 window,
24888 cx,
24889 );
24890 editor.expand_selected_diff_hunks(cx);
24891 });
24892 }
24893 }),
24894 )
24895 },
24896 )
24897 .into_any_element()
24898}
24899
24900pub fn multibuffer_context_lines(cx: &App) -> u32 {
24901 EditorSettings::try_get(cx)
24902 .map(|settings| settings.excerpt_context_lines)
24903 .unwrap_or(2)
24904 .min(32)
24905}