1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlay_hint_cache;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use proposed_changes_editor::{
72 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
73};
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79};
80use aho_corasick::AhoCorasick;
81use anyhow::{Context as _, Result, anyhow};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::{AGENT_REPLICA_ID, ReplicaId};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
95use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
96use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
97use futures::{
98 FutureExt, StreamExt as _,
99 future::{self, Shared, join},
100 stream::FuturesUnordered,
101};
102use fuzzy::{StringMatch, StringMatchCandidate};
103use git::blame::{GitBlame, GlobalBlameRenderer};
104use gpui::{
105 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
106 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
107 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
108 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
109 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
110 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
111 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
112 div, point, prelude::*, pulsating_between, px, relative, size,
113};
114use highlight_matching_bracket::refresh_matching_bracket_highlights;
115use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
125 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143 ToOffsetUtf16,
144};
145use parking_lot::Mutex;
146use persistence::DB;
147use project::{
148 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
149 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
150 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
151 ProjectTransaction, TaskSourceKind,
152 debugger::{
153 breakpoint_store::{
154 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
155 BreakpointStore, BreakpointStoreEvent,
156 },
157 session::{Session, SessionEvent},
158 },
159 git_store::{GitStoreEvent, RepositoryEvent},
160 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
161 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
162};
163use rand::seq::SliceRandom;
164use rpc::{ErrorCode, ErrorExt, proto::PeerId};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
166use selections_collection::{
167 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
168};
169use serde::{Deserialize, Serialize};
170use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 iter::{self, Peekable},
179 mem,
180 num::NonZeroU32,
181 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 sync::Arc,
185 time::{Duration, Instant},
186};
187use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::{
208 code_context_menus::CompletionsMenuSource,
209 editor_settings::MultiCursorModifier,
210 hover_links::{find_url, find_url_from_range},
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
213};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
218const MAX_LINE_LEN: usize = 1024;
219const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
220const MAX_SELECTION_HISTORY_LEN: usize = 1024;
221pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
222#[doc(hidden)]
223pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
224pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
225
226pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
227pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
229pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(u32),
283 DebuggerValue(u32),
284 // LSP
285 Hint(u32),
286 Color(u32),
287}
288
289impl InlayId {
290 fn id(&self) -> u32 {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_vertical);
362 workspace.register_action(Editor::new_file_horizontal);
363 workspace.register_action(Editor::cancel_language_server_work);
364 workspace.register_action(Editor::toggle_focus);
365 },
366 )
367 .detach();
368
369 cx.on_action(move |_: &workspace::NewFile, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach();
381 }
382 });
383 cx.on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 if let Some(app_state) = app_state.upgrade() {
386 workspace::open_new(
387 Default::default(),
388 app_state,
389 cx,
390 |workspace, window, cx| {
391 cx.activate(true);
392 Editor::new_file(workspace, &Default::default(), window, cx)
393 },
394 )
395 .detach();
396 }
397 });
398}
399
400pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
401 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
402}
403
404pub trait DiagnosticRenderer {
405 fn render_group(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
408 buffer_id: BufferId,
409 snapshot: EditorSnapshot,
410 editor: WeakEntity<Editor>,
411 cx: &mut App,
412 ) -> Vec<BlockProperties<Anchor>>;
413
414 fn render_hover(
415 &self,
416 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
417 range: Range<Point>,
418 buffer_id: BufferId,
419 cx: &mut App,
420 ) -> Option<Entity<markdown::Markdown>>;
421
422 fn open_link(
423 &self,
424 editor: &mut Editor,
425 link: SharedString,
426 window: &mut Window,
427 cx: &mut Context<Editor>,
428 );
429}
430
431pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
432
433impl GlobalDiagnosticRenderer {
434 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
435 cx.try_global::<Self>().map(|g| g.0.clone())
436 }
437}
438
439impl gpui::Global for GlobalDiagnosticRenderer {}
440pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
441 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
442}
443
444pub struct SearchWithinRange;
445
446trait InvalidationRegion {
447 fn ranges(&self) -> &[Range<Anchor>];
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum SelectPhase {
452 Begin {
453 position: DisplayPoint,
454 add: bool,
455 click_count: usize,
456 },
457 BeginColumnar {
458 position: DisplayPoint,
459 reset: bool,
460 mode: ColumnarMode,
461 goal_column: u32,
462 },
463 Extend {
464 position: DisplayPoint,
465 click_count: usize,
466 },
467 Update {
468 position: DisplayPoint,
469 goal_column: u32,
470 scroll_delta: gpui::Point<f32>,
471 },
472 End,
473}
474
475#[derive(Clone, Debug, PartialEq)]
476pub enum ColumnarMode {
477 FromMouse,
478 FromSelection,
479}
480
481#[derive(Clone, Debug)]
482pub enum SelectMode {
483 Character,
484 Word(Range<Anchor>),
485 Line(Range<Anchor>),
486 All,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// When set to `true`, the editor's height will be determined by its content.
502 sized_by_content: bool,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sized_by_content: false,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, String)>,
637 edit_preview: Option<EditPreview>,
638 display_mode: EditDisplayMode,
639 snapshot: BufferSnapshot,
640 },
641 /// Move to a specific location in the active editor
642 MoveWithin {
643 target: Anchor,
644 snapshot: BufferSnapshot,
645 },
646 /// Move to a specific location in a different editor (not the active one)
647 MoveOutside {
648 target: language::Anchor,
649 snapshot: BufferSnapshot,
650 },
651}
652
653struct EditPredictionState {
654 inlay_ids: Vec<InlayId>,
655 completion: EditPrediction,
656 completion_id: Option<SharedString>,
657 invalidation_range: Option<Range<Anchor>>,
658}
659
660enum EditPredictionSettings {
661 Disabled,
662 Enabled {
663 show_in_menu: bool,
664 preview_requires_modifier: bool,
665 },
666}
667
668enum EditPredictionHighlight {}
669
670#[derive(Debug, Clone)]
671struct InlineDiagnostic {
672 message: SharedString,
673 group_id: usize,
674 is_primary: bool,
675 start: Point,
676 severity: lsp::DiagnosticSeverity,
677}
678
679pub enum MenuEditPredictionsPolicy {
680 Never,
681 ByProvider,
682}
683
684pub enum EditPredictionPreview {
685 /// Modifier is not pressed
686 Inactive { released_too_fast: bool },
687 /// Modifier pressed
688 Active {
689 since: Instant,
690 previous_scroll_position: Option<ScrollAnchor>,
691 },
692}
693
694impl EditPredictionPreview {
695 pub fn released_too_fast(&self) -> bool {
696 match self {
697 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
698 EditPredictionPreview::Active { .. } => false,
699 }
700 }
701
702 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
703 if let EditPredictionPreview::Active {
704 previous_scroll_position,
705 ..
706 } = self
707 {
708 *previous_scroll_position = scroll_position;
709 }
710 }
711}
712
713pub struct ContextMenuOptions {
714 pub min_entries_visible: usize,
715 pub max_entries_visible: usize,
716 pub placement: Option<ContextMenuPlacement>,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum ContextMenuPlacement {
721 Above,
722 Below,
723}
724
725#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
726struct EditorActionId(usize);
727
728impl EditorActionId {
729 pub fn post_inc(&mut self) -> Self {
730 let answer = self.0;
731
732 *self = Self(answer + 1);
733
734 Self(answer)
735 }
736}
737
738// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
739// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
740
741type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
742type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
743
744#[derive(Default)]
745struct ScrollbarMarkerState {
746 scrollbar_size: Size<Pixels>,
747 dirty: bool,
748 markers: Arc<[PaintQuad]>,
749 pending_refresh: Option<Task<Result<()>>>,
750}
751
752impl ScrollbarMarkerState {
753 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
754 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
755 }
756}
757
758#[derive(Clone, Copy, PartialEq, Eq)]
759pub enum MinimapVisibility {
760 Disabled,
761 Enabled {
762 /// The configuration currently present in the users settings.
763 setting_configuration: bool,
764 /// Whether to override the currently set visibility from the users setting.
765 toggle_override: bool,
766 },
767}
768
769impl MinimapVisibility {
770 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
771 if mode.is_full() {
772 Self::Enabled {
773 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
774 toggle_override: false,
775 }
776 } else {
777 Self::Disabled
778 }
779 }
780
781 fn hidden(&self) -> Self {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => Self::Enabled {
787 setting_configuration,
788 toggle_override: setting_configuration,
789 },
790 Self::Disabled => Self::Disabled,
791 }
792 }
793
794 fn disabled(&self) -> bool {
795 matches!(*self, Self::Disabled)
796 }
797
798 fn settings_visibility(&self) -> bool {
799 match *self {
800 Self::Enabled {
801 setting_configuration,
802 ..
803 } => setting_configuration,
804 _ => false,
805 }
806 }
807
808 fn visible(&self) -> bool {
809 match *self {
810 Self::Enabled {
811 setting_configuration,
812 toggle_override,
813 } => setting_configuration ^ toggle_override,
814 _ => false,
815 }
816 }
817
818 fn toggle_visibility(&self) -> Self {
819 match *self {
820 Self::Enabled {
821 toggle_override,
822 setting_configuration,
823 } => Self::Enabled {
824 setting_configuration,
825 toggle_override: !toggle_override,
826 },
827 Self::Disabled => Self::Disabled,
828 }
829 }
830}
831
832#[derive(Clone, Debug)]
833struct RunnableTasks {
834 templates: Vec<(TaskSourceKind, TaskTemplate)>,
835 offset: multi_buffer::Anchor,
836 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
837 column: u32,
838 // Values of all named captures, including those starting with '_'
839 extra_variables: HashMap<String, String>,
840 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
841 context_range: Range<BufferOffset>,
842}
843
844impl RunnableTasks {
845 fn resolve<'a>(
846 &'a self,
847 cx: &'a task::TaskContext,
848 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
849 self.templates.iter().filter_map(|(kind, template)| {
850 template
851 .resolve_task(&kind.to_id_base(), cx)
852 .map(|task| (kind.clone(), task))
853 })
854 }
855}
856
857#[derive(Clone)]
858pub struct ResolvedTasks {
859 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
860 position: Anchor,
861}
862
863#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
864struct BufferOffset(usize);
865
866/// Addons allow storing per-editor state in other crates (e.g. Vim)
867pub trait Addon: 'static {
868 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
869
870 fn render_buffer_header_controls(
871 &self,
872 _: &ExcerptInfo,
873 _: &Window,
874 _: &App,
875 ) -> Option<AnyElement> {
876 None
877 }
878
879 fn to_any(&self) -> &dyn std::any::Any;
880
881 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
882 None
883 }
884}
885
886struct ChangeLocation {
887 current: Option<Vec<Anchor>>,
888 original: Vec<Anchor>,
889}
890impl ChangeLocation {
891 fn locations(&self) -> &[Anchor] {
892 self.current.as_ref().unwrap_or(&self.original)
893 }
894}
895
896/// A set of caret positions, registered when the editor was edited.
897pub struct ChangeList {
898 changes: Vec<ChangeLocation>,
899 /// Currently "selected" change.
900 position: Option<usize>,
901}
902
903impl ChangeList {
904 pub fn new() -> Self {
905 Self {
906 changes: Vec::new(),
907 position: None,
908 }
909 }
910
911 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
912 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
913 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
914 if self.changes.is_empty() {
915 return None;
916 }
917
918 let prev = self.position.unwrap_or(self.changes.len());
919 let next = if direction == Direction::Prev {
920 prev.saturating_sub(count)
921 } else {
922 (prev + count).min(self.changes.len() - 1)
923 };
924 self.position = Some(next);
925 self.changes.get(next).map(|change| change.locations())
926 }
927
928 /// Adds a new change to the list, resetting the change list position.
929 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
930 self.position.take();
931 if let Some(last) = self.changes.last_mut()
932 && group
933 {
934 last.current = Some(new_positions)
935 } else {
936 self.changes.push(ChangeLocation {
937 original: new_positions,
938 current: None,
939 });
940 }
941 }
942
943 pub fn last(&self) -> Option<&[Anchor]> {
944 self.changes.last().map(|change| change.locations())
945 }
946
947 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
948 self.changes.last().map(|change| change.original.as_slice())
949 }
950
951 pub fn invert_last_group(&mut self) {
952 if let Some(last) = self.changes.last_mut()
953 && let Some(current) = last.current.as_mut()
954 {
955 mem::swap(&mut last.original, current);
956 }
957 }
958}
959
960#[derive(Clone)]
961struct InlineBlamePopoverState {
962 scroll_handle: ScrollHandle,
963 commit_message: Option<ParsedCommitMessage>,
964 markdown: Entity<Markdown>,
965}
966
967struct InlineBlamePopover {
968 position: gpui::Point<Pixels>,
969 hide_task: Option<Task<()>>,
970 popover_bounds: Option<Bounds<Pixels>>,
971 popover_state: InlineBlamePopoverState,
972 keyboard_grace: bool,
973}
974
975enum SelectionDragState {
976 /// State when no drag related activity is detected.
977 None,
978 /// State when the mouse is down on a selection that is about to be dragged.
979 ReadyToDrag {
980 selection: Selection<Anchor>,
981 click_position: gpui::Point<Pixels>,
982 mouse_down_time: Instant,
983 },
984 /// State when the mouse is dragging the selection in the editor.
985 Dragging {
986 selection: Selection<Anchor>,
987 drop_cursor: Selection<Anchor>,
988 hide_drop_cursor: bool,
989 },
990}
991
992enum ColumnarSelectionState {
993 FromMouse {
994 selection_tail: Anchor,
995 display_point: Option<DisplayPoint>,
996 },
997 FromSelection {
998 selection_tail: Anchor,
999 },
1000}
1001
1002/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1003/// a breakpoint on them.
1004#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1005struct PhantomBreakpointIndicator {
1006 display_row: DisplayRow,
1007 /// There's a small debounce between hovering over the line and showing the indicator.
1008 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1009 is_active: bool,
1010 collides_with_existing_breakpoint: bool,
1011}
1012
1013/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1014///
1015/// See the [module level documentation](self) for more information.
1016pub struct Editor {
1017 focus_handle: FocusHandle,
1018 last_focused_descendant: Option<WeakFocusHandle>,
1019 /// The text buffer being edited
1020 buffer: Entity<MultiBuffer>,
1021 /// Map of how text in the buffer should be displayed.
1022 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1023 pub display_map: Entity<DisplayMap>,
1024 placeholder_display_map: Option<Entity<DisplayMap>>,
1025 pub selections: SelectionsCollection,
1026 pub scroll_manager: ScrollManager,
1027 /// When inline assist editors are linked, they all render cursors because
1028 /// typing enters text into each of them, even the ones that aren't focused.
1029 pub(crate) show_cursor_when_unfocused: bool,
1030 columnar_selection_state: Option<ColumnarSelectionState>,
1031 add_selections_state: Option<AddSelectionsState>,
1032 select_next_state: Option<SelectNextState>,
1033 select_prev_state: Option<SelectNextState>,
1034 selection_history: SelectionHistory,
1035 defer_selection_effects: bool,
1036 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1037 autoclose_regions: Vec<AutocloseRegion>,
1038 snippet_stack: InvalidationStack<SnippetState>,
1039 select_syntax_node_history: SelectSyntaxNodeHistory,
1040 ime_transaction: Option<TransactionId>,
1041 pub diagnostics_max_severity: DiagnosticSeverity,
1042 active_diagnostics: ActiveDiagnostic,
1043 show_inline_diagnostics: bool,
1044 inline_diagnostics_update: Task<()>,
1045 inline_diagnostics_enabled: bool,
1046 diagnostics_enabled: bool,
1047 word_completions_enabled: bool,
1048 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1049 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1050 hard_wrap: Option<usize>,
1051 project: Option<Entity<Project>>,
1052 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1053 completion_provider: Option<Rc<dyn CompletionProvider>>,
1054 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1055 blink_manager: Entity<BlinkManager>,
1056 show_cursor_names: bool,
1057 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1058 pub show_local_selections: bool,
1059 mode: EditorMode,
1060 show_breadcrumbs: bool,
1061 show_gutter: bool,
1062 show_scrollbars: ScrollbarAxes,
1063 minimap_visibility: MinimapVisibility,
1064 offset_content: bool,
1065 disable_expand_excerpt_buttons: bool,
1066 show_line_numbers: Option<bool>,
1067 use_relative_line_numbers: Option<bool>,
1068 show_git_diff_gutter: Option<bool>,
1069 show_code_actions: Option<bool>,
1070 show_runnables: Option<bool>,
1071 show_breakpoints: Option<bool>,
1072 show_wrap_guides: Option<bool>,
1073 show_indent_guides: Option<bool>,
1074 highlight_order: usize,
1075 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1076 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1077 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1078 scrollbar_marker_state: ScrollbarMarkerState,
1079 active_indent_guides_state: ActiveIndentGuidesState,
1080 nav_history: Option<ItemNavHistory>,
1081 context_menu: RefCell<Option<CodeContextMenu>>,
1082 context_menu_options: Option<ContextMenuOptions>,
1083 mouse_context_menu: Option<MouseContextMenu>,
1084 completion_tasks: Vec<(CompletionId, Task<()>)>,
1085 inline_blame_popover: Option<InlineBlamePopover>,
1086 inline_blame_popover_show_task: Option<Task<()>>,
1087 signature_help_state: SignatureHelpState,
1088 auto_signature_help: Option<bool>,
1089 find_all_references_task_sources: Vec<Anchor>,
1090 next_completion_id: CompletionId,
1091 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1092 code_actions_task: Option<Task<Result<()>>>,
1093 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1094 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 document_highlights_task: Option<Task<()>>,
1096 linked_editing_range_task: Option<Task<Option<()>>>,
1097 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1098 pending_rename: Option<RenameState>,
1099 searchable: bool,
1100 cursor_shape: CursorShape,
1101 current_line_highlight: Option<CurrentLineHighlight>,
1102 collapse_matches: bool,
1103 autoindent_mode: Option<AutoindentMode>,
1104 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1105 input_enabled: bool,
1106 use_modal_editing: bool,
1107 read_only: bool,
1108 leader_id: Option<CollaboratorId>,
1109 remote_id: Option<ViewId>,
1110 pub hover_state: HoverState,
1111 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1112 gutter_hovered: bool,
1113 hovered_link_state: Option<HoveredLinkState>,
1114 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1115 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1116 active_edit_prediction: Option<EditPredictionState>,
1117 /// Used to prevent flickering as the user types while the menu is open
1118 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1119 edit_prediction_settings: EditPredictionSettings,
1120 edit_predictions_hidden_for_vim_mode: bool,
1121 show_edit_predictions_override: Option<bool>,
1122 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1123 edit_prediction_preview: EditPredictionPreview,
1124 edit_prediction_indent_conflict: bool,
1125 edit_prediction_requires_modifier_in_indent_conflict: bool,
1126 inlay_hint_cache: InlayHintCache,
1127 next_inlay_id: u32,
1128 next_color_inlay_id: u32,
1129 _subscriptions: Vec<Subscription>,
1130 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1131 gutter_dimensions: GutterDimensions,
1132 style: Option<EditorStyle>,
1133 text_style_refinement: Option<TextStyleRefinement>,
1134 next_editor_action_id: EditorActionId,
1135 editor_actions: Rc<
1136 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1137 >,
1138 use_autoclose: bool,
1139 use_auto_surround: bool,
1140 auto_replace_emoji_shortcode: bool,
1141 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1142 show_git_blame_gutter: bool,
1143 show_git_blame_inline: bool,
1144 show_git_blame_inline_delay_task: Option<Task<()>>,
1145 git_blame_inline_enabled: bool,
1146 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1147 serialize_dirty_buffers: bool,
1148 show_selection_menu: Option<bool>,
1149 blame: Option<Entity<GitBlame>>,
1150 blame_subscription: Option<Subscription>,
1151 custom_context_menu: Option<
1152 Box<
1153 dyn 'static
1154 + Fn(
1155 &mut Self,
1156 DisplayPoint,
1157 &mut Window,
1158 &mut Context<Self>,
1159 ) -> Option<Entity<ui::ContextMenu>>,
1160 >,
1161 >,
1162 last_bounds: Option<Bounds<Pixels>>,
1163 last_position_map: Option<Rc<PositionMap>>,
1164 expect_bounds_change: Option<Bounds<Pixels>>,
1165 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1166 tasks_update_task: Option<Task<()>>,
1167 breakpoint_store: Option<Entity<BreakpointStore>>,
1168 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1169 hovered_diff_hunk_row: Option<DisplayRow>,
1170 pull_diagnostics_task: Task<()>,
1171 in_project_search: bool,
1172 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1173 breadcrumb_header: Option<String>,
1174 focused_block: Option<FocusedBlock>,
1175 next_scroll_position: NextScrollCursorCenterTopBottom,
1176 addons: HashMap<TypeId, Box<dyn Addon>>,
1177 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1178 load_diff_task: Option<Shared<Task<()>>>,
1179 /// Whether we are temporarily displaying a diff other than git's
1180 temporary_diff_override: bool,
1181 selection_mark_mode: bool,
1182 toggle_fold_multiple_buffers: Task<()>,
1183 _scroll_cursor_center_top_bottom_task: Task<()>,
1184 serialize_selections: Task<()>,
1185 serialize_folds: Task<()>,
1186 mouse_cursor_hidden: bool,
1187 minimap: Option<Entity<Self>>,
1188 hide_mouse_mode: HideMouseMode,
1189 pub change_list: ChangeList,
1190 inline_value_cache: InlineValueCache,
1191 selection_drag_state: SelectionDragState,
1192 colors: Option<LspColorData>,
1193 refresh_colors_task: Task<()>,
1194 folding_newlines: Task<()>,
1195 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1196}
1197
1198#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1199enum NextScrollCursorCenterTopBottom {
1200 #[default]
1201 Center,
1202 Top,
1203 Bottom,
1204}
1205
1206impl NextScrollCursorCenterTopBottom {
1207 fn next(&self) -> Self {
1208 match self {
1209 Self::Center => Self::Top,
1210 Self::Top => Self::Bottom,
1211 Self::Bottom => Self::Center,
1212 }
1213 }
1214}
1215
1216#[derive(Clone)]
1217pub struct EditorSnapshot {
1218 pub mode: EditorMode,
1219 show_gutter: bool,
1220 show_line_numbers: Option<bool>,
1221 show_git_diff_gutter: Option<bool>,
1222 show_code_actions: Option<bool>,
1223 show_runnables: Option<bool>,
1224 show_breakpoints: Option<bool>,
1225 git_blame_gutter_max_author_length: Option<usize>,
1226 pub display_snapshot: DisplaySnapshot,
1227 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1228 is_focused: bool,
1229 scroll_anchor: ScrollAnchor,
1230 ongoing_scroll: OngoingScroll,
1231 current_line_highlight: CurrentLineHighlight,
1232 gutter_hovered: bool,
1233}
1234
1235#[derive(Default, Debug, Clone, Copy)]
1236pub struct GutterDimensions {
1237 pub left_padding: Pixels,
1238 pub right_padding: Pixels,
1239 pub width: Pixels,
1240 pub margin: Pixels,
1241 pub git_blame_entries_width: Option<Pixels>,
1242}
1243
1244impl GutterDimensions {
1245 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1246 Self {
1247 margin: Self::default_gutter_margin(font_id, font_size, cx),
1248 ..Default::default()
1249 }
1250 }
1251
1252 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1253 -cx.text_system().descent(font_id, font_size)
1254 }
1255 /// The full width of the space taken up by the gutter.
1256 pub fn full_width(&self) -> Pixels {
1257 self.margin + self.width
1258 }
1259
1260 /// The width of the space reserved for the fold indicators,
1261 /// use alongside 'justify_end' and `gutter_width` to
1262 /// right align content with the line numbers
1263 pub fn fold_area_width(&self) -> Pixels {
1264 self.margin + self.right_padding
1265 }
1266}
1267
1268struct CharacterDimensions {
1269 em_width: Pixels,
1270 em_advance: Pixels,
1271 line_height: Pixels,
1272}
1273
1274#[derive(Debug)]
1275pub struct RemoteSelection {
1276 pub replica_id: ReplicaId,
1277 pub selection: Selection<Anchor>,
1278 pub cursor_shape: CursorShape,
1279 pub collaborator_id: CollaboratorId,
1280 pub line_mode: bool,
1281 pub user_name: Option<SharedString>,
1282 pub color: PlayerColor,
1283}
1284
1285#[derive(Clone, Debug)]
1286struct SelectionHistoryEntry {
1287 selections: Arc<[Selection<Anchor>]>,
1288 select_next_state: Option<SelectNextState>,
1289 select_prev_state: Option<SelectNextState>,
1290 add_selections_state: Option<AddSelectionsState>,
1291}
1292
1293#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1294enum SelectionHistoryMode {
1295 Normal,
1296 Undoing,
1297 Redoing,
1298 Skipping,
1299}
1300
1301#[derive(Clone, PartialEq, Eq, Hash)]
1302struct HoveredCursor {
1303 replica_id: u16,
1304 selection_id: usize,
1305}
1306
1307impl Default for SelectionHistoryMode {
1308 fn default() -> Self {
1309 Self::Normal
1310 }
1311}
1312
1313#[derive(Debug)]
1314/// SelectionEffects controls the side-effects of updating the selection.
1315///
1316/// The default behaviour does "what you mostly want":
1317/// - it pushes to the nav history if the cursor moved by >10 lines
1318/// - it re-triggers completion requests
1319/// - it scrolls to fit
1320///
1321/// You might want to modify these behaviours. For example when doing a "jump"
1322/// like go to definition, we always want to add to nav history; but when scrolling
1323/// in vim mode we never do.
1324///
1325/// Similarly, you might want to disable scrolling if you don't want the viewport to
1326/// move.
1327#[derive(Clone)]
1328pub struct SelectionEffects {
1329 nav_history: Option<bool>,
1330 completions: bool,
1331 scroll: Option<Autoscroll>,
1332}
1333
1334impl Default for SelectionEffects {
1335 fn default() -> Self {
1336 Self {
1337 nav_history: None,
1338 completions: true,
1339 scroll: Some(Autoscroll::fit()),
1340 }
1341 }
1342}
1343impl SelectionEffects {
1344 pub fn scroll(scroll: Autoscroll) -> Self {
1345 Self {
1346 scroll: Some(scroll),
1347 ..Default::default()
1348 }
1349 }
1350
1351 pub fn no_scroll() -> Self {
1352 Self {
1353 scroll: None,
1354 ..Default::default()
1355 }
1356 }
1357
1358 pub fn completions(self, completions: bool) -> Self {
1359 Self {
1360 completions,
1361 ..self
1362 }
1363 }
1364
1365 pub fn nav_history(self, nav_history: bool) -> Self {
1366 Self {
1367 nav_history: Some(nav_history),
1368 ..self
1369 }
1370 }
1371}
1372
1373struct DeferredSelectionEffectsState {
1374 changed: bool,
1375 effects: SelectionEffects,
1376 old_cursor_position: Anchor,
1377 history_entry: SelectionHistoryEntry,
1378}
1379
1380#[derive(Default)]
1381struct SelectionHistory {
1382 #[allow(clippy::type_complexity)]
1383 selections_by_transaction:
1384 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1385 mode: SelectionHistoryMode,
1386 undo_stack: VecDeque<SelectionHistoryEntry>,
1387 redo_stack: VecDeque<SelectionHistoryEntry>,
1388}
1389
1390impl SelectionHistory {
1391 #[track_caller]
1392 fn insert_transaction(
1393 &mut self,
1394 transaction_id: TransactionId,
1395 selections: Arc<[Selection<Anchor>]>,
1396 ) {
1397 if selections.is_empty() {
1398 log::error!(
1399 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1400 std::panic::Location::caller()
1401 );
1402 return;
1403 }
1404 self.selections_by_transaction
1405 .insert(transaction_id, (selections, None));
1406 }
1407
1408 #[allow(clippy::type_complexity)]
1409 fn transaction(
1410 &self,
1411 transaction_id: TransactionId,
1412 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1413 self.selections_by_transaction.get(&transaction_id)
1414 }
1415
1416 #[allow(clippy::type_complexity)]
1417 fn transaction_mut(
1418 &mut self,
1419 transaction_id: TransactionId,
1420 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1421 self.selections_by_transaction.get_mut(&transaction_id)
1422 }
1423
1424 fn push(&mut self, entry: SelectionHistoryEntry) {
1425 if !entry.selections.is_empty() {
1426 match self.mode {
1427 SelectionHistoryMode::Normal => {
1428 self.push_undo(entry);
1429 self.redo_stack.clear();
1430 }
1431 SelectionHistoryMode::Undoing => self.push_redo(entry),
1432 SelectionHistoryMode::Redoing => self.push_undo(entry),
1433 SelectionHistoryMode::Skipping => {}
1434 }
1435 }
1436 }
1437
1438 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1439 if self
1440 .undo_stack
1441 .back()
1442 .is_none_or(|e| e.selections != entry.selections)
1443 {
1444 self.undo_stack.push_back(entry);
1445 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1446 self.undo_stack.pop_front();
1447 }
1448 }
1449 }
1450
1451 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1452 if self
1453 .redo_stack
1454 .back()
1455 .is_none_or(|e| e.selections != entry.selections)
1456 {
1457 self.redo_stack.push_back(entry);
1458 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1459 self.redo_stack.pop_front();
1460 }
1461 }
1462 }
1463}
1464
1465#[derive(Clone, Copy)]
1466pub struct RowHighlightOptions {
1467 pub autoscroll: bool,
1468 pub include_gutter: bool,
1469}
1470
1471impl Default for RowHighlightOptions {
1472 fn default() -> Self {
1473 Self {
1474 autoscroll: Default::default(),
1475 include_gutter: true,
1476 }
1477 }
1478}
1479
1480struct RowHighlight {
1481 index: usize,
1482 range: Range<Anchor>,
1483 color: Hsla,
1484 options: RowHighlightOptions,
1485 type_id: TypeId,
1486}
1487
1488#[derive(Clone, Debug)]
1489struct AddSelectionsState {
1490 groups: Vec<AddSelectionsGroup>,
1491}
1492
1493#[derive(Clone, Debug)]
1494struct AddSelectionsGroup {
1495 above: bool,
1496 stack: Vec<usize>,
1497}
1498
1499#[derive(Clone)]
1500struct SelectNextState {
1501 query: AhoCorasick,
1502 wordwise: bool,
1503 done: bool,
1504}
1505
1506impl std::fmt::Debug for SelectNextState {
1507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1508 f.debug_struct(std::any::type_name::<Self>())
1509 .field("wordwise", &self.wordwise)
1510 .field("done", &self.done)
1511 .finish()
1512 }
1513}
1514
1515#[derive(Debug)]
1516struct AutocloseRegion {
1517 selection_id: usize,
1518 range: Range<Anchor>,
1519 pair: BracketPair,
1520}
1521
1522#[derive(Debug)]
1523struct SnippetState {
1524 ranges: Vec<Vec<Range<Anchor>>>,
1525 active_index: usize,
1526 choices: Vec<Option<Vec<String>>>,
1527}
1528
1529#[doc(hidden)]
1530pub struct RenameState {
1531 pub range: Range<Anchor>,
1532 pub old_name: Arc<str>,
1533 pub editor: Entity<Editor>,
1534 block_id: CustomBlockId,
1535}
1536
1537struct InvalidationStack<T>(Vec<T>);
1538
1539struct RegisteredEditPredictionProvider {
1540 provider: Arc<dyn EditPredictionProviderHandle>,
1541 _subscription: Subscription,
1542}
1543
1544#[derive(Debug, PartialEq, Eq)]
1545pub struct ActiveDiagnosticGroup {
1546 pub active_range: Range<Anchor>,
1547 pub active_message: String,
1548 pub group_id: usize,
1549 pub blocks: HashSet<CustomBlockId>,
1550}
1551
1552#[derive(Debug, PartialEq, Eq)]
1553
1554pub(crate) enum ActiveDiagnostic {
1555 None,
1556 All,
1557 Group(ActiveDiagnosticGroup),
1558}
1559
1560#[derive(Serialize, Deserialize, Clone, Debug)]
1561pub struct ClipboardSelection {
1562 /// The number of bytes in this selection.
1563 pub len: usize,
1564 /// Whether this was a full-line selection.
1565 pub is_entire_line: bool,
1566 /// The indentation of the first line when this content was originally copied.
1567 pub first_line_indent: u32,
1568}
1569
1570// selections, scroll behavior, was newest selection reversed
1571type SelectSyntaxNodeHistoryState = (
1572 Box<[Selection<usize>]>,
1573 SelectSyntaxNodeScrollBehavior,
1574 bool,
1575);
1576
1577#[derive(Default)]
1578struct SelectSyntaxNodeHistory {
1579 stack: Vec<SelectSyntaxNodeHistoryState>,
1580 // disable temporarily to allow changing selections without losing the stack
1581 pub disable_clearing: bool,
1582}
1583
1584impl SelectSyntaxNodeHistory {
1585 pub fn try_clear(&mut self) {
1586 if !self.disable_clearing {
1587 self.stack.clear();
1588 }
1589 }
1590
1591 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1592 self.stack.push(selection);
1593 }
1594
1595 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1596 self.stack.pop()
1597 }
1598}
1599
1600enum SelectSyntaxNodeScrollBehavior {
1601 CursorTop,
1602 FitSelection,
1603 CursorBottom,
1604}
1605
1606#[derive(Debug)]
1607pub(crate) struct NavigationData {
1608 cursor_anchor: Anchor,
1609 cursor_position: Point,
1610 scroll_anchor: ScrollAnchor,
1611 scroll_top_row: u32,
1612}
1613
1614#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1615pub enum GotoDefinitionKind {
1616 Symbol,
1617 Declaration,
1618 Type,
1619 Implementation,
1620}
1621
1622#[derive(Debug, Clone)]
1623enum InlayHintRefreshReason {
1624 ModifiersChanged(bool),
1625 Toggle(bool),
1626 SettingsChange(InlayHintSettings),
1627 NewLinesShown,
1628 BufferEdited(HashSet<Arc<Language>>),
1629 RefreshRequested,
1630 ExcerptsRemoved(Vec<ExcerptId>),
1631}
1632
1633impl InlayHintRefreshReason {
1634 fn description(&self) -> &'static str {
1635 match self {
1636 Self::ModifiersChanged(_) => "modifiers changed",
1637 Self::Toggle(_) => "toggle",
1638 Self::SettingsChange(_) => "settings change",
1639 Self::NewLinesShown => "new lines shown",
1640 Self::BufferEdited(_) => "buffer edited",
1641 Self::RefreshRequested => "refresh requested",
1642 Self::ExcerptsRemoved(_) => "excerpts removed",
1643 }
1644 }
1645}
1646
1647pub enum FormatTarget {
1648 Buffers(HashSet<Entity<Buffer>>),
1649 Ranges(Vec<Range<MultiBufferPoint>>),
1650}
1651
1652pub(crate) struct FocusedBlock {
1653 id: BlockId,
1654 focus_handle: WeakFocusHandle,
1655}
1656
1657#[derive(Clone)]
1658enum JumpData {
1659 MultiBufferRow {
1660 row: MultiBufferRow,
1661 line_offset_from_top: u32,
1662 },
1663 MultiBufferPoint {
1664 excerpt_id: ExcerptId,
1665 position: Point,
1666 anchor: text::Anchor,
1667 line_offset_from_top: u32,
1668 },
1669}
1670
1671pub enum MultibufferSelectionMode {
1672 First,
1673 All,
1674}
1675
1676#[derive(Clone, Copy, Debug, Default)]
1677pub struct RewrapOptions {
1678 pub override_language_settings: bool,
1679 pub preserve_existing_whitespace: bool,
1680}
1681
1682impl Editor {
1683 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1684 let buffer = cx.new(|cx| Buffer::local("", cx));
1685 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1686 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1687 }
1688
1689 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1690 let buffer = cx.new(|cx| Buffer::local("", cx));
1691 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1692 Self::new(EditorMode::full(), buffer, None, window, cx)
1693 }
1694
1695 pub fn auto_height(
1696 min_lines: usize,
1697 max_lines: usize,
1698 window: &mut Window,
1699 cx: &mut Context<Self>,
1700 ) -> Self {
1701 let buffer = cx.new(|cx| Buffer::local("", cx));
1702 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1703 Self::new(
1704 EditorMode::AutoHeight {
1705 min_lines,
1706 max_lines: Some(max_lines),
1707 },
1708 buffer,
1709 None,
1710 window,
1711 cx,
1712 )
1713 }
1714
1715 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1716 /// The editor grows as tall as needed to fit its content.
1717 pub fn auto_height_unbounded(
1718 min_lines: usize,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 let buffer = cx.new(|cx| Buffer::local("", cx));
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(
1725 EditorMode::AutoHeight {
1726 min_lines,
1727 max_lines: None,
1728 },
1729 buffer,
1730 None,
1731 window,
1732 cx,
1733 )
1734 }
1735
1736 pub fn for_buffer(
1737 buffer: Entity<Buffer>,
1738 project: Option<Entity<Project>>,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1743 Self::new(EditorMode::full(), buffer, project, window, cx)
1744 }
1745
1746 pub fn for_multibuffer(
1747 buffer: Entity<MultiBuffer>,
1748 project: Option<Entity<Project>>,
1749 window: &mut Window,
1750 cx: &mut Context<Self>,
1751 ) -> Self {
1752 Self::new(EditorMode::full(), buffer, project, window, cx)
1753 }
1754
1755 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1756 let mut clone = Self::new(
1757 self.mode.clone(),
1758 self.buffer.clone(),
1759 self.project.clone(),
1760 window,
1761 cx,
1762 );
1763 self.display_map.update(cx, |display_map, cx| {
1764 let snapshot = display_map.snapshot(cx);
1765 clone.display_map.update(cx, |display_map, cx| {
1766 display_map.set_state(&snapshot, cx);
1767 });
1768 });
1769 clone.folds_did_change(cx);
1770 clone.selections.clone_state(&self.selections);
1771 clone.scroll_manager.clone_state(&self.scroll_manager);
1772 clone.searchable = self.searchable;
1773 clone.read_only = self.read_only;
1774 clone
1775 }
1776
1777 pub fn new(
1778 mode: EditorMode,
1779 buffer: Entity<MultiBuffer>,
1780 project: Option<Entity<Project>>,
1781 window: &mut Window,
1782 cx: &mut Context<Self>,
1783 ) -> Self {
1784 Editor::new_internal(mode, buffer, project, None, window, cx)
1785 }
1786
1787 fn new_internal(
1788 mode: EditorMode,
1789 buffer: Entity<MultiBuffer>,
1790 project: Option<Entity<Project>>,
1791 display_map: Option<Entity<DisplayMap>>,
1792 window: &mut Window,
1793 cx: &mut Context<Self>,
1794 ) -> Self {
1795 debug_assert!(
1796 display_map.is_none() || mode.is_minimap(),
1797 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1798 );
1799
1800 let full_mode = mode.is_full();
1801 let is_minimap = mode.is_minimap();
1802 let diagnostics_max_severity = if full_mode {
1803 EditorSettings::get_global(cx)
1804 .diagnostics_max_severity
1805 .unwrap_or(DiagnosticSeverity::Hint)
1806 } else {
1807 DiagnosticSeverity::Off
1808 };
1809 let style = window.text_style();
1810 let font_size = style.font_size.to_pixels(window.rem_size());
1811 let editor = cx.entity().downgrade();
1812 let fold_placeholder = FoldPlaceholder {
1813 constrain_width: false,
1814 render: Arc::new(move |fold_id, fold_range, cx| {
1815 let editor = editor.clone();
1816 div()
1817 .id(fold_id)
1818 .bg(cx.theme().colors().ghost_element_background)
1819 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1820 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1821 .rounded_xs()
1822 .size_full()
1823 .cursor_pointer()
1824 .child("⋯")
1825 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1826 .on_click(move |_, _window, cx| {
1827 editor
1828 .update(cx, |editor, cx| {
1829 editor.unfold_ranges(
1830 &[fold_range.start..fold_range.end],
1831 true,
1832 false,
1833 cx,
1834 );
1835 cx.stop_propagation();
1836 })
1837 .ok();
1838 })
1839 .into_any()
1840 }),
1841 merge_adjacent: true,
1842 ..FoldPlaceholder::default()
1843 };
1844 let display_map = display_map.unwrap_or_else(|| {
1845 cx.new(|cx| {
1846 DisplayMap::new(
1847 buffer.clone(),
1848 style.font(),
1849 font_size,
1850 None,
1851 FILE_HEADER_HEIGHT,
1852 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1853 fold_placeholder,
1854 diagnostics_max_severity,
1855 cx,
1856 )
1857 })
1858 });
1859
1860 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1861
1862 let blink_manager = cx.new(|cx| {
1863 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1864 if is_minimap {
1865 blink_manager.disable(cx);
1866 }
1867 blink_manager
1868 });
1869
1870 let soft_wrap_mode_override =
1871 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1872
1873 let mut project_subscriptions = Vec::new();
1874 if full_mode && let Some(project) = project.as_ref() {
1875 project_subscriptions.push(cx.subscribe_in(
1876 project,
1877 window,
1878 |editor, _, event, window, cx| match event {
1879 project::Event::RefreshCodeLens => {
1880 // we always query lens with actions, without storing them, always refreshing them
1881 }
1882 project::Event::RefreshInlayHints => {
1883 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1884 }
1885 project::Event::LanguageServerAdded(..)
1886 | project::Event::LanguageServerRemoved(..) => {
1887 if editor.tasks_update_task.is_none() {
1888 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1889 }
1890 }
1891 project::Event::SnippetEdit(id, snippet_edits) => {
1892 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1893 let focus_handle = editor.focus_handle(cx);
1894 if focus_handle.is_focused(window) {
1895 let snapshot = buffer.read(cx).snapshot();
1896 for (range, snippet) in snippet_edits {
1897 let editor_range =
1898 language::range_from_lsp(*range).to_offset(&snapshot);
1899 editor
1900 .insert_snippet(
1901 &[editor_range],
1902 snippet.clone(),
1903 window,
1904 cx,
1905 )
1906 .ok();
1907 }
1908 }
1909 }
1910 }
1911 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1912 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1913 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1914 }
1915 }
1916
1917 project::Event::EntryRenamed(transaction) => {
1918 let Some(workspace) = editor.workspace() else {
1919 return;
1920 };
1921 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1922 else {
1923 return;
1924 };
1925 if active_editor.entity_id() == cx.entity_id() {
1926 let edited_buffers_already_open = {
1927 let other_editors: Vec<Entity<Editor>> = workspace
1928 .read(cx)
1929 .panes()
1930 .iter()
1931 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1932 .filter(|editor| editor.entity_id() != cx.entity_id())
1933 .collect();
1934
1935 transaction.0.keys().all(|buffer| {
1936 other_editors.iter().any(|editor| {
1937 let multi_buffer = editor.read(cx).buffer();
1938 multi_buffer.read(cx).is_singleton()
1939 && multi_buffer.read(cx).as_singleton().map_or(
1940 false,
1941 |singleton| {
1942 singleton.entity_id() == buffer.entity_id()
1943 },
1944 )
1945 })
1946 })
1947 };
1948
1949 if !edited_buffers_already_open {
1950 let workspace = workspace.downgrade();
1951 let transaction = transaction.clone();
1952 cx.defer_in(window, move |_, window, cx| {
1953 cx.spawn_in(window, async move |editor, cx| {
1954 Self::open_project_transaction(
1955 &editor,
1956 workspace,
1957 transaction,
1958 "Rename".to_string(),
1959 cx,
1960 )
1961 .await
1962 .ok()
1963 })
1964 .detach();
1965 });
1966 }
1967 }
1968 }
1969
1970 _ => {}
1971 },
1972 ));
1973 if let Some(task_inventory) = project
1974 .read(cx)
1975 .task_store()
1976 .read(cx)
1977 .task_inventory()
1978 .cloned()
1979 {
1980 project_subscriptions.push(cx.observe_in(
1981 &task_inventory,
1982 window,
1983 |editor, _, window, cx| {
1984 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1985 },
1986 ));
1987 };
1988
1989 project_subscriptions.push(cx.subscribe_in(
1990 &project.read(cx).breakpoint_store(),
1991 window,
1992 |editor, _, event, window, cx| match event {
1993 BreakpointStoreEvent::ClearDebugLines => {
1994 editor.clear_row_highlights::<ActiveDebugLine>();
1995 editor.refresh_inline_values(cx);
1996 }
1997 BreakpointStoreEvent::SetDebugLine => {
1998 if editor.go_to_active_debug_line(window, cx) {
1999 cx.stop_propagation();
2000 }
2001
2002 editor.refresh_inline_values(cx);
2003 }
2004 _ => {}
2005 },
2006 ));
2007 let git_store = project.read(cx).git_store().clone();
2008 let project = project.clone();
2009 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2010 if let GitStoreEvent::RepositoryUpdated(
2011 _,
2012 RepositoryEvent::Updated {
2013 new_instance: true, ..
2014 },
2015 _,
2016 ) = event
2017 {
2018 this.load_diff_task = Some(
2019 update_uncommitted_diff_for_buffer(
2020 cx.entity(),
2021 &project,
2022 this.buffer.read(cx).all_buffers(),
2023 this.buffer.clone(),
2024 cx,
2025 )
2026 .shared(),
2027 );
2028 }
2029 }));
2030 }
2031
2032 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2033
2034 let inlay_hint_settings =
2035 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2036 let focus_handle = cx.focus_handle();
2037 if !is_minimap {
2038 cx.on_focus(&focus_handle, window, Self::handle_focus)
2039 .detach();
2040 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2041 .detach();
2042 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2043 .detach();
2044 cx.on_blur(&focus_handle, window, Self::handle_blur)
2045 .detach();
2046 cx.observe_pending_input(window, Self::observe_pending_input)
2047 .detach();
2048 }
2049
2050 let show_indent_guides =
2051 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2052 Some(false)
2053 } else {
2054 None
2055 };
2056
2057 let breakpoint_store = match (&mode, project.as_ref()) {
2058 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2059 _ => None,
2060 };
2061
2062 let mut code_action_providers = Vec::new();
2063 let mut load_uncommitted_diff = None;
2064 if let Some(project) = project.clone() {
2065 load_uncommitted_diff = Some(
2066 update_uncommitted_diff_for_buffer(
2067 cx.entity(),
2068 &project,
2069 buffer.read(cx).all_buffers(),
2070 buffer.clone(),
2071 cx,
2072 )
2073 .shared(),
2074 );
2075 code_action_providers.push(Rc::new(project) as Rc<_>);
2076 }
2077
2078 let mut editor = Self {
2079 focus_handle,
2080 show_cursor_when_unfocused: false,
2081 last_focused_descendant: None,
2082 buffer: buffer.clone(),
2083 display_map: display_map.clone(),
2084 placeholder_display_map: None,
2085 selections,
2086 scroll_manager: ScrollManager::new(cx),
2087 columnar_selection_state: None,
2088 add_selections_state: None,
2089 select_next_state: None,
2090 select_prev_state: None,
2091 selection_history: SelectionHistory::default(),
2092 defer_selection_effects: false,
2093 deferred_selection_effects_state: None,
2094 autoclose_regions: Vec::new(),
2095 snippet_stack: InvalidationStack::default(),
2096 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2097 ime_transaction: None,
2098 active_diagnostics: ActiveDiagnostic::None,
2099 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2100 inline_diagnostics_update: Task::ready(()),
2101 inline_diagnostics: Vec::new(),
2102 soft_wrap_mode_override,
2103 diagnostics_max_severity,
2104 hard_wrap: None,
2105 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2106 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2107 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2108 project,
2109 blink_manager: blink_manager.clone(),
2110 show_local_selections: true,
2111 show_scrollbars: ScrollbarAxes {
2112 horizontal: full_mode,
2113 vertical: full_mode,
2114 },
2115 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2116 offset_content: !matches!(mode, EditorMode::SingleLine),
2117 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2118 show_gutter: full_mode,
2119 show_line_numbers: (!full_mode).then_some(false),
2120 use_relative_line_numbers: None,
2121 disable_expand_excerpt_buttons: !full_mode,
2122 show_git_diff_gutter: None,
2123 show_code_actions: None,
2124 show_runnables: None,
2125 show_breakpoints: None,
2126 show_wrap_guides: None,
2127 show_indent_guides,
2128 highlight_order: 0,
2129 highlighted_rows: HashMap::default(),
2130 background_highlights: HashMap::default(),
2131 gutter_highlights: HashMap::default(),
2132 scrollbar_marker_state: ScrollbarMarkerState::default(),
2133 active_indent_guides_state: ActiveIndentGuidesState::default(),
2134 nav_history: None,
2135 context_menu: RefCell::new(None),
2136 context_menu_options: None,
2137 mouse_context_menu: None,
2138 completion_tasks: Vec::new(),
2139 inline_blame_popover: None,
2140 inline_blame_popover_show_task: None,
2141 signature_help_state: SignatureHelpState::default(),
2142 auto_signature_help: None,
2143 find_all_references_task_sources: Vec::new(),
2144 next_completion_id: 0,
2145 next_inlay_id: 0,
2146 code_action_providers,
2147 available_code_actions: None,
2148 code_actions_task: None,
2149 quick_selection_highlight_task: None,
2150 debounced_selection_highlight_task: None,
2151 document_highlights_task: None,
2152 linked_editing_range_task: None,
2153 pending_rename: None,
2154 searchable: !is_minimap,
2155 cursor_shape: EditorSettings::get_global(cx)
2156 .cursor_shape
2157 .unwrap_or_default(),
2158 current_line_highlight: None,
2159 autoindent_mode: Some(AutoindentMode::EachLine),
2160 collapse_matches: false,
2161 workspace: None,
2162 input_enabled: !is_minimap,
2163 use_modal_editing: full_mode,
2164 read_only: is_minimap,
2165 use_autoclose: true,
2166 use_auto_surround: true,
2167 auto_replace_emoji_shortcode: false,
2168 jsx_tag_auto_close_enabled_in_any_buffer: false,
2169 leader_id: None,
2170 remote_id: None,
2171 hover_state: HoverState::default(),
2172 pending_mouse_down: None,
2173 hovered_link_state: None,
2174 edit_prediction_provider: None,
2175 active_edit_prediction: None,
2176 stale_edit_prediction_in_menu: None,
2177 edit_prediction_preview: EditPredictionPreview::Inactive {
2178 released_too_fast: false,
2179 },
2180 inline_diagnostics_enabled: full_mode,
2181 diagnostics_enabled: full_mode,
2182 word_completions_enabled: full_mode,
2183 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2184 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2185 gutter_hovered: false,
2186 pixel_position_of_newest_cursor: None,
2187 last_bounds: None,
2188 last_position_map: None,
2189 expect_bounds_change: None,
2190 gutter_dimensions: GutterDimensions::default(),
2191 style: None,
2192 show_cursor_names: false,
2193 hovered_cursors: HashMap::default(),
2194 next_editor_action_id: EditorActionId::default(),
2195 editor_actions: Rc::default(),
2196 edit_predictions_hidden_for_vim_mode: false,
2197 show_edit_predictions_override: None,
2198 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2199 edit_prediction_settings: EditPredictionSettings::Disabled,
2200 edit_prediction_indent_conflict: false,
2201 edit_prediction_requires_modifier_in_indent_conflict: true,
2202 custom_context_menu: None,
2203 show_git_blame_gutter: false,
2204 show_git_blame_inline: false,
2205 show_selection_menu: None,
2206 show_git_blame_inline_delay_task: None,
2207 git_blame_inline_enabled: full_mode
2208 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2209 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2210 serialize_dirty_buffers: !is_minimap
2211 && ProjectSettings::get_global(cx)
2212 .session
2213 .restore_unsaved_buffers,
2214 blame: None,
2215 blame_subscription: None,
2216 tasks: BTreeMap::default(),
2217
2218 breakpoint_store,
2219 gutter_breakpoint_indicator: (None, None),
2220 hovered_diff_hunk_row: None,
2221 _subscriptions: (!is_minimap)
2222 .then(|| {
2223 vec![
2224 cx.observe(&buffer, Self::on_buffer_changed),
2225 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2226 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2227 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2228 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2229 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2230 cx.observe_window_activation(window, |editor, window, cx| {
2231 let active = window.is_window_active();
2232 editor.blink_manager.update(cx, |blink_manager, cx| {
2233 if active {
2234 blink_manager.enable(cx);
2235 } else {
2236 blink_manager.disable(cx);
2237 }
2238 });
2239 if active {
2240 editor.show_mouse_cursor(cx);
2241 }
2242 }),
2243 ]
2244 })
2245 .unwrap_or_default(),
2246 tasks_update_task: None,
2247 pull_diagnostics_task: Task::ready(()),
2248 colors: None,
2249 refresh_colors_task: Task::ready(()),
2250 next_color_inlay_id: 0,
2251 linked_edit_ranges: Default::default(),
2252 in_project_search: false,
2253 previous_search_ranges: None,
2254 breadcrumb_header: None,
2255 focused_block: None,
2256 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2257 addons: HashMap::default(),
2258 registered_buffers: HashMap::default(),
2259 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2260 selection_mark_mode: false,
2261 toggle_fold_multiple_buffers: Task::ready(()),
2262 serialize_selections: Task::ready(()),
2263 serialize_folds: Task::ready(()),
2264 text_style_refinement: None,
2265 load_diff_task: load_uncommitted_diff,
2266 temporary_diff_override: false,
2267 mouse_cursor_hidden: false,
2268 minimap: None,
2269 hide_mouse_mode: EditorSettings::get_global(cx)
2270 .hide_mouse
2271 .unwrap_or_default(),
2272 change_list: ChangeList::new(),
2273 mode,
2274 selection_drag_state: SelectionDragState::None,
2275 folding_newlines: Task::ready(()),
2276 lookup_key: None,
2277 };
2278
2279 if is_minimap {
2280 return editor;
2281 }
2282
2283 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2284 editor
2285 ._subscriptions
2286 .push(cx.observe(breakpoints, |_, _, cx| {
2287 cx.notify();
2288 }));
2289 }
2290 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2291 editor._subscriptions.extend(project_subscriptions);
2292
2293 editor._subscriptions.push(cx.subscribe_in(
2294 &cx.entity(),
2295 window,
2296 |editor, _, e: &EditorEvent, window, cx| match e {
2297 EditorEvent::ScrollPositionChanged { local, .. } => {
2298 if *local {
2299 let new_anchor = editor.scroll_manager.anchor();
2300 let snapshot = editor.snapshot(window, cx);
2301 editor.update_restoration_data(cx, move |data| {
2302 data.scroll_position = (
2303 new_anchor.top_row(snapshot.buffer_snapshot()),
2304 new_anchor.offset,
2305 );
2306 });
2307 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2308 editor.inline_blame_popover.take();
2309 }
2310 }
2311 EditorEvent::Edited { .. } => {
2312 if !vim_enabled(cx) {
2313 let (map, selections) = editor.selections.all_adjusted_display(cx);
2314 let pop_state = editor
2315 .change_list
2316 .last()
2317 .map(|previous| {
2318 previous.len() == selections.len()
2319 && previous.iter().enumerate().all(|(ix, p)| {
2320 p.to_display_point(&map).row()
2321 == selections[ix].head().row()
2322 })
2323 })
2324 .unwrap_or(false);
2325 let new_positions = selections
2326 .into_iter()
2327 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2328 .collect();
2329 editor
2330 .change_list
2331 .push_to_change_list(pop_state, new_positions);
2332 }
2333 }
2334 _ => (),
2335 },
2336 ));
2337
2338 if let Some(dap_store) = editor
2339 .project
2340 .as_ref()
2341 .map(|project| project.read(cx).dap_store())
2342 {
2343 let weak_editor = cx.weak_entity();
2344
2345 editor
2346 ._subscriptions
2347 .push(
2348 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2349 let session_entity = cx.entity();
2350 weak_editor
2351 .update(cx, |editor, cx| {
2352 editor._subscriptions.push(
2353 cx.subscribe(&session_entity, Self::on_debug_session_event),
2354 );
2355 })
2356 .ok();
2357 }),
2358 );
2359
2360 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2361 editor
2362 ._subscriptions
2363 .push(cx.subscribe(&session, Self::on_debug_session_event));
2364 }
2365 }
2366
2367 // skip adding the initial selection to selection history
2368 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2369 editor.end_selection(window, cx);
2370 editor.selection_history.mode = SelectionHistoryMode::Normal;
2371
2372 editor.scroll_manager.show_scrollbars(window, cx);
2373 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2374
2375 if full_mode {
2376 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2377 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2378
2379 if editor.git_blame_inline_enabled {
2380 editor.start_git_blame_inline(false, window, cx);
2381 }
2382
2383 editor.go_to_active_debug_line(window, cx);
2384
2385 if let Some(buffer) = buffer.read(cx).as_singleton()
2386 && let Some(project) = editor.project()
2387 {
2388 let handle = project.update(cx, |project, cx| {
2389 project.register_buffer_with_language_servers(&buffer, cx)
2390 });
2391 editor
2392 .registered_buffers
2393 .insert(buffer.read(cx).remote_id(), handle);
2394 }
2395
2396 editor.minimap =
2397 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2398 editor.colors = Some(LspColorData::new(cx));
2399 editor.update_lsp_data(false, None, window, cx);
2400 }
2401
2402 if editor.mode.is_full() {
2403 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2404 }
2405
2406 editor
2407 }
2408
2409 pub fn deploy_mouse_context_menu(
2410 &mut self,
2411 position: gpui::Point<Pixels>,
2412 context_menu: Entity<ContextMenu>,
2413 window: &mut Window,
2414 cx: &mut Context<Self>,
2415 ) {
2416 self.mouse_context_menu = Some(MouseContextMenu::new(
2417 self,
2418 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2419 context_menu,
2420 window,
2421 cx,
2422 ));
2423 }
2424
2425 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2426 self.mouse_context_menu
2427 .as_ref()
2428 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2429 }
2430
2431 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2432 if self
2433 .selections
2434 .pending_anchor()
2435 .is_some_and(|pending_selection| {
2436 let snapshot = self.buffer().read(cx).snapshot(cx);
2437 pending_selection.range().includes(range, &snapshot)
2438 })
2439 {
2440 return true;
2441 }
2442
2443 self.selections
2444 .disjoint_in_range::<usize>(range.clone(), cx)
2445 .into_iter()
2446 .any(|selection| {
2447 // This is needed to cover a corner case, if we just check for an existing
2448 // selection in the fold range, having a cursor at the start of the fold
2449 // marks it as selected. Non-empty selections don't cause this.
2450 let length = selection.end - selection.start;
2451 length > 0
2452 })
2453 }
2454
2455 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2456 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2457 }
2458
2459 fn key_context_internal(
2460 &self,
2461 has_active_edit_prediction: bool,
2462 window: &Window,
2463 cx: &App,
2464 ) -> KeyContext {
2465 let mut key_context = KeyContext::new_with_defaults();
2466 key_context.add("Editor");
2467 let mode = match self.mode {
2468 EditorMode::SingleLine => "single_line",
2469 EditorMode::AutoHeight { .. } => "auto_height",
2470 EditorMode::Minimap { .. } => "minimap",
2471 EditorMode::Full { .. } => "full",
2472 };
2473
2474 if EditorSettings::jupyter_enabled(cx) {
2475 key_context.add("jupyter");
2476 }
2477
2478 key_context.set("mode", mode);
2479 if self.pending_rename.is_some() {
2480 key_context.add("renaming");
2481 }
2482
2483 match self.context_menu.borrow().as_ref() {
2484 Some(CodeContextMenu::Completions(menu)) => {
2485 if menu.visible() {
2486 key_context.add("menu");
2487 key_context.add("showing_completions");
2488 }
2489 }
2490 Some(CodeContextMenu::CodeActions(menu)) => {
2491 if menu.visible() {
2492 key_context.add("menu");
2493 key_context.add("showing_code_actions")
2494 }
2495 }
2496 None => {}
2497 }
2498
2499 if self.signature_help_state.has_multiple_signatures() {
2500 key_context.add("showing_signature_help");
2501 }
2502
2503 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2504 if !self.focus_handle(cx).contains_focused(window, cx)
2505 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2506 {
2507 for addon in self.addons.values() {
2508 addon.extend_key_context(&mut key_context, cx)
2509 }
2510 }
2511
2512 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2513 if let Some(extension) = singleton_buffer
2514 .read(cx)
2515 .file()
2516 .and_then(|file| file.path().extension())
2517 {
2518 key_context.set("extension", extension.to_string());
2519 }
2520 } else {
2521 key_context.add("multibuffer");
2522 }
2523
2524 if has_active_edit_prediction {
2525 if self.edit_prediction_in_conflict() {
2526 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2527 } else {
2528 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2529 key_context.add("copilot_suggestion");
2530 }
2531 }
2532
2533 if self.selection_mark_mode {
2534 key_context.add("selection_mode");
2535 }
2536
2537 key_context
2538 }
2539
2540 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2541 self.last_bounds.as_ref()
2542 }
2543
2544 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2545 if self.mouse_cursor_hidden {
2546 self.mouse_cursor_hidden = false;
2547 cx.notify();
2548 }
2549 }
2550
2551 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2552 let hide_mouse_cursor = match origin {
2553 HideMouseCursorOrigin::TypingAction => {
2554 matches!(
2555 self.hide_mouse_mode,
2556 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2557 )
2558 }
2559 HideMouseCursorOrigin::MovementAction => {
2560 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2561 }
2562 };
2563 if self.mouse_cursor_hidden != hide_mouse_cursor {
2564 self.mouse_cursor_hidden = hide_mouse_cursor;
2565 cx.notify();
2566 }
2567 }
2568
2569 pub fn edit_prediction_in_conflict(&self) -> bool {
2570 if !self.show_edit_predictions_in_menu() {
2571 return false;
2572 }
2573
2574 let showing_completions = self
2575 .context_menu
2576 .borrow()
2577 .as_ref()
2578 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2579
2580 showing_completions
2581 || self.edit_prediction_requires_modifier()
2582 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2583 // bindings to insert tab characters.
2584 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2585 }
2586
2587 pub fn accept_edit_prediction_keybind(
2588 &self,
2589 accept_partial: bool,
2590 window: &Window,
2591 cx: &App,
2592 ) -> AcceptEditPredictionBinding {
2593 let key_context = self.key_context_internal(true, window, cx);
2594 let in_conflict = self.edit_prediction_in_conflict();
2595
2596 let bindings = if accept_partial {
2597 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2598 } else {
2599 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2600 };
2601
2602 // TODO: if the binding contains multiple keystrokes, display all of them, not
2603 // just the first one.
2604 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2605 !in_conflict
2606 || binding
2607 .keystrokes()
2608 .first()
2609 .is_some_and(|keystroke| keystroke.modifiers().modified())
2610 }))
2611 }
2612
2613 pub fn new_file(
2614 workspace: &mut Workspace,
2615 _: &workspace::NewFile,
2616 window: &mut Window,
2617 cx: &mut Context<Workspace>,
2618 ) {
2619 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2620 "Failed to create buffer",
2621 window,
2622 cx,
2623 |e, _, _| match e.error_code() {
2624 ErrorCode::RemoteUpgradeRequired => Some(format!(
2625 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2626 e.error_tag("required").unwrap_or("the latest version")
2627 )),
2628 _ => None,
2629 },
2630 );
2631 }
2632
2633 pub fn new_in_workspace(
2634 workspace: &mut Workspace,
2635 window: &mut Window,
2636 cx: &mut Context<Workspace>,
2637 ) -> Task<Result<Entity<Editor>>> {
2638 let project = workspace.project().clone();
2639 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2640
2641 cx.spawn_in(window, async move |workspace, cx| {
2642 let buffer = create.await?;
2643 workspace.update_in(cx, |workspace, window, cx| {
2644 let editor =
2645 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2646 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2647 editor
2648 })
2649 })
2650 }
2651
2652 fn new_file_vertical(
2653 workspace: &mut Workspace,
2654 _: &workspace::NewFileSplitVertical,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2659 }
2660
2661 fn new_file_horizontal(
2662 workspace: &mut Workspace,
2663 _: &workspace::NewFileSplitHorizontal,
2664 window: &mut Window,
2665 cx: &mut Context<Workspace>,
2666 ) {
2667 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2668 }
2669
2670 fn new_file_in_direction(
2671 workspace: &mut Workspace,
2672 direction: SplitDirection,
2673 window: &mut Window,
2674 cx: &mut Context<Workspace>,
2675 ) {
2676 let project = workspace.project().clone();
2677 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2678
2679 cx.spawn_in(window, async move |workspace, cx| {
2680 let buffer = create.await?;
2681 workspace.update_in(cx, move |workspace, window, cx| {
2682 workspace.split_item(
2683 direction,
2684 Box::new(
2685 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2686 ),
2687 window,
2688 cx,
2689 )
2690 })?;
2691 anyhow::Ok(())
2692 })
2693 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2694 match e.error_code() {
2695 ErrorCode::RemoteUpgradeRequired => Some(format!(
2696 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2697 e.error_tag("required").unwrap_or("the latest version")
2698 )),
2699 _ => None,
2700 }
2701 });
2702 }
2703
2704 pub fn leader_id(&self) -> Option<CollaboratorId> {
2705 self.leader_id
2706 }
2707
2708 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2709 &self.buffer
2710 }
2711
2712 pub fn project(&self) -> Option<&Entity<Project>> {
2713 self.project.as_ref()
2714 }
2715
2716 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2717 self.workspace.as_ref()?.0.upgrade()
2718 }
2719
2720 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2721 self.buffer().read(cx).title(cx)
2722 }
2723
2724 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2725 let git_blame_gutter_max_author_length = self
2726 .render_git_blame_gutter(cx)
2727 .then(|| {
2728 if let Some(blame) = self.blame.as_ref() {
2729 let max_author_length =
2730 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2731 Some(max_author_length)
2732 } else {
2733 None
2734 }
2735 })
2736 .flatten();
2737
2738 EditorSnapshot {
2739 mode: self.mode.clone(),
2740 show_gutter: self.show_gutter,
2741 show_line_numbers: self.show_line_numbers,
2742 show_git_diff_gutter: self.show_git_diff_gutter,
2743 show_code_actions: self.show_code_actions,
2744 show_runnables: self.show_runnables,
2745 show_breakpoints: self.show_breakpoints,
2746 git_blame_gutter_max_author_length,
2747 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2748 placeholder_display_snapshot: self
2749 .placeholder_display_map
2750 .as_ref()
2751 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2752 scroll_anchor: self.scroll_manager.anchor(),
2753 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2754 is_focused: self.focus_handle.is_focused(window),
2755 current_line_highlight: self
2756 .current_line_highlight
2757 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2758 gutter_hovered: self.gutter_hovered,
2759 }
2760 }
2761
2762 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2763 self.buffer.read(cx).language_at(point, cx)
2764 }
2765
2766 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2767 self.buffer.read(cx).read(cx).file_at(point).cloned()
2768 }
2769
2770 pub fn active_excerpt(
2771 &self,
2772 cx: &App,
2773 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2774 self.buffer
2775 .read(cx)
2776 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2777 }
2778
2779 pub fn mode(&self) -> &EditorMode {
2780 &self.mode
2781 }
2782
2783 pub fn set_mode(&mut self, mode: EditorMode) {
2784 self.mode = mode;
2785 }
2786
2787 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2788 self.collaboration_hub.as_deref()
2789 }
2790
2791 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2792 self.collaboration_hub = Some(hub);
2793 }
2794
2795 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2796 self.in_project_search = in_project_search;
2797 }
2798
2799 pub fn set_custom_context_menu(
2800 &mut self,
2801 f: impl 'static
2802 + Fn(
2803 &mut Self,
2804 DisplayPoint,
2805 &mut Window,
2806 &mut Context<Self>,
2807 ) -> Option<Entity<ui::ContextMenu>>,
2808 ) {
2809 self.custom_context_menu = Some(Box::new(f))
2810 }
2811
2812 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2813 self.completion_provider = provider;
2814 }
2815
2816 #[cfg(any(test, feature = "test-support"))]
2817 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2818 self.completion_provider.clone()
2819 }
2820
2821 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2822 self.semantics_provider.clone()
2823 }
2824
2825 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2826 self.semantics_provider = provider;
2827 }
2828
2829 pub fn set_edit_prediction_provider<T>(
2830 &mut self,
2831 provider: Option<Entity<T>>,
2832 window: &mut Window,
2833 cx: &mut Context<Self>,
2834 ) where
2835 T: EditPredictionProvider,
2836 {
2837 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2838 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2839 if this.focus_handle.is_focused(window) {
2840 this.update_visible_edit_prediction(window, cx);
2841 }
2842 }),
2843 provider: Arc::new(provider),
2844 });
2845 self.update_edit_prediction_settings(cx);
2846 self.refresh_edit_prediction(false, false, window, cx);
2847 }
2848
2849 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2850 self.placeholder_display_map
2851 .as_ref()
2852 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2853 }
2854
2855 pub fn set_placeholder_text(
2856 &mut self,
2857 placeholder_text: &str,
2858 window: &mut Window,
2859 cx: &mut Context<Self>,
2860 ) {
2861 let multibuffer = cx
2862 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2863
2864 let style = window.text_style();
2865
2866 self.placeholder_display_map = Some(cx.new(|cx| {
2867 DisplayMap::new(
2868 multibuffer,
2869 style.font(),
2870 style.font_size.to_pixels(window.rem_size()),
2871 None,
2872 FILE_HEADER_HEIGHT,
2873 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2874 Default::default(),
2875 DiagnosticSeverity::Off,
2876 cx,
2877 )
2878 }));
2879 cx.notify();
2880 }
2881
2882 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2883 self.cursor_shape = cursor_shape;
2884
2885 // Disrupt blink for immediate user feedback that the cursor shape has changed
2886 self.blink_manager.update(cx, BlinkManager::show_cursor);
2887
2888 cx.notify();
2889 }
2890
2891 pub fn set_current_line_highlight(
2892 &mut self,
2893 current_line_highlight: Option<CurrentLineHighlight>,
2894 ) {
2895 self.current_line_highlight = current_line_highlight;
2896 }
2897
2898 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2899 self.collapse_matches = collapse_matches;
2900 }
2901
2902 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2903 let buffers = self.buffer.read(cx).all_buffers();
2904 let Some(project) = self.project.as_ref() else {
2905 return;
2906 };
2907 project.update(cx, |project, cx| {
2908 for buffer in buffers {
2909 self.registered_buffers
2910 .entry(buffer.read(cx).remote_id())
2911 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2912 }
2913 })
2914 }
2915
2916 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2917 if self.collapse_matches {
2918 return range.start..range.start;
2919 }
2920 range.clone()
2921 }
2922
2923 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2924 if self.display_map.read(cx).clip_at_line_ends != clip {
2925 self.display_map
2926 .update(cx, |map, _| map.clip_at_line_ends = clip);
2927 }
2928 }
2929
2930 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2931 self.input_enabled = input_enabled;
2932 }
2933
2934 pub fn set_edit_predictions_hidden_for_vim_mode(
2935 &mut self,
2936 hidden: bool,
2937 window: &mut Window,
2938 cx: &mut Context<Self>,
2939 ) {
2940 if hidden != self.edit_predictions_hidden_for_vim_mode {
2941 self.edit_predictions_hidden_for_vim_mode = hidden;
2942 if hidden {
2943 self.update_visible_edit_prediction(window, cx);
2944 } else {
2945 self.refresh_edit_prediction(true, false, window, cx);
2946 }
2947 }
2948 }
2949
2950 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2951 self.menu_edit_predictions_policy = value;
2952 }
2953
2954 pub fn set_autoindent(&mut self, autoindent: bool) {
2955 if autoindent {
2956 self.autoindent_mode = Some(AutoindentMode::EachLine);
2957 } else {
2958 self.autoindent_mode = None;
2959 }
2960 }
2961
2962 pub fn read_only(&self, cx: &App) -> bool {
2963 self.read_only || self.buffer.read(cx).read_only()
2964 }
2965
2966 pub fn set_read_only(&mut self, read_only: bool) {
2967 self.read_only = read_only;
2968 }
2969
2970 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2971 self.use_autoclose = autoclose;
2972 }
2973
2974 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2975 self.use_auto_surround = auto_surround;
2976 }
2977
2978 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2979 self.auto_replace_emoji_shortcode = auto_replace;
2980 }
2981
2982 pub fn toggle_edit_predictions(
2983 &mut self,
2984 _: &ToggleEditPrediction,
2985 window: &mut Window,
2986 cx: &mut Context<Self>,
2987 ) {
2988 if self.show_edit_predictions_override.is_some() {
2989 self.set_show_edit_predictions(None, window, cx);
2990 } else {
2991 let show_edit_predictions = !self.edit_predictions_enabled();
2992 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2993 }
2994 }
2995
2996 pub fn set_show_edit_predictions(
2997 &mut self,
2998 show_edit_predictions: Option<bool>,
2999 window: &mut Window,
3000 cx: &mut Context<Self>,
3001 ) {
3002 self.show_edit_predictions_override = show_edit_predictions;
3003 self.update_edit_prediction_settings(cx);
3004
3005 if let Some(false) = show_edit_predictions {
3006 self.discard_edit_prediction(false, cx);
3007 } else {
3008 self.refresh_edit_prediction(false, true, window, cx);
3009 }
3010 }
3011
3012 fn edit_predictions_disabled_in_scope(
3013 &self,
3014 buffer: &Entity<Buffer>,
3015 buffer_position: language::Anchor,
3016 cx: &App,
3017 ) -> bool {
3018 let snapshot = buffer.read(cx).snapshot();
3019 let settings = snapshot.settings_at(buffer_position, cx);
3020
3021 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3022 return false;
3023 };
3024
3025 scope.override_name().is_some_and(|scope_name| {
3026 settings
3027 .edit_predictions_disabled_in
3028 .iter()
3029 .any(|s| s == scope_name)
3030 })
3031 }
3032
3033 pub fn set_use_modal_editing(&mut self, to: bool) {
3034 self.use_modal_editing = to;
3035 }
3036
3037 pub fn use_modal_editing(&self) -> bool {
3038 self.use_modal_editing
3039 }
3040
3041 fn selections_did_change(
3042 &mut self,
3043 local: bool,
3044 old_cursor_position: &Anchor,
3045 effects: SelectionEffects,
3046 window: &mut Window,
3047 cx: &mut Context<Self>,
3048 ) {
3049 window.invalidate_character_coordinates();
3050
3051 // Copy selections to primary selection buffer
3052 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3053 if local {
3054 let selections = self.selections.all::<usize>(cx);
3055 let buffer_handle = self.buffer.read(cx).read(cx);
3056
3057 let mut text = String::new();
3058 for (index, selection) in selections.iter().enumerate() {
3059 let text_for_selection = buffer_handle
3060 .text_for_range(selection.start..selection.end)
3061 .collect::<String>();
3062
3063 text.push_str(&text_for_selection);
3064 if index != selections.len() - 1 {
3065 text.push('\n');
3066 }
3067 }
3068
3069 if !text.is_empty() {
3070 cx.write_to_primary(ClipboardItem::new_string(text));
3071 }
3072 }
3073
3074 let selection_anchors = self.selections.disjoint_anchors_arc();
3075
3076 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3077 self.buffer.update(cx, |buffer, cx| {
3078 buffer.set_active_selections(
3079 &selection_anchors,
3080 self.selections.line_mode(),
3081 self.cursor_shape,
3082 cx,
3083 )
3084 });
3085 }
3086 let display_map = self
3087 .display_map
3088 .update(cx, |display_map, cx| display_map.snapshot(cx));
3089 let buffer = display_map.buffer_snapshot();
3090 if self.selections.count() == 1 {
3091 self.add_selections_state = None;
3092 }
3093 self.select_next_state = None;
3094 self.select_prev_state = None;
3095 self.select_syntax_node_history.try_clear();
3096 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3097 self.snippet_stack.invalidate(&selection_anchors, buffer);
3098 self.take_rename(false, window, cx);
3099
3100 let newest_selection = self.selections.newest_anchor();
3101 let new_cursor_position = newest_selection.head();
3102 let selection_start = newest_selection.start;
3103
3104 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3105 self.push_to_nav_history(
3106 *old_cursor_position,
3107 Some(new_cursor_position.to_point(buffer)),
3108 false,
3109 effects.nav_history == Some(true),
3110 cx,
3111 );
3112 }
3113
3114 if local {
3115 if let Some(buffer_id) = new_cursor_position.buffer_id
3116 && !self.registered_buffers.contains_key(&buffer_id)
3117 && let Some(project) = self.project.as_ref()
3118 {
3119 project.update(cx, |project, cx| {
3120 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3121 return;
3122 };
3123 self.registered_buffers.insert(
3124 buffer_id,
3125 project.register_buffer_with_language_servers(&buffer, cx),
3126 );
3127 })
3128 }
3129
3130 let mut context_menu = self.context_menu.borrow_mut();
3131 let completion_menu = match context_menu.as_ref() {
3132 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3133 Some(CodeContextMenu::CodeActions(_)) => {
3134 *context_menu = None;
3135 None
3136 }
3137 None => None,
3138 };
3139 let completion_position = completion_menu.map(|menu| menu.initial_position);
3140 drop(context_menu);
3141
3142 if effects.completions
3143 && let Some(completion_position) = completion_position
3144 {
3145 let start_offset = selection_start.to_offset(buffer);
3146 let position_matches = start_offset == completion_position.to_offset(buffer);
3147 let continue_showing = if position_matches {
3148 if self.snippet_stack.is_empty() {
3149 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3150 == Some(CharKind::Word)
3151 } else {
3152 // Snippet choices can be shown even when the cursor is in whitespace.
3153 // Dismissing the menu with actions like backspace is handled by
3154 // invalidation regions.
3155 true
3156 }
3157 } else {
3158 false
3159 };
3160
3161 if continue_showing {
3162 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3163 } else {
3164 self.hide_context_menu(window, cx);
3165 }
3166 }
3167
3168 hide_hover(self, cx);
3169
3170 if old_cursor_position.to_display_point(&display_map).row()
3171 != new_cursor_position.to_display_point(&display_map).row()
3172 {
3173 self.available_code_actions.take();
3174 }
3175 self.refresh_code_actions(window, cx);
3176 self.refresh_document_highlights(cx);
3177 self.refresh_selected_text_highlights(false, window, cx);
3178 refresh_matching_bracket_highlights(self, cx);
3179 self.update_visible_edit_prediction(window, cx);
3180 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3181 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3182 self.inline_blame_popover.take();
3183 if self.git_blame_inline_enabled {
3184 self.start_inline_blame_timer(window, cx);
3185 }
3186 }
3187
3188 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3189 cx.emit(EditorEvent::SelectionsChanged { local });
3190
3191 let selections = &self.selections.disjoint_anchors_arc();
3192 if selections.len() == 1 {
3193 cx.emit(SearchEvent::ActiveMatchChanged)
3194 }
3195 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3196 let inmemory_selections = selections
3197 .iter()
3198 .map(|s| {
3199 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3200 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3201 })
3202 .collect();
3203 self.update_restoration_data(cx, |data| {
3204 data.selections = inmemory_selections;
3205 });
3206
3207 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3208 && let Some(workspace_id) =
3209 self.workspace.as_ref().and_then(|workspace| workspace.1)
3210 {
3211 let snapshot = self.buffer().read(cx).snapshot(cx);
3212 let selections = selections.clone();
3213 let background_executor = cx.background_executor().clone();
3214 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3215 self.serialize_selections = cx.background_spawn(async move {
3216 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3217 let db_selections = selections
3218 .iter()
3219 .map(|selection| {
3220 (
3221 selection.start.to_offset(&snapshot),
3222 selection.end.to_offset(&snapshot),
3223 )
3224 })
3225 .collect();
3226
3227 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3228 .await
3229 .with_context(|| {
3230 format!(
3231 "persisting editor selections for editor {editor_id}, \
3232 workspace {workspace_id:?}"
3233 )
3234 })
3235 .log_err();
3236 });
3237 }
3238 }
3239
3240 cx.notify();
3241 }
3242
3243 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3244 use text::ToOffset as _;
3245 use text::ToPoint as _;
3246
3247 if self.mode.is_minimap()
3248 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3249 {
3250 return;
3251 }
3252
3253 if !self.buffer().read(cx).is_singleton() {
3254 return;
3255 }
3256
3257 let display_snapshot = self
3258 .display_map
3259 .update(cx, |display_map, cx| display_map.snapshot(cx));
3260 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3261 return;
3262 };
3263 let inmemory_folds = display_snapshot
3264 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3265 .map(|fold| {
3266 fold.range.start.text_anchor.to_point(&snapshot)
3267 ..fold.range.end.text_anchor.to_point(&snapshot)
3268 })
3269 .collect();
3270 self.update_restoration_data(cx, |data| {
3271 data.folds = inmemory_folds;
3272 });
3273
3274 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3275 return;
3276 };
3277 let background_executor = cx.background_executor().clone();
3278 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3279 let db_folds = display_snapshot
3280 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3281 .map(|fold| {
3282 (
3283 fold.range.start.text_anchor.to_offset(&snapshot),
3284 fold.range.end.text_anchor.to_offset(&snapshot),
3285 )
3286 })
3287 .collect();
3288 self.serialize_folds = cx.background_spawn(async move {
3289 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3290 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3291 .await
3292 .with_context(|| {
3293 format!(
3294 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3295 )
3296 })
3297 .log_err();
3298 });
3299 }
3300
3301 pub fn sync_selections(
3302 &mut self,
3303 other: Entity<Editor>,
3304 cx: &mut Context<Self>,
3305 ) -> gpui::Subscription {
3306 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3307 if !other_selections.is_empty() {
3308 self.selections.change_with(cx, |selections| {
3309 selections.select_anchors(other_selections);
3310 });
3311 }
3312
3313 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3314 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3315 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3316 if other_selections.is_empty() {
3317 return;
3318 }
3319 this.selections.change_with(cx, |selections| {
3320 selections.select_anchors(other_selections);
3321 });
3322 }
3323 });
3324
3325 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3326 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3327 let these_selections = this.selections.disjoint_anchors().to_vec();
3328 if these_selections.is_empty() {
3329 return;
3330 }
3331 other.update(cx, |other_editor, cx| {
3332 other_editor.selections.change_with(cx, |selections| {
3333 selections.select_anchors(these_selections);
3334 })
3335 });
3336 }
3337 });
3338
3339 Subscription::join(other_subscription, this_subscription)
3340 }
3341
3342 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3343 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3344 /// effects of selection change occur at the end of the transaction.
3345 pub fn change_selections<R>(
3346 &mut self,
3347 effects: SelectionEffects,
3348 window: &mut Window,
3349 cx: &mut Context<Self>,
3350 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3351 ) -> R {
3352 if let Some(state) = &mut self.deferred_selection_effects_state {
3353 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3354 state.effects.completions = effects.completions;
3355 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3356 let (changed, result) = self.selections.change_with(cx, change);
3357 state.changed |= changed;
3358 return result;
3359 }
3360 let mut state = DeferredSelectionEffectsState {
3361 changed: false,
3362 effects,
3363 old_cursor_position: self.selections.newest_anchor().head(),
3364 history_entry: SelectionHistoryEntry {
3365 selections: self.selections.disjoint_anchors_arc(),
3366 select_next_state: self.select_next_state.clone(),
3367 select_prev_state: self.select_prev_state.clone(),
3368 add_selections_state: self.add_selections_state.clone(),
3369 },
3370 };
3371 let (changed, result) = self.selections.change_with(cx, change);
3372 state.changed = state.changed || changed;
3373 if self.defer_selection_effects {
3374 self.deferred_selection_effects_state = Some(state);
3375 } else {
3376 self.apply_selection_effects(state, window, cx);
3377 }
3378 result
3379 }
3380
3381 /// Defers the effects of selection change, so that the effects of multiple calls to
3382 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3383 /// to selection history and the state of popovers based on selection position aren't
3384 /// erroneously updated.
3385 pub fn with_selection_effects_deferred<R>(
3386 &mut self,
3387 window: &mut Window,
3388 cx: &mut Context<Self>,
3389 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3390 ) -> R {
3391 let already_deferred = self.defer_selection_effects;
3392 self.defer_selection_effects = true;
3393 let result = update(self, window, cx);
3394 if !already_deferred {
3395 self.defer_selection_effects = false;
3396 if let Some(state) = self.deferred_selection_effects_state.take() {
3397 self.apply_selection_effects(state, window, cx);
3398 }
3399 }
3400 result
3401 }
3402
3403 fn apply_selection_effects(
3404 &mut self,
3405 state: DeferredSelectionEffectsState,
3406 window: &mut Window,
3407 cx: &mut Context<Self>,
3408 ) {
3409 if state.changed {
3410 self.selection_history.push(state.history_entry);
3411
3412 if let Some(autoscroll) = state.effects.scroll {
3413 self.request_autoscroll(autoscroll, cx);
3414 }
3415
3416 let old_cursor_position = &state.old_cursor_position;
3417
3418 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3419
3420 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3421 self.show_signature_help(&ShowSignatureHelp, window, cx);
3422 }
3423 }
3424 }
3425
3426 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3427 where
3428 I: IntoIterator<Item = (Range<S>, T)>,
3429 S: ToOffset,
3430 T: Into<Arc<str>>,
3431 {
3432 if self.read_only(cx) {
3433 return;
3434 }
3435
3436 self.buffer
3437 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3438 }
3439
3440 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3441 where
3442 I: IntoIterator<Item = (Range<S>, T)>,
3443 S: ToOffset,
3444 T: Into<Arc<str>>,
3445 {
3446 if self.read_only(cx) {
3447 return;
3448 }
3449
3450 self.buffer.update(cx, |buffer, cx| {
3451 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3452 });
3453 }
3454
3455 pub fn edit_with_block_indent<I, S, T>(
3456 &mut self,
3457 edits: I,
3458 original_indent_columns: Vec<Option<u32>>,
3459 cx: &mut Context<Self>,
3460 ) where
3461 I: IntoIterator<Item = (Range<S>, T)>,
3462 S: ToOffset,
3463 T: Into<Arc<str>>,
3464 {
3465 if self.read_only(cx) {
3466 return;
3467 }
3468
3469 self.buffer.update(cx, |buffer, cx| {
3470 buffer.edit(
3471 edits,
3472 Some(AutoindentMode::Block {
3473 original_indent_columns,
3474 }),
3475 cx,
3476 )
3477 });
3478 }
3479
3480 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3481 self.hide_context_menu(window, cx);
3482
3483 match phase {
3484 SelectPhase::Begin {
3485 position,
3486 add,
3487 click_count,
3488 } => self.begin_selection(position, add, click_count, window, cx),
3489 SelectPhase::BeginColumnar {
3490 position,
3491 goal_column,
3492 reset,
3493 mode,
3494 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3495 SelectPhase::Extend {
3496 position,
3497 click_count,
3498 } => self.extend_selection(position, click_count, window, cx),
3499 SelectPhase::Update {
3500 position,
3501 goal_column,
3502 scroll_delta,
3503 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3504 SelectPhase::End => self.end_selection(window, cx),
3505 }
3506 }
3507
3508 fn extend_selection(
3509 &mut self,
3510 position: DisplayPoint,
3511 click_count: usize,
3512 window: &mut Window,
3513 cx: &mut Context<Self>,
3514 ) {
3515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3516 let tail = self.selections.newest::<usize>(cx).tail();
3517 let click_count = click_count.max(match self.selections.select_mode() {
3518 SelectMode::Character => 1,
3519 SelectMode::Word(_) => 2,
3520 SelectMode::Line(_) => 3,
3521 SelectMode::All => 4,
3522 });
3523 self.begin_selection(position, false, click_count, window, cx);
3524
3525 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3526
3527 let current_selection = match self.selections.select_mode() {
3528 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3529 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3530 };
3531
3532 let mut pending_selection = self
3533 .selections
3534 .pending_anchor()
3535 .cloned()
3536 .expect("extend_selection not called with pending selection");
3537
3538 if pending_selection
3539 .start
3540 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3541 == Ordering::Greater
3542 {
3543 pending_selection.start = current_selection.start;
3544 }
3545 if pending_selection
3546 .end
3547 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3548 == Ordering::Less
3549 {
3550 pending_selection.end = current_selection.end;
3551 pending_selection.reversed = true;
3552 }
3553
3554 let mut pending_mode = self.selections.pending_mode().unwrap();
3555 match &mut pending_mode {
3556 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3557 _ => {}
3558 }
3559
3560 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3561 SelectionEffects::scroll(Autoscroll::fit())
3562 } else {
3563 SelectionEffects::no_scroll()
3564 };
3565
3566 self.change_selections(effects, window, cx, |s| {
3567 s.set_pending(pending_selection.clone(), pending_mode);
3568 s.set_is_extending(true);
3569 });
3570 }
3571
3572 fn begin_selection(
3573 &mut self,
3574 position: DisplayPoint,
3575 add: bool,
3576 click_count: usize,
3577 window: &mut Window,
3578 cx: &mut Context<Self>,
3579 ) {
3580 if !self.focus_handle.is_focused(window) {
3581 self.last_focused_descendant = None;
3582 window.focus(&self.focus_handle);
3583 }
3584
3585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3586 let buffer = display_map.buffer_snapshot();
3587 let position = display_map.clip_point(position, Bias::Left);
3588
3589 let start;
3590 let end;
3591 let mode;
3592 let mut auto_scroll;
3593 match click_count {
3594 1 => {
3595 start = buffer.anchor_before(position.to_point(&display_map));
3596 end = start;
3597 mode = SelectMode::Character;
3598 auto_scroll = true;
3599 }
3600 2 => {
3601 let position = display_map
3602 .clip_point(position, Bias::Left)
3603 .to_offset(&display_map, Bias::Left);
3604 let (range, _) = buffer.surrounding_word(position, None);
3605 start = buffer.anchor_before(range.start);
3606 end = buffer.anchor_before(range.end);
3607 mode = SelectMode::Word(start..end);
3608 auto_scroll = true;
3609 }
3610 3 => {
3611 let position = display_map
3612 .clip_point(position, Bias::Left)
3613 .to_point(&display_map);
3614 let line_start = display_map.prev_line_boundary(position).0;
3615 let next_line_start = buffer.clip_point(
3616 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3617 Bias::Left,
3618 );
3619 start = buffer.anchor_before(line_start);
3620 end = buffer.anchor_before(next_line_start);
3621 mode = SelectMode::Line(start..end);
3622 auto_scroll = true;
3623 }
3624 _ => {
3625 start = buffer.anchor_before(0);
3626 end = buffer.anchor_before(buffer.len());
3627 mode = SelectMode::All;
3628 auto_scroll = false;
3629 }
3630 }
3631 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3632
3633 let point_to_delete: Option<usize> = {
3634 let selected_points: Vec<Selection<Point>> =
3635 self.selections.disjoint_in_range(start..end, cx);
3636
3637 if !add || click_count > 1 {
3638 None
3639 } else if !selected_points.is_empty() {
3640 Some(selected_points[0].id)
3641 } else {
3642 let clicked_point_already_selected =
3643 self.selections.disjoint_anchors().iter().find(|selection| {
3644 selection.start.to_point(buffer) == start.to_point(buffer)
3645 || selection.end.to_point(buffer) == end.to_point(buffer)
3646 });
3647
3648 clicked_point_already_selected.map(|selection| selection.id)
3649 }
3650 };
3651
3652 let selections_count = self.selections.count();
3653 let effects = if auto_scroll {
3654 SelectionEffects::default()
3655 } else {
3656 SelectionEffects::no_scroll()
3657 };
3658
3659 self.change_selections(effects, window, cx, |s| {
3660 if let Some(point_to_delete) = point_to_delete {
3661 s.delete(point_to_delete);
3662
3663 if selections_count == 1 {
3664 s.set_pending_anchor_range(start..end, mode);
3665 }
3666 } else {
3667 if !add {
3668 s.clear_disjoint();
3669 }
3670
3671 s.set_pending_anchor_range(start..end, mode);
3672 }
3673 });
3674 }
3675
3676 fn begin_columnar_selection(
3677 &mut self,
3678 position: DisplayPoint,
3679 goal_column: u32,
3680 reset: bool,
3681 mode: ColumnarMode,
3682 window: &mut Window,
3683 cx: &mut Context<Self>,
3684 ) {
3685 if !self.focus_handle.is_focused(window) {
3686 self.last_focused_descendant = None;
3687 window.focus(&self.focus_handle);
3688 }
3689
3690 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3691
3692 if reset {
3693 let pointer_position = display_map
3694 .buffer_snapshot()
3695 .anchor_before(position.to_point(&display_map));
3696
3697 self.change_selections(
3698 SelectionEffects::scroll(Autoscroll::newest()),
3699 window,
3700 cx,
3701 |s| {
3702 s.clear_disjoint();
3703 s.set_pending_anchor_range(
3704 pointer_position..pointer_position,
3705 SelectMode::Character,
3706 );
3707 },
3708 );
3709 };
3710
3711 let tail = self.selections.newest::<Point>(cx).tail();
3712 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3713 self.columnar_selection_state = match mode {
3714 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3715 selection_tail: selection_anchor,
3716 display_point: if reset {
3717 if position.column() != goal_column {
3718 Some(DisplayPoint::new(position.row(), goal_column))
3719 } else {
3720 None
3721 }
3722 } else {
3723 None
3724 },
3725 }),
3726 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3727 selection_tail: selection_anchor,
3728 }),
3729 };
3730
3731 if !reset {
3732 self.select_columns(position, goal_column, &display_map, window, cx);
3733 }
3734 }
3735
3736 fn update_selection(
3737 &mut self,
3738 position: DisplayPoint,
3739 goal_column: u32,
3740 scroll_delta: gpui::Point<f32>,
3741 window: &mut Window,
3742 cx: &mut Context<Self>,
3743 ) {
3744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3745
3746 if self.columnar_selection_state.is_some() {
3747 self.select_columns(position, goal_column, &display_map, window, cx);
3748 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3749 let buffer = display_map.buffer_snapshot();
3750 let head;
3751 let tail;
3752 let mode = self.selections.pending_mode().unwrap();
3753 match &mode {
3754 SelectMode::Character => {
3755 head = position.to_point(&display_map);
3756 tail = pending.tail().to_point(buffer);
3757 }
3758 SelectMode::Word(original_range) => {
3759 let offset = display_map
3760 .clip_point(position, Bias::Left)
3761 .to_offset(&display_map, Bias::Left);
3762 let original_range = original_range.to_offset(buffer);
3763
3764 let head_offset = if buffer.is_inside_word(offset, None)
3765 || original_range.contains(&offset)
3766 {
3767 let (word_range, _) = buffer.surrounding_word(offset, None);
3768 if word_range.start < original_range.start {
3769 word_range.start
3770 } else {
3771 word_range.end
3772 }
3773 } else {
3774 offset
3775 };
3776
3777 head = head_offset.to_point(buffer);
3778 if head_offset <= original_range.start {
3779 tail = original_range.end.to_point(buffer);
3780 } else {
3781 tail = original_range.start.to_point(buffer);
3782 }
3783 }
3784 SelectMode::Line(original_range) => {
3785 let original_range = original_range.to_point(display_map.buffer_snapshot());
3786
3787 let position = display_map
3788 .clip_point(position, Bias::Left)
3789 .to_point(&display_map);
3790 let line_start = display_map.prev_line_boundary(position).0;
3791 let next_line_start = buffer.clip_point(
3792 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3793 Bias::Left,
3794 );
3795
3796 if line_start < original_range.start {
3797 head = line_start
3798 } else {
3799 head = next_line_start
3800 }
3801
3802 if head <= original_range.start {
3803 tail = original_range.end;
3804 } else {
3805 tail = original_range.start;
3806 }
3807 }
3808 SelectMode::All => {
3809 return;
3810 }
3811 };
3812
3813 if head < tail {
3814 pending.start = buffer.anchor_before(head);
3815 pending.end = buffer.anchor_before(tail);
3816 pending.reversed = true;
3817 } else {
3818 pending.start = buffer.anchor_before(tail);
3819 pending.end = buffer.anchor_before(head);
3820 pending.reversed = false;
3821 }
3822
3823 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3824 s.set_pending(pending.clone(), mode);
3825 });
3826 } else {
3827 log::error!("update_selection dispatched with no pending selection");
3828 return;
3829 }
3830
3831 self.apply_scroll_delta(scroll_delta, window, cx);
3832 cx.notify();
3833 }
3834
3835 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3836 self.columnar_selection_state.take();
3837 if let Some(pending_mode) = self.selections.pending_mode() {
3838 let selections = self.selections.all::<usize>(cx);
3839 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3840 s.select(selections);
3841 s.clear_pending();
3842 if s.is_extending() {
3843 s.set_is_extending(false);
3844 } else {
3845 s.set_select_mode(pending_mode);
3846 }
3847 });
3848 }
3849 }
3850
3851 fn select_columns(
3852 &mut self,
3853 head: DisplayPoint,
3854 goal_column: u32,
3855 display_map: &DisplaySnapshot,
3856 window: &mut Window,
3857 cx: &mut Context<Self>,
3858 ) {
3859 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3860 return;
3861 };
3862
3863 let tail = match columnar_state {
3864 ColumnarSelectionState::FromMouse {
3865 selection_tail,
3866 display_point,
3867 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3868 ColumnarSelectionState::FromSelection { selection_tail } => {
3869 selection_tail.to_display_point(display_map)
3870 }
3871 };
3872
3873 let start_row = cmp::min(tail.row(), head.row());
3874 let end_row = cmp::max(tail.row(), head.row());
3875 let start_column = cmp::min(tail.column(), goal_column);
3876 let end_column = cmp::max(tail.column(), goal_column);
3877 let reversed = start_column < tail.column();
3878
3879 let selection_ranges = (start_row.0..=end_row.0)
3880 .map(DisplayRow)
3881 .filter_map(|row| {
3882 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3883 || start_column <= display_map.line_len(row))
3884 && !display_map.is_block_line(row)
3885 {
3886 let start = display_map
3887 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3888 .to_point(display_map);
3889 let end = display_map
3890 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3891 .to_point(display_map);
3892 if reversed {
3893 Some(end..start)
3894 } else {
3895 Some(start..end)
3896 }
3897 } else {
3898 None
3899 }
3900 })
3901 .collect::<Vec<_>>();
3902
3903 let ranges = match columnar_state {
3904 ColumnarSelectionState::FromMouse { .. } => {
3905 let mut non_empty_ranges = selection_ranges
3906 .iter()
3907 .filter(|selection_range| selection_range.start != selection_range.end)
3908 .peekable();
3909 if non_empty_ranges.peek().is_some() {
3910 non_empty_ranges.cloned().collect()
3911 } else {
3912 selection_ranges
3913 }
3914 }
3915 _ => selection_ranges,
3916 };
3917
3918 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3919 s.select_ranges(ranges);
3920 });
3921 cx.notify();
3922 }
3923
3924 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3925 self.selections
3926 .all_adjusted(cx)
3927 .iter()
3928 .any(|selection| !selection.is_empty())
3929 }
3930
3931 pub fn has_pending_nonempty_selection(&self) -> bool {
3932 let pending_nonempty_selection = match self.selections.pending_anchor() {
3933 Some(Selection { start, end, .. }) => start != end,
3934 None => false,
3935 };
3936
3937 pending_nonempty_selection
3938 || (self.columnar_selection_state.is_some()
3939 && self.selections.disjoint_anchors().len() > 1)
3940 }
3941
3942 pub fn has_pending_selection(&self) -> bool {
3943 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3944 }
3945
3946 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3947 self.selection_mark_mode = false;
3948 self.selection_drag_state = SelectionDragState::None;
3949
3950 if self.clear_expanded_diff_hunks(cx) {
3951 cx.notify();
3952 return;
3953 }
3954 if self.dismiss_menus_and_popups(true, window, cx) {
3955 return;
3956 }
3957
3958 if self.mode.is_full()
3959 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3960 {
3961 return;
3962 }
3963
3964 cx.propagate();
3965 }
3966
3967 pub fn dismiss_menus_and_popups(
3968 &mut self,
3969 is_user_requested: bool,
3970 window: &mut Window,
3971 cx: &mut Context<Self>,
3972 ) -> bool {
3973 if self.take_rename(false, window, cx).is_some() {
3974 return true;
3975 }
3976
3977 if hide_hover(self, cx) {
3978 return true;
3979 }
3980
3981 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3982 return true;
3983 }
3984
3985 if self.hide_context_menu(window, cx).is_some() {
3986 return true;
3987 }
3988
3989 if self.mouse_context_menu.take().is_some() {
3990 return true;
3991 }
3992
3993 if is_user_requested && self.discard_edit_prediction(true, cx) {
3994 return true;
3995 }
3996
3997 if self.snippet_stack.pop().is_some() {
3998 return true;
3999 }
4000
4001 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4002 self.dismiss_diagnostics(cx);
4003 return true;
4004 }
4005
4006 false
4007 }
4008
4009 fn linked_editing_ranges_for(
4010 &self,
4011 selection: Range<text::Anchor>,
4012 cx: &App,
4013 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4014 if self.linked_edit_ranges.is_empty() {
4015 return None;
4016 }
4017 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4018 selection.end.buffer_id.and_then(|end_buffer_id| {
4019 if selection.start.buffer_id != Some(end_buffer_id) {
4020 return None;
4021 }
4022 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4023 let snapshot = buffer.read(cx).snapshot();
4024 self.linked_edit_ranges
4025 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4026 .map(|ranges| (ranges, snapshot, buffer))
4027 })?;
4028 use text::ToOffset as TO;
4029 // find offset from the start of current range to current cursor position
4030 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4031
4032 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4033 let start_difference = start_offset - start_byte_offset;
4034 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4035 let end_difference = end_offset - start_byte_offset;
4036 // Current range has associated linked ranges.
4037 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4038 for range in linked_ranges.iter() {
4039 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4040 let end_offset = start_offset + end_difference;
4041 let start_offset = start_offset + start_difference;
4042 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4043 continue;
4044 }
4045 if self.selections.disjoint_anchor_ranges().any(|s| {
4046 if s.start.buffer_id != selection.start.buffer_id
4047 || s.end.buffer_id != selection.end.buffer_id
4048 {
4049 return false;
4050 }
4051 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4052 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4053 }) {
4054 continue;
4055 }
4056 let start = buffer_snapshot.anchor_after(start_offset);
4057 let end = buffer_snapshot.anchor_after(end_offset);
4058 linked_edits
4059 .entry(buffer.clone())
4060 .or_default()
4061 .push(start..end);
4062 }
4063 Some(linked_edits)
4064 }
4065
4066 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4067 let text: Arc<str> = text.into();
4068
4069 if self.read_only(cx) {
4070 return;
4071 }
4072
4073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4074
4075 let selections = self.selections.all_adjusted(cx);
4076 let mut bracket_inserted = false;
4077 let mut edits = Vec::new();
4078 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4079 let mut new_selections = Vec::with_capacity(selections.len());
4080 let mut new_autoclose_regions = Vec::new();
4081 let snapshot = self.buffer.read(cx).read(cx);
4082 let mut clear_linked_edit_ranges = false;
4083
4084 for (selection, autoclose_region) in
4085 self.selections_with_autoclose_regions(selections, &snapshot)
4086 {
4087 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4088 // Determine if the inserted text matches the opening or closing
4089 // bracket of any of this language's bracket pairs.
4090 let mut bracket_pair = None;
4091 let mut is_bracket_pair_start = false;
4092 let mut is_bracket_pair_end = false;
4093 if !text.is_empty() {
4094 let mut bracket_pair_matching_end = None;
4095 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4096 // and they are removing the character that triggered IME popup.
4097 for (pair, enabled) in scope.brackets() {
4098 if !pair.close && !pair.surround {
4099 continue;
4100 }
4101
4102 if enabled && pair.start.ends_with(text.as_ref()) {
4103 let prefix_len = pair.start.len() - text.len();
4104 let preceding_text_matches_prefix = prefix_len == 0
4105 || (selection.start.column >= (prefix_len as u32)
4106 && snapshot.contains_str_at(
4107 Point::new(
4108 selection.start.row,
4109 selection.start.column - (prefix_len as u32),
4110 ),
4111 &pair.start[..prefix_len],
4112 ));
4113 if preceding_text_matches_prefix {
4114 bracket_pair = Some(pair.clone());
4115 is_bracket_pair_start = true;
4116 break;
4117 }
4118 }
4119 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4120 {
4121 // take first bracket pair matching end, but don't break in case a later bracket
4122 // pair matches start
4123 bracket_pair_matching_end = Some(pair.clone());
4124 }
4125 }
4126 if let Some(end) = bracket_pair_matching_end
4127 && bracket_pair.is_none()
4128 {
4129 bracket_pair = Some(end);
4130 is_bracket_pair_end = true;
4131 }
4132 }
4133
4134 if let Some(bracket_pair) = bracket_pair {
4135 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4136 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4137 let auto_surround =
4138 self.use_auto_surround && snapshot_settings.use_auto_surround;
4139 if selection.is_empty() {
4140 if is_bracket_pair_start {
4141 // If the inserted text is a suffix of an opening bracket and the
4142 // selection is preceded by the rest of the opening bracket, then
4143 // insert the closing bracket.
4144 let following_text_allows_autoclose = snapshot
4145 .chars_at(selection.start)
4146 .next()
4147 .is_none_or(|c| scope.should_autoclose_before(c));
4148
4149 let preceding_text_allows_autoclose = selection.start.column == 0
4150 || snapshot
4151 .reversed_chars_at(selection.start)
4152 .next()
4153 .is_none_or(|c| {
4154 bracket_pair.start != bracket_pair.end
4155 || !snapshot
4156 .char_classifier_at(selection.start)
4157 .is_word(c)
4158 });
4159
4160 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4161 && bracket_pair.start.len() == 1
4162 {
4163 let target = bracket_pair.start.chars().next().unwrap();
4164 let current_line_count = snapshot
4165 .reversed_chars_at(selection.start)
4166 .take_while(|&c| c != '\n')
4167 .filter(|&c| c == target)
4168 .count();
4169 current_line_count % 2 == 1
4170 } else {
4171 false
4172 };
4173
4174 if autoclose
4175 && bracket_pair.close
4176 && following_text_allows_autoclose
4177 && preceding_text_allows_autoclose
4178 && !is_closing_quote
4179 {
4180 let anchor = snapshot.anchor_before(selection.end);
4181 new_selections.push((selection.map(|_| anchor), text.len()));
4182 new_autoclose_regions.push((
4183 anchor,
4184 text.len(),
4185 selection.id,
4186 bracket_pair.clone(),
4187 ));
4188 edits.push((
4189 selection.range(),
4190 format!("{}{}", text, bracket_pair.end).into(),
4191 ));
4192 bracket_inserted = true;
4193 continue;
4194 }
4195 }
4196
4197 if let Some(region) = autoclose_region {
4198 // If the selection is followed by an auto-inserted closing bracket,
4199 // then don't insert that closing bracket again; just move the selection
4200 // past the closing bracket.
4201 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4202 && text.as_ref() == region.pair.end.as_str()
4203 && snapshot.contains_str_at(region.range.end, text.as_ref());
4204 if should_skip {
4205 let anchor = snapshot.anchor_after(selection.end);
4206 new_selections
4207 .push((selection.map(|_| anchor), region.pair.end.len()));
4208 continue;
4209 }
4210 }
4211
4212 let always_treat_brackets_as_autoclosed = snapshot
4213 .language_settings_at(selection.start, cx)
4214 .always_treat_brackets_as_autoclosed;
4215 if always_treat_brackets_as_autoclosed
4216 && is_bracket_pair_end
4217 && snapshot.contains_str_at(selection.end, text.as_ref())
4218 {
4219 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4220 // and the inserted text is a closing bracket and the selection is followed
4221 // by the closing bracket then move the selection past the closing bracket.
4222 let anchor = snapshot.anchor_after(selection.end);
4223 new_selections.push((selection.map(|_| anchor), text.len()));
4224 continue;
4225 }
4226 }
4227 // If an opening bracket is 1 character long and is typed while
4228 // text is selected, then surround that text with the bracket pair.
4229 else if auto_surround
4230 && bracket_pair.surround
4231 && is_bracket_pair_start
4232 && bracket_pair.start.chars().count() == 1
4233 {
4234 edits.push((selection.start..selection.start, text.clone()));
4235 edits.push((
4236 selection.end..selection.end,
4237 bracket_pair.end.as_str().into(),
4238 ));
4239 bracket_inserted = true;
4240 new_selections.push((
4241 Selection {
4242 id: selection.id,
4243 start: snapshot.anchor_after(selection.start),
4244 end: snapshot.anchor_before(selection.end),
4245 reversed: selection.reversed,
4246 goal: selection.goal,
4247 },
4248 0,
4249 ));
4250 continue;
4251 }
4252 }
4253 }
4254
4255 if self.auto_replace_emoji_shortcode
4256 && selection.is_empty()
4257 && text.as_ref().ends_with(':')
4258 && let Some(possible_emoji_short_code) =
4259 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4260 && !possible_emoji_short_code.is_empty()
4261 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4262 {
4263 let emoji_shortcode_start = Point::new(
4264 selection.start.row,
4265 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4266 );
4267
4268 // Remove shortcode from buffer
4269 edits.push((
4270 emoji_shortcode_start..selection.start,
4271 "".to_string().into(),
4272 ));
4273 new_selections.push((
4274 Selection {
4275 id: selection.id,
4276 start: snapshot.anchor_after(emoji_shortcode_start),
4277 end: snapshot.anchor_before(selection.start),
4278 reversed: selection.reversed,
4279 goal: selection.goal,
4280 },
4281 0,
4282 ));
4283
4284 // Insert emoji
4285 let selection_start_anchor = snapshot.anchor_after(selection.start);
4286 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4287 edits.push((selection.start..selection.end, emoji.to_string().into()));
4288
4289 continue;
4290 }
4291
4292 // If not handling any auto-close operation, then just replace the selected
4293 // text with the given input and move the selection to the end of the
4294 // newly inserted text.
4295 let anchor = snapshot.anchor_after(selection.end);
4296 if !self.linked_edit_ranges.is_empty() {
4297 let start_anchor = snapshot.anchor_before(selection.start);
4298
4299 let is_word_char = text.chars().next().is_none_or(|char| {
4300 let classifier = snapshot
4301 .char_classifier_at(start_anchor.to_offset(&snapshot))
4302 .scope_context(Some(CharScopeContext::LinkedEdit));
4303 classifier.is_word(char)
4304 });
4305
4306 if is_word_char {
4307 if let Some(ranges) = self
4308 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4309 {
4310 for (buffer, edits) in ranges {
4311 linked_edits
4312 .entry(buffer.clone())
4313 .or_default()
4314 .extend(edits.into_iter().map(|range| (range, text.clone())));
4315 }
4316 }
4317 } else {
4318 clear_linked_edit_ranges = true;
4319 }
4320 }
4321
4322 new_selections.push((selection.map(|_| anchor), 0));
4323 edits.push((selection.start..selection.end, text.clone()));
4324 }
4325
4326 drop(snapshot);
4327
4328 self.transact(window, cx, |this, window, cx| {
4329 if clear_linked_edit_ranges {
4330 this.linked_edit_ranges.clear();
4331 }
4332 let initial_buffer_versions =
4333 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4334
4335 this.buffer.update(cx, |buffer, cx| {
4336 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4337 });
4338 for (buffer, edits) in linked_edits {
4339 buffer.update(cx, |buffer, cx| {
4340 let snapshot = buffer.snapshot();
4341 let edits = edits
4342 .into_iter()
4343 .map(|(range, text)| {
4344 use text::ToPoint as TP;
4345 let end_point = TP::to_point(&range.end, &snapshot);
4346 let start_point = TP::to_point(&range.start, &snapshot);
4347 (start_point..end_point, text)
4348 })
4349 .sorted_by_key(|(range, _)| range.start);
4350 buffer.edit(edits, None, cx);
4351 })
4352 }
4353 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4354 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4355 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4356 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4357 .zip(new_selection_deltas)
4358 .map(|(selection, delta)| Selection {
4359 id: selection.id,
4360 start: selection.start + delta,
4361 end: selection.end + delta,
4362 reversed: selection.reversed,
4363 goal: SelectionGoal::None,
4364 })
4365 .collect::<Vec<_>>();
4366
4367 let mut i = 0;
4368 for (position, delta, selection_id, pair) in new_autoclose_regions {
4369 let position = position.to_offset(map.buffer_snapshot()) + delta;
4370 let start = map.buffer_snapshot().anchor_before(position);
4371 let end = map.buffer_snapshot().anchor_after(position);
4372 while let Some(existing_state) = this.autoclose_regions.get(i) {
4373 match existing_state
4374 .range
4375 .start
4376 .cmp(&start, map.buffer_snapshot())
4377 {
4378 Ordering::Less => i += 1,
4379 Ordering::Greater => break,
4380 Ordering::Equal => {
4381 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4382 Ordering::Less => i += 1,
4383 Ordering::Equal => break,
4384 Ordering::Greater => break,
4385 }
4386 }
4387 }
4388 }
4389 this.autoclose_regions.insert(
4390 i,
4391 AutocloseRegion {
4392 selection_id,
4393 range: start..end,
4394 pair,
4395 },
4396 );
4397 }
4398
4399 let had_active_edit_prediction = this.has_active_edit_prediction();
4400 this.change_selections(
4401 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4402 window,
4403 cx,
4404 |s| s.select(new_selections),
4405 );
4406
4407 if !bracket_inserted
4408 && let Some(on_type_format_task) =
4409 this.trigger_on_type_formatting(text.to_string(), window, cx)
4410 {
4411 on_type_format_task.detach_and_log_err(cx);
4412 }
4413
4414 let editor_settings = EditorSettings::get_global(cx);
4415 if bracket_inserted
4416 && (editor_settings.auto_signature_help
4417 || editor_settings.show_signature_help_after_edits)
4418 {
4419 this.show_signature_help(&ShowSignatureHelp, window, cx);
4420 }
4421
4422 let trigger_in_words =
4423 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4424 if this.hard_wrap.is_some() {
4425 let latest: Range<Point> = this.selections.newest(cx).range();
4426 if latest.is_empty()
4427 && this
4428 .buffer()
4429 .read(cx)
4430 .snapshot(cx)
4431 .line_len(MultiBufferRow(latest.start.row))
4432 == latest.start.column
4433 {
4434 this.rewrap_impl(
4435 RewrapOptions {
4436 override_language_settings: true,
4437 preserve_existing_whitespace: true,
4438 },
4439 cx,
4440 )
4441 }
4442 }
4443 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4444 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4445 this.refresh_edit_prediction(true, false, window, cx);
4446 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4447 });
4448 }
4449
4450 fn find_possible_emoji_shortcode_at_position(
4451 snapshot: &MultiBufferSnapshot,
4452 position: Point,
4453 ) -> Option<String> {
4454 let mut chars = Vec::new();
4455 let mut found_colon = false;
4456 for char in snapshot.reversed_chars_at(position).take(100) {
4457 // Found a possible emoji shortcode in the middle of the buffer
4458 if found_colon {
4459 if char.is_whitespace() {
4460 chars.reverse();
4461 return Some(chars.iter().collect());
4462 }
4463 // If the previous character is not a whitespace, we are in the middle of a word
4464 // and we only want to complete the shortcode if the word is made up of other emojis
4465 let mut containing_word = String::new();
4466 for ch in snapshot
4467 .reversed_chars_at(position)
4468 .skip(chars.len() + 1)
4469 .take(100)
4470 {
4471 if ch.is_whitespace() {
4472 break;
4473 }
4474 containing_word.push(ch);
4475 }
4476 let containing_word = containing_word.chars().rev().collect::<String>();
4477 if util::word_consists_of_emojis(containing_word.as_str()) {
4478 chars.reverse();
4479 return Some(chars.iter().collect());
4480 }
4481 }
4482
4483 if char.is_whitespace() || !char.is_ascii() {
4484 return None;
4485 }
4486 if char == ':' {
4487 found_colon = true;
4488 } else {
4489 chars.push(char);
4490 }
4491 }
4492 // Found a possible emoji shortcode at the beginning of the buffer
4493 chars.reverse();
4494 Some(chars.iter().collect())
4495 }
4496
4497 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4498 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4499 self.transact(window, cx, |this, window, cx| {
4500 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4501 let selections = this.selections.all::<usize>(cx);
4502 let multi_buffer = this.buffer.read(cx);
4503 let buffer = multi_buffer.snapshot(cx);
4504 selections
4505 .iter()
4506 .map(|selection| {
4507 let start_point = selection.start.to_point(&buffer);
4508 let mut existing_indent =
4509 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4510 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4511 let start = selection.start;
4512 let end = selection.end;
4513 let selection_is_empty = start == end;
4514 let language_scope = buffer.language_scope_at(start);
4515 let (
4516 comment_delimiter,
4517 doc_delimiter,
4518 insert_extra_newline,
4519 indent_on_newline,
4520 indent_on_extra_newline,
4521 ) = if let Some(language) = &language_scope {
4522 let mut insert_extra_newline =
4523 insert_extra_newline_brackets(&buffer, start..end, language)
4524 || insert_extra_newline_tree_sitter(&buffer, start..end);
4525
4526 // Comment extension on newline is allowed only for cursor selections
4527 let comment_delimiter = maybe!({
4528 if !selection_is_empty {
4529 return None;
4530 }
4531
4532 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4533 return None;
4534 }
4535
4536 let delimiters = language.line_comment_prefixes();
4537 let max_len_of_delimiter =
4538 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4539 let (snapshot, range) =
4540 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4541
4542 let num_of_whitespaces = snapshot
4543 .chars_for_range(range.clone())
4544 .take_while(|c| c.is_whitespace())
4545 .count();
4546 let comment_candidate = snapshot
4547 .chars_for_range(range.clone())
4548 .skip(num_of_whitespaces)
4549 .take(max_len_of_delimiter)
4550 .collect::<String>();
4551 let (delimiter, trimmed_len) = delimiters
4552 .iter()
4553 .filter_map(|delimiter| {
4554 let prefix = delimiter.trim_end();
4555 if comment_candidate.starts_with(prefix) {
4556 Some((delimiter, prefix.len()))
4557 } else {
4558 None
4559 }
4560 })
4561 .max_by_key(|(_, len)| *len)?;
4562
4563 if let Some(BlockCommentConfig {
4564 start: block_start, ..
4565 }) = language.block_comment()
4566 {
4567 let block_start_trimmed = block_start.trim_end();
4568 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4569 let line_content = snapshot
4570 .chars_for_range(range)
4571 .skip(num_of_whitespaces)
4572 .take(block_start_trimmed.len())
4573 .collect::<String>();
4574
4575 if line_content.starts_with(block_start_trimmed) {
4576 return None;
4577 }
4578 }
4579 }
4580
4581 let cursor_is_placed_after_comment_marker =
4582 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4583 if cursor_is_placed_after_comment_marker {
4584 Some(delimiter.clone())
4585 } else {
4586 None
4587 }
4588 });
4589
4590 let mut indent_on_newline = IndentSize::spaces(0);
4591 let mut indent_on_extra_newline = IndentSize::spaces(0);
4592
4593 let doc_delimiter = maybe!({
4594 if !selection_is_empty {
4595 return None;
4596 }
4597
4598 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4599 return None;
4600 }
4601
4602 let BlockCommentConfig {
4603 start: start_tag,
4604 end: end_tag,
4605 prefix: delimiter,
4606 tab_size: len,
4607 } = language.documentation_comment()?;
4608 let is_within_block_comment = buffer
4609 .language_scope_at(start_point)
4610 .is_some_and(|scope| scope.override_name() == Some("comment"));
4611 if !is_within_block_comment {
4612 return None;
4613 }
4614
4615 let (snapshot, range) =
4616 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4617
4618 let num_of_whitespaces = snapshot
4619 .chars_for_range(range.clone())
4620 .take_while(|c| c.is_whitespace())
4621 .count();
4622
4623 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4624 let column = start_point.column;
4625 let cursor_is_after_start_tag = {
4626 let start_tag_len = start_tag.len();
4627 let start_tag_line = snapshot
4628 .chars_for_range(range.clone())
4629 .skip(num_of_whitespaces)
4630 .take(start_tag_len)
4631 .collect::<String>();
4632 if start_tag_line.starts_with(start_tag.as_ref()) {
4633 num_of_whitespaces + start_tag_len <= column as usize
4634 } else {
4635 false
4636 }
4637 };
4638
4639 let cursor_is_after_delimiter = {
4640 let delimiter_trim = delimiter.trim_end();
4641 let delimiter_line = snapshot
4642 .chars_for_range(range.clone())
4643 .skip(num_of_whitespaces)
4644 .take(delimiter_trim.len())
4645 .collect::<String>();
4646 if delimiter_line.starts_with(delimiter_trim) {
4647 num_of_whitespaces + delimiter_trim.len() <= column as usize
4648 } else {
4649 false
4650 }
4651 };
4652
4653 let cursor_is_before_end_tag_if_exists = {
4654 let mut char_position = 0u32;
4655 let mut end_tag_offset = None;
4656
4657 'outer: for chunk in snapshot.text_for_range(range) {
4658 if let Some(byte_pos) = chunk.find(&**end_tag) {
4659 let chars_before_match =
4660 chunk[..byte_pos].chars().count() as u32;
4661 end_tag_offset =
4662 Some(char_position + chars_before_match);
4663 break 'outer;
4664 }
4665 char_position += chunk.chars().count() as u32;
4666 }
4667
4668 if let Some(end_tag_offset) = end_tag_offset {
4669 let cursor_is_before_end_tag = column <= end_tag_offset;
4670 if cursor_is_after_start_tag {
4671 if cursor_is_before_end_tag {
4672 insert_extra_newline = true;
4673 }
4674 let cursor_is_at_start_of_end_tag =
4675 column == end_tag_offset;
4676 if cursor_is_at_start_of_end_tag {
4677 indent_on_extra_newline.len = *len;
4678 }
4679 }
4680 cursor_is_before_end_tag
4681 } else {
4682 true
4683 }
4684 };
4685
4686 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4687 && cursor_is_before_end_tag_if_exists
4688 {
4689 if cursor_is_after_start_tag {
4690 indent_on_newline.len = *len;
4691 }
4692 Some(delimiter.clone())
4693 } else {
4694 None
4695 }
4696 });
4697
4698 (
4699 comment_delimiter,
4700 doc_delimiter,
4701 insert_extra_newline,
4702 indent_on_newline,
4703 indent_on_extra_newline,
4704 )
4705 } else {
4706 (
4707 None,
4708 None,
4709 false,
4710 IndentSize::default(),
4711 IndentSize::default(),
4712 )
4713 };
4714
4715 let prevent_auto_indent = doc_delimiter.is_some();
4716 let delimiter = comment_delimiter.or(doc_delimiter);
4717
4718 let capacity_for_delimiter =
4719 delimiter.as_deref().map(str::len).unwrap_or_default();
4720 let mut new_text = String::with_capacity(
4721 1 + capacity_for_delimiter
4722 + existing_indent.len as usize
4723 + indent_on_newline.len as usize
4724 + indent_on_extra_newline.len as usize,
4725 );
4726 new_text.push('\n');
4727 new_text.extend(existing_indent.chars());
4728 new_text.extend(indent_on_newline.chars());
4729
4730 if let Some(delimiter) = &delimiter {
4731 new_text.push_str(delimiter);
4732 }
4733
4734 if insert_extra_newline {
4735 new_text.push('\n');
4736 new_text.extend(existing_indent.chars());
4737 new_text.extend(indent_on_extra_newline.chars());
4738 }
4739
4740 let anchor = buffer.anchor_after(end);
4741 let new_selection = selection.map(|_| anchor);
4742 (
4743 ((start..end, new_text), prevent_auto_indent),
4744 (insert_extra_newline, new_selection),
4745 )
4746 })
4747 .unzip()
4748 };
4749
4750 let mut auto_indent_edits = Vec::new();
4751 let mut edits = Vec::new();
4752 for (edit, prevent_auto_indent) in edits_with_flags {
4753 if prevent_auto_indent {
4754 edits.push(edit);
4755 } else {
4756 auto_indent_edits.push(edit);
4757 }
4758 }
4759 if !edits.is_empty() {
4760 this.edit(edits, cx);
4761 }
4762 if !auto_indent_edits.is_empty() {
4763 this.edit_with_autoindent(auto_indent_edits, cx);
4764 }
4765
4766 let buffer = this.buffer.read(cx).snapshot(cx);
4767 let new_selections = selection_info
4768 .into_iter()
4769 .map(|(extra_newline_inserted, new_selection)| {
4770 let mut cursor = new_selection.end.to_point(&buffer);
4771 if extra_newline_inserted {
4772 cursor.row -= 1;
4773 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4774 }
4775 new_selection.map(|_| cursor)
4776 })
4777 .collect();
4778
4779 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4780 this.refresh_edit_prediction(true, false, window, cx);
4781 });
4782 }
4783
4784 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4786
4787 let buffer = self.buffer.read(cx);
4788 let snapshot = buffer.snapshot(cx);
4789
4790 let mut edits = Vec::new();
4791 let mut rows = Vec::new();
4792
4793 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4794 let cursor = selection.head();
4795 let row = cursor.row;
4796
4797 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4798
4799 let newline = "\n".to_string();
4800 edits.push((start_of_line..start_of_line, newline));
4801
4802 rows.push(row + rows_inserted as u32);
4803 }
4804
4805 self.transact(window, cx, |editor, window, cx| {
4806 editor.edit(edits, cx);
4807
4808 editor.change_selections(Default::default(), window, cx, |s| {
4809 let mut index = 0;
4810 s.move_cursors_with(|map, _, _| {
4811 let row = rows[index];
4812 index += 1;
4813
4814 let point = Point::new(row, 0);
4815 let boundary = map.next_line_boundary(point).1;
4816 let clipped = map.clip_point(boundary, Bias::Left);
4817
4818 (clipped, SelectionGoal::None)
4819 });
4820 });
4821
4822 let mut indent_edits = Vec::new();
4823 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4824 for row in rows {
4825 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4826 for (row, indent) in indents {
4827 if indent.len == 0 {
4828 continue;
4829 }
4830
4831 let text = match indent.kind {
4832 IndentKind::Space => " ".repeat(indent.len as usize),
4833 IndentKind::Tab => "\t".repeat(indent.len as usize),
4834 };
4835 let point = Point::new(row.0, 0);
4836 indent_edits.push((point..point, text));
4837 }
4838 }
4839 editor.edit(indent_edits, cx);
4840 });
4841 }
4842
4843 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4845
4846 let buffer = self.buffer.read(cx);
4847 let snapshot = buffer.snapshot(cx);
4848
4849 let mut edits = Vec::new();
4850 let mut rows = Vec::new();
4851 let mut rows_inserted = 0;
4852
4853 for selection in self.selections.all_adjusted(cx) {
4854 let cursor = selection.head();
4855 let row = cursor.row;
4856
4857 let point = Point::new(row + 1, 0);
4858 let start_of_line = snapshot.clip_point(point, Bias::Left);
4859
4860 let newline = "\n".to_string();
4861 edits.push((start_of_line..start_of_line, newline));
4862
4863 rows_inserted += 1;
4864 rows.push(row + rows_inserted);
4865 }
4866
4867 self.transact(window, cx, |editor, window, cx| {
4868 editor.edit(edits, cx);
4869
4870 editor.change_selections(Default::default(), window, cx, |s| {
4871 let mut index = 0;
4872 s.move_cursors_with(|map, _, _| {
4873 let row = rows[index];
4874 index += 1;
4875
4876 let point = Point::new(row, 0);
4877 let boundary = map.next_line_boundary(point).1;
4878 let clipped = map.clip_point(boundary, Bias::Left);
4879
4880 (clipped, SelectionGoal::None)
4881 });
4882 });
4883
4884 let mut indent_edits = Vec::new();
4885 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4886 for row in rows {
4887 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4888 for (row, indent) in indents {
4889 if indent.len == 0 {
4890 continue;
4891 }
4892
4893 let text = match indent.kind {
4894 IndentKind::Space => " ".repeat(indent.len as usize),
4895 IndentKind::Tab => "\t".repeat(indent.len as usize),
4896 };
4897 let point = Point::new(row.0, 0);
4898 indent_edits.push((point..point, text));
4899 }
4900 }
4901 editor.edit(indent_edits, cx);
4902 });
4903 }
4904
4905 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4906 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4907 original_indent_columns: Vec::new(),
4908 });
4909 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4910 }
4911
4912 fn insert_with_autoindent_mode(
4913 &mut self,
4914 text: &str,
4915 autoindent_mode: Option<AutoindentMode>,
4916 window: &mut Window,
4917 cx: &mut Context<Self>,
4918 ) {
4919 if self.read_only(cx) {
4920 return;
4921 }
4922
4923 let text: Arc<str> = text.into();
4924 self.transact(window, cx, |this, window, cx| {
4925 let old_selections = this.selections.all_adjusted(cx);
4926 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4927 let anchors = {
4928 let snapshot = buffer.read(cx);
4929 old_selections
4930 .iter()
4931 .map(|s| {
4932 let anchor = snapshot.anchor_after(s.head());
4933 s.map(|_| anchor)
4934 })
4935 .collect::<Vec<_>>()
4936 };
4937 buffer.edit(
4938 old_selections
4939 .iter()
4940 .map(|s| (s.start..s.end, text.clone())),
4941 autoindent_mode,
4942 cx,
4943 );
4944 anchors
4945 });
4946
4947 this.change_selections(Default::default(), window, cx, |s| {
4948 s.select_anchors(selection_anchors);
4949 });
4950
4951 cx.notify();
4952 });
4953 }
4954
4955 fn trigger_completion_on_input(
4956 &mut self,
4957 text: &str,
4958 trigger_in_words: bool,
4959 window: &mut Window,
4960 cx: &mut Context<Self>,
4961 ) {
4962 let completions_source = self
4963 .context_menu
4964 .borrow()
4965 .as_ref()
4966 .and_then(|menu| match menu {
4967 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4968 CodeContextMenu::CodeActions(_) => None,
4969 });
4970
4971 match completions_source {
4972 Some(CompletionsMenuSource::Words { .. }) => {
4973 self.open_or_update_completions_menu(
4974 Some(CompletionsMenuSource::Words {
4975 ignore_threshold: false,
4976 }),
4977 None,
4978 window,
4979 cx,
4980 );
4981 }
4982 Some(CompletionsMenuSource::Normal)
4983 | Some(CompletionsMenuSource::SnippetChoices)
4984 | None
4985 if self.is_completion_trigger(
4986 text,
4987 trigger_in_words,
4988 completions_source.is_some(),
4989 cx,
4990 ) =>
4991 {
4992 self.show_completions(
4993 &ShowCompletions {
4994 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4995 },
4996 window,
4997 cx,
4998 )
4999 }
5000 _ => {
5001 self.hide_context_menu(window, cx);
5002 }
5003 }
5004 }
5005
5006 fn is_completion_trigger(
5007 &self,
5008 text: &str,
5009 trigger_in_words: bool,
5010 menu_is_open: bool,
5011 cx: &mut Context<Self>,
5012 ) -> bool {
5013 let position = self.selections.newest_anchor().head();
5014 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5015 return false;
5016 };
5017
5018 if let Some(completion_provider) = &self.completion_provider {
5019 completion_provider.is_completion_trigger(
5020 &buffer,
5021 position.text_anchor,
5022 text,
5023 trigger_in_words,
5024 menu_is_open,
5025 cx,
5026 )
5027 } else {
5028 false
5029 }
5030 }
5031
5032 /// If any empty selections is touching the start of its innermost containing autoclose
5033 /// region, expand it to select the brackets.
5034 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5035 let selections = self.selections.all::<usize>(cx);
5036 let buffer = self.buffer.read(cx).read(cx);
5037 let new_selections = self
5038 .selections_with_autoclose_regions(selections, &buffer)
5039 .map(|(mut selection, region)| {
5040 if !selection.is_empty() {
5041 return selection;
5042 }
5043
5044 if let Some(region) = region {
5045 let mut range = region.range.to_offset(&buffer);
5046 if selection.start == range.start && range.start >= region.pair.start.len() {
5047 range.start -= region.pair.start.len();
5048 if buffer.contains_str_at(range.start, ®ion.pair.start)
5049 && buffer.contains_str_at(range.end, ®ion.pair.end)
5050 {
5051 range.end += region.pair.end.len();
5052 selection.start = range.start;
5053 selection.end = range.end;
5054
5055 return selection;
5056 }
5057 }
5058 }
5059
5060 let always_treat_brackets_as_autoclosed = buffer
5061 .language_settings_at(selection.start, cx)
5062 .always_treat_brackets_as_autoclosed;
5063
5064 if !always_treat_brackets_as_autoclosed {
5065 return selection;
5066 }
5067
5068 if let Some(scope) = buffer.language_scope_at(selection.start) {
5069 for (pair, enabled) in scope.brackets() {
5070 if !enabled || !pair.close {
5071 continue;
5072 }
5073
5074 if buffer.contains_str_at(selection.start, &pair.end) {
5075 let pair_start_len = pair.start.len();
5076 if buffer.contains_str_at(
5077 selection.start.saturating_sub(pair_start_len),
5078 &pair.start,
5079 ) {
5080 selection.start -= pair_start_len;
5081 selection.end += pair.end.len();
5082
5083 return selection;
5084 }
5085 }
5086 }
5087 }
5088
5089 selection
5090 })
5091 .collect();
5092
5093 drop(buffer);
5094 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5095 selections.select(new_selections)
5096 });
5097 }
5098
5099 /// Iterate the given selections, and for each one, find the smallest surrounding
5100 /// autoclose region. This uses the ordering of the selections and the autoclose
5101 /// regions to avoid repeated comparisons.
5102 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5103 &'a self,
5104 selections: impl IntoIterator<Item = Selection<D>>,
5105 buffer: &'a MultiBufferSnapshot,
5106 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5107 let mut i = 0;
5108 let mut regions = self.autoclose_regions.as_slice();
5109 selections.into_iter().map(move |selection| {
5110 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5111
5112 let mut enclosing = None;
5113 while let Some(pair_state) = regions.get(i) {
5114 if pair_state.range.end.to_offset(buffer) < range.start {
5115 regions = ®ions[i + 1..];
5116 i = 0;
5117 } else if pair_state.range.start.to_offset(buffer) > range.end {
5118 break;
5119 } else {
5120 if pair_state.selection_id == selection.id {
5121 enclosing = Some(pair_state);
5122 }
5123 i += 1;
5124 }
5125 }
5126
5127 (selection, enclosing)
5128 })
5129 }
5130
5131 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5132 fn invalidate_autoclose_regions(
5133 &mut self,
5134 mut selections: &[Selection<Anchor>],
5135 buffer: &MultiBufferSnapshot,
5136 ) {
5137 self.autoclose_regions.retain(|state| {
5138 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5139 return false;
5140 }
5141
5142 let mut i = 0;
5143 while let Some(selection) = selections.get(i) {
5144 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5145 selections = &selections[1..];
5146 continue;
5147 }
5148 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5149 break;
5150 }
5151 if selection.id == state.selection_id {
5152 return true;
5153 } else {
5154 i += 1;
5155 }
5156 }
5157 false
5158 });
5159 }
5160
5161 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5162 let offset = position.to_offset(buffer);
5163 let (word_range, kind) =
5164 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5165 if offset > word_range.start && kind == Some(CharKind::Word) {
5166 Some(
5167 buffer
5168 .text_for_range(word_range.start..offset)
5169 .collect::<String>(),
5170 )
5171 } else {
5172 None
5173 }
5174 }
5175
5176 pub fn toggle_inline_values(
5177 &mut self,
5178 _: &ToggleInlineValues,
5179 _: &mut Window,
5180 cx: &mut Context<Self>,
5181 ) {
5182 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5183
5184 self.refresh_inline_values(cx);
5185 }
5186
5187 pub fn toggle_inlay_hints(
5188 &mut self,
5189 _: &ToggleInlayHints,
5190 _: &mut Window,
5191 cx: &mut Context<Self>,
5192 ) {
5193 self.refresh_inlay_hints(
5194 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5195 cx,
5196 );
5197 }
5198
5199 pub fn inlay_hints_enabled(&self) -> bool {
5200 self.inlay_hint_cache.enabled
5201 }
5202
5203 pub fn inline_values_enabled(&self) -> bool {
5204 self.inline_value_cache.enabled
5205 }
5206
5207 #[cfg(any(test, feature = "test-support"))]
5208 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5209 self.display_map
5210 .read(cx)
5211 .current_inlays()
5212 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5213 .cloned()
5214 .collect()
5215 }
5216
5217 #[cfg(any(test, feature = "test-support"))]
5218 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5219 self.display_map
5220 .read(cx)
5221 .current_inlays()
5222 .cloned()
5223 .collect()
5224 }
5225
5226 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5227 if self.semantics_provider.is_none() || !self.mode.is_full() {
5228 return;
5229 }
5230
5231 let reason_description = reason.description();
5232 let ignore_debounce = matches!(
5233 reason,
5234 InlayHintRefreshReason::SettingsChange(_)
5235 | InlayHintRefreshReason::Toggle(_)
5236 | InlayHintRefreshReason::ExcerptsRemoved(_)
5237 | InlayHintRefreshReason::ModifiersChanged(_)
5238 );
5239 let (invalidate_cache, required_languages) = match reason {
5240 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5241 match self.inlay_hint_cache.modifiers_override(enabled) {
5242 Some(enabled) => {
5243 if enabled {
5244 (InvalidationStrategy::RefreshRequested, None)
5245 } else {
5246 self.clear_inlay_hints(cx);
5247 return;
5248 }
5249 }
5250 None => return,
5251 }
5252 }
5253 InlayHintRefreshReason::Toggle(enabled) => {
5254 if self.inlay_hint_cache.toggle(enabled) {
5255 if enabled {
5256 (InvalidationStrategy::RefreshRequested, None)
5257 } else {
5258 self.clear_inlay_hints(cx);
5259 return;
5260 }
5261 } else {
5262 return;
5263 }
5264 }
5265 InlayHintRefreshReason::SettingsChange(new_settings) => {
5266 match self.inlay_hint_cache.update_settings(
5267 &self.buffer,
5268 new_settings,
5269 self.visible_inlay_hints(cx).cloned().collect::<Vec<_>>(),
5270 cx,
5271 ) {
5272 ControlFlow::Break(Some(InlaySplice {
5273 to_remove,
5274 to_insert,
5275 })) => {
5276 self.splice_inlays(&to_remove, to_insert, cx);
5277 return;
5278 }
5279 ControlFlow::Break(None) => return,
5280 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5281 }
5282 }
5283 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5284 if let Some(InlaySplice {
5285 to_remove,
5286 to_insert,
5287 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5288 {
5289 self.splice_inlays(&to_remove, to_insert, cx);
5290 }
5291 self.display_map.update(cx, |display_map, _| {
5292 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5293 });
5294 return;
5295 }
5296 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5297 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5298 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5299 }
5300 InlayHintRefreshReason::RefreshRequested => {
5301 (InvalidationStrategy::RefreshRequested, None)
5302 }
5303 };
5304
5305 if let Some(InlaySplice {
5306 to_remove,
5307 to_insert,
5308 }) = self.inlay_hint_cache.spawn_hint_refresh(
5309 reason_description,
5310 self.visible_excerpts(required_languages.as_ref(), cx),
5311 invalidate_cache,
5312 ignore_debounce,
5313 cx,
5314 ) {
5315 self.splice_inlays(&to_remove, to_insert, cx);
5316 }
5317 }
5318
5319 pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
5320 self.splice_inlays(
5321 &self
5322 .visible_inlay_hints(cx)
5323 .map(|inlay| inlay.id)
5324 .collect::<Vec<_>>(),
5325 Vec::new(),
5326 cx,
5327 );
5328 }
5329
5330 fn visible_inlay_hints<'a>(
5331 &'a self,
5332 cx: &'a Context<Editor>,
5333 ) -> impl Iterator<Item = &'a Inlay> {
5334 self.display_map
5335 .read(cx)
5336 .current_inlays()
5337 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5338 }
5339
5340 pub fn visible_excerpts(
5341 &self,
5342 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5343 cx: &mut Context<Editor>,
5344 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5345 let Some(project) = self.project() else {
5346 return HashMap::default();
5347 };
5348 let project = project.read(cx);
5349 let multi_buffer = self.buffer().read(cx);
5350 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5351 let multi_buffer_visible_start = self
5352 .scroll_manager
5353 .anchor()
5354 .anchor
5355 .to_point(&multi_buffer_snapshot);
5356 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5357 multi_buffer_visible_start
5358 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5359 Bias::Left,
5360 );
5361 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5362 multi_buffer_snapshot
5363 .range_to_buffer_ranges(multi_buffer_visible_range)
5364 .into_iter()
5365 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5366 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5367 let buffer_file = project::File::from_dyn(buffer.file())?;
5368 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5369 let worktree_entry = buffer_worktree
5370 .read(cx)
5371 .entry_for_id(buffer_file.project_entry_id()?)?;
5372 if worktree_entry.is_ignored {
5373 return None;
5374 }
5375
5376 let language = buffer.language()?;
5377 if let Some(restrict_to_languages) = restrict_to_languages
5378 && !restrict_to_languages.contains(language)
5379 {
5380 return None;
5381 }
5382 Some((
5383 excerpt_id,
5384 (
5385 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5386 buffer.version().clone(),
5387 excerpt_visible_range,
5388 ),
5389 ))
5390 })
5391 .collect()
5392 }
5393
5394 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5395 TextLayoutDetails {
5396 text_system: window.text_system().clone(),
5397 editor_style: self.style.clone().unwrap(),
5398 rem_size: window.rem_size(),
5399 scroll_anchor: self.scroll_manager.anchor(),
5400 visible_rows: self.visible_line_count(),
5401 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5402 }
5403 }
5404
5405 pub fn splice_inlays(
5406 &self,
5407 to_remove: &[InlayId],
5408 to_insert: Vec<Inlay>,
5409 cx: &mut Context<Self>,
5410 ) {
5411 self.display_map.update(cx, |display_map, cx| {
5412 display_map.splice_inlays(to_remove, to_insert, cx)
5413 });
5414 cx.notify();
5415 }
5416
5417 fn trigger_on_type_formatting(
5418 &self,
5419 input: String,
5420 window: &mut Window,
5421 cx: &mut Context<Self>,
5422 ) -> Option<Task<Result<()>>> {
5423 if input.len() != 1 {
5424 return None;
5425 }
5426
5427 let project = self.project()?;
5428 let position = self.selections.newest_anchor().head();
5429 let (buffer, buffer_position) = self
5430 .buffer
5431 .read(cx)
5432 .text_anchor_for_position(position, cx)?;
5433
5434 let settings = language_settings::language_settings(
5435 buffer
5436 .read(cx)
5437 .language_at(buffer_position)
5438 .map(|l| l.name()),
5439 buffer.read(cx).file(),
5440 cx,
5441 );
5442 if !settings.use_on_type_format {
5443 return None;
5444 }
5445
5446 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5447 // hence we do LSP request & edit on host side only — add formats to host's history.
5448 let push_to_lsp_host_history = true;
5449 // If this is not the host, append its history with new edits.
5450 let push_to_client_history = project.read(cx).is_via_collab();
5451
5452 let on_type_formatting = project.update(cx, |project, cx| {
5453 project.on_type_format(
5454 buffer.clone(),
5455 buffer_position,
5456 input,
5457 push_to_lsp_host_history,
5458 cx,
5459 )
5460 });
5461 Some(cx.spawn_in(window, async move |editor, cx| {
5462 if let Some(transaction) = on_type_formatting.await? {
5463 if push_to_client_history {
5464 buffer
5465 .update(cx, |buffer, _| {
5466 buffer.push_transaction(transaction, Instant::now());
5467 buffer.finalize_last_transaction();
5468 })
5469 .ok();
5470 }
5471 editor.update(cx, |editor, cx| {
5472 editor.refresh_document_highlights(cx);
5473 })?;
5474 }
5475 Ok(())
5476 }))
5477 }
5478
5479 pub fn show_word_completions(
5480 &mut self,
5481 _: &ShowWordCompletions,
5482 window: &mut Window,
5483 cx: &mut Context<Self>,
5484 ) {
5485 self.open_or_update_completions_menu(
5486 Some(CompletionsMenuSource::Words {
5487 ignore_threshold: true,
5488 }),
5489 None,
5490 window,
5491 cx,
5492 );
5493 }
5494
5495 pub fn show_completions(
5496 &mut self,
5497 options: &ShowCompletions,
5498 window: &mut Window,
5499 cx: &mut Context<Self>,
5500 ) {
5501 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5502 }
5503
5504 fn open_or_update_completions_menu(
5505 &mut self,
5506 requested_source: Option<CompletionsMenuSource>,
5507 trigger: Option<&str>,
5508 window: &mut Window,
5509 cx: &mut Context<Self>,
5510 ) {
5511 if self.pending_rename.is_some() {
5512 return;
5513 }
5514
5515 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5516
5517 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5518 // inserted and selected. To handle that case, the start of the selection is used so that
5519 // the menu starts with all choices.
5520 let position = self
5521 .selections
5522 .newest_anchor()
5523 .start
5524 .bias_right(&multibuffer_snapshot);
5525 if position.diff_base_anchor.is_some() {
5526 return;
5527 }
5528 let buffer_position = multibuffer_snapshot.anchor_before(position);
5529 let Some(buffer) = buffer_position
5530 .buffer_id
5531 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5532 else {
5533 return;
5534 };
5535 let buffer_snapshot = buffer.read(cx).snapshot();
5536
5537 let query: Option<Arc<String>> =
5538 Self::completion_query(&multibuffer_snapshot, buffer_position)
5539 .map(|query| query.into());
5540
5541 drop(multibuffer_snapshot);
5542
5543 // Hide the current completions menu when query is empty. Without this, cached
5544 // completions from before the trigger char may be reused (#32774).
5545 if query.is_none() {
5546 let menu_is_open = matches!(
5547 self.context_menu.borrow().as_ref(),
5548 Some(CodeContextMenu::Completions(_))
5549 );
5550 if menu_is_open {
5551 self.hide_context_menu(window, cx);
5552 }
5553 }
5554
5555 let mut ignore_word_threshold = false;
5556 let provider = match requested_source {
5557 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5558 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5559 ignore_word_threshold = ignore_threshold;
5560 None
5561 }
5562 Some(CompletionsMenuSource::SnippetChoices) => {
5563 log::error!("bug: SnippetChoices requested_source is not handled");
5564 None
5565 }
5566 };
5567
5568 let sort_completions = provider
5569 .as_ref()
5570 .is_some_and(|provider| provider.sort_completions());
5571
5572 let filter_completions = provider
5573 .as_ref()
5574 .is_none_or(|provider| provider.filter_completions());
5575
5576 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5577 if filter_completions {
5578 menu.filter(query.clone(), provider.clone(), window, cx);
5579 }
5580 // When `is_incomplete` is false, no need to re-query completions when the current query
5581 // is a suffix of the initial query.
5582 if !menu.is_incomplete {
5583 // If the new query is a suffix of the old query (typing more characters) and
5584 // the previous result was complete, the existing completions can be filtered.
5585 //
5586 // Note that this is always true for snippet completions.
5587 let query_matches = match (&menu.initial_query, &query) {
5588 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5589 (None, _) => true,
5590 _ => false,
5591 };
5592 if query_matches {
5593 let position_matches = if menu.initial_position == position {
5594 true
5595 } else {
5596 let snapshot = self.buffer.read(cx).read(cx);
5597 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5598 };
5599 if position_matches {
5600 return;
5601 }
5602 }
5603 }
5604 };
5605
5606 let trigger_kind = match trigger {
5607 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5608 CompletionTriggerKind::TRIGGER_CHARACTER
5609 }
5610 _ => CompletionTriggerKind::INVOKED,
5611 };
5612 let completion_context = CompletionContext {
5613 trigger_character: trigger.and_then(|trigger| {
5614 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5615 Some(String::from(trigger))
5616 } else {
5617 None
5618 }
5619 }),
5620 trigger_kind,
5621 };
5622
5623 let Anchor {
5624 excerpt_id: buffer_excerpt_id,
5625 text_anchor: buffer_position,
5626 ..
5627 } = buffer_position;
5628
5629 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5630 buffer_snapshot.surrounding_word(buffer_position, None)
5631 {
5632 let word_to_exclude = buffer_snapshot
5633 .text_for_range(word_range.clone())
5634 .collect::<String>();
5635 (
5636 buffer_snapshot.anchor_before(word_range.start)
5637 ..buffer_snapshot.anchor_after(buffer_position),
5638 Some(word_to_exclude),
5639 )
5640 } else {
5641 (buffer_position..buffer_position, None)
5642 };
5643
5644 let language = buffer_snapshot
5645 .language_at(buffer_position)
5646 .map(|language| language.name());
5647
5648 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5649 .completions
5650 .clone();
5651
5652 let show_completion_documentation = buffer_snapshot
5653 .settings_at(buffer_position, cx)
5654 .show_completion_documentation;
5655
5656 // The document can be large, so stay in reasonable bounds when searching for words,
5657 // otherwise completion pop-up might be slow to appear.
5658 const WORD_LOOKUP_ROWS: u32 = 5_000;
5659 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5660 let min_word_search = buffer_snapshot.clip_point(
5661 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5662 Bias::Left,
5663 );
5664 let max_word_search = buffer_snapshot.clip_point(
5665 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5666 Bias::Right,
5667 );
5668 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5669 ..buffer_snapshot.point_to_offset(max_word_search);
5670
5671 let skip_digits = query
5672 .as_ref()
5673 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5674
5675 let omit_word_completions = !self.word_completions_enabled
5676 || (!ignore_word_threshold
5677 && match &query {
5678 Some(query) => query.chars().count() < completion_settings.words_min_length,
5679 None => completion_settings.words_min_length != 0,
5680 });
5681
5682 let (mut words, provider_responses) = match &provider {
5683 Some(provider) => {
5684 let provider_responses = provider.completions(
5685 buffer_excerpt_id,
5686 &buffer,
5687 buffer_position,
5688 completion_context,
5689 window,
5690 cx,
5691 );
5692
5693 let words = match (omit_word_completions, completion_settings.words) {
5694 (true, _) | (_, WordsCompletionMode::Disabled) => {
5695 Task::ready(BTreeMap::default())
5696 }
5697 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5698 .background_spawn(async move {
5699 buffer_snapshot.words_in_range(WordsQuery {
5700 fuzzy_contents: None,
5701 range: word_search_range,
5702 skip_digits,
5703 })
5704 }),
5705 };
5706
5707 (words, provider_responses)
5708 }
5709 None => {
5710 let words = if omit_word_completions {
5711 Task::ready(BTreeMap::default())
5712 } else {
5713 cx.background_spawn(async move {
5714 buffer_snapshot.words_in_range(WordsQuery {
5715 fuzzy_contents: None,
5716 range: word_search_range,
5717 skip_digits,
5718 })
5719 })
5720 };
5721 (words, Task::ready(Ok(Vec::new())))
5722 }
5723 };
5724
5725 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5726
5727 let id = post_inc(&mut self.next_completion_id);
5728 let task = cx.spawn_in(window, async move |editor, cx| {
5729 let Ok(()) = editor.update(cx, |this, _| {
5730 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5731 }) else {
5732 return;
5733 };
5734
5735 // TODO: Ideally completions from different sources would be selectively re-queried, so
5736 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5737 let mut completions = Vec::new();
5738 let mut is_incomplete = false;
5739 let mut display_options: Option<CompletionDisplayOptions> = None;
5740 if let Some(provider_responses) = provider_responses.await.log_err()
5741 && !provider_responses.is_empty()
5742 {
5743 for response in provider_responses {
5744 completions.extend(response.completions);
5745 is_incomplete = is_incomplete || response.is_incomplete;
5746 match display_options.as_mut() {
5747 None => {
5748 display_options = Some(response.display_options);
5749 }
5750 Some(options) => options.merge(&response.display_options),
5751 }
5752 }
5753 if completion_settings.words == WordsCompletionMode::Fallback {
5754 words = Task::ready(BTreeMap::default());
5755 }
5756 }
5757 let display_options = display_options.unwrap_or_default();
5758
5759 let mut words = words.await;
5760 if let Some(word_to_exclude) = &word_to_exclude {
5761 words.remove(word_to_exclude);
5762 }
5763 for lsp_completion in &completions {
5764 words.remove(&lsp_completion.new_text);
5765 }
5766 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5767 replace_range: word_replace_range.clone(),
5768 new_text: word.clone(),
5769 label: CodeLabel::plain(word, None),
5770 icon_path: None,
5771 documentation: None,
5772 source: CompletionSource::BufferWord {
5773 word_range,
5774 resolved: false,
5775 },
5776 insert_text_mode: Some(InsertTextMode::AS_IS),
5777 confirm: None,
5778 }));
5779
5780 let menu = if completions.is_empty() {
5781 None
5782 } else {
5783 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5784 let languages = editor
5785 .workspace
5786 .as_ref()
5787 .and_then(|(workspace, _)| workspace.upgrade())
5788 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5789 let menu = CompletionsMenu::new(
5790 id,
5791 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5792 sort_completions,
5793 show_completion_documentation,
5794 position,
5795 query.clone(),
5796 is_incomplete,
5797 buffer.clone(),
5798 completions.into(),
5799 display_options,
5800 snippet_sort_order,
5801 languages,
5802 language,
5803 cx,
5804 );
5805
5806 let query = if filter_completions { query } else { None };
5807 let matches_task = if let Some(query) = query {
5808 menu.do_async_filtering(query, cx)
5809 } else {
5810 Task::ready(menu.unfiltered_matches())
5811 };
5812 (menu, matches_task)
5813 }) else {
5814 return;
5815 };
5816
5817 let matches = matches_task.await;
5818
5819 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5820 // Newer menu already set, so exit.
5821 if let Some(CodeContextMenu::Completions(prev_menu)) =
5822 editor.context_menu.borrow().as_ref()
5823 && prev_menu.id > id
5824 {
5825 return;
5826 };
5827
5828 // Only valid to take prev_menu because it the new menu is immediately set
5829 // below, or the menu is hidden.
5830 if let Some(CodeContextMenu::Completions(prev_menu)) =
5831 editor.context_menu.borrow_mut().take()
5832 {
5833 let position_matches =
5834 if prev_menu.initial_position == menu.initial_position {
5835 true
5836 } else {
5837 let snapshot = editor.buffer.read(cx).read(cx);
5838 prev_menu.initial_position.to_offset(&snapshot)
5839 == menu.initial_position.to_offset(&snapshot)
5840 };
5841 if position_matches {
5842 // Preserve markdown cache before `set_filter_results` because it will
5843 // try to populate the documentation cache.
5844 menu.preserve_markdown_cache(prev_menu);
5845 }
5846 };
5847
5848 menu.set_filter_results(matches, provider, window, cx);
5849 }) else {
5850 return;
5851 };
5852
5853 menu.visible().then_some(menu)
5854 };
5855
5856 editor
5857 .update_in(cx, |editor, window, cx| {
5858 if editor.focus_handle.is_focused(window)
5859 && let Some(menu) = menu
5860 {
5861 *editor.context_menu.borrow_mut() =
5862 Some(CodeContextMenu::Completions(menu));
5863
5864 crate::hover_popover::hide_hover(editor, cx);
5865 if editor.show_edit_predictions_in_menu() {
5866 editor.update_visible_edit_prediction(window, cx);
5867 } else {
5868 editor.discard_edit_prediction(false, cx);
5869 }
5870
5871 cx.notify();
5872 return;
5873 }
5874
5875 if editor.completion_tasks.len() <= 1 {
5876 // If there are no more completion tasks and the last menu was empty, we should hide it.
5877 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5878 // If it was already hidden and we don't show edit predictions in the menu,
5879 // we should also show the edit prediction when available.
5880 if was_hidden && editor.show_edit_predictions_in_menu() {
5881 editor.update_visible_edit_prediction(window, cx);
5882 }
5883 }
5884 })
5885 .ok();
5886 });
5887
5888 self.completion_tasks.push((id, task));
5889 }
5890
5891 #[cfg(feature = "test-support")]
5892 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5893 let menu = self.context_menu.borrow();
5894 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5895 let completions = menu.completions.borrow();
5896 Some(completions.to_vec())
5897 } else {
5898 None
5899 }
5900 }
5901
5902 pub fn with_completions_menu_matching_id<R>(
5903 &self,
5904 id: CompletionId,
5905 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5906 ) -> R {
5907 let mut context_menu = self.context_menu.borrow_mut();
5908 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5909 return f(None);
5910 };
5911 if completions_menu.id != id {
5912 return f(None);
5913 }
5914 f(Some(completions_menu))
5915 }
5916
5917 pub fn confirm_completion(
5918 &mut self,
5919 action: &ConfirmCompletion,
5920 window: &mut Window,
5921 cx: &mut Context<Self>,
5922 ) -> Option<Task<Result<()>>> {
5923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5924 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5925 }
5926
5927 pub fn confirm_completion_insert(
5928 &mut self,
5929 _: &ConfirmCompletionInsert,
5930 window: &mut Window,
5931 cx: &mut Context<Self>,
5932 ) -> Option<Task<Result<()>>> {
5933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5934 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5935 }
5936
5937 pub fn confirm_completion_replace(
5938 &mut self,
5939 _: &ConfirmCompletionReplace,
5940 window: &mut Window,
5941 cx: &mut Context<Self>,
5942 ) -> Option<Task<Result<()>>> {
5943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5944 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5945 }
5946
5947 pub fn compose_completion(
5948 &mut self,
5949 action: &ComposeCompletion,
5950 window: &mut Window,
5951 cx: &mut Context<Self>,
5952 ) -> Option<Task<Result<()>>> {
5953 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5954 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5955 }
5956
5957 fn do_completion(
5958 &mut self,
5959 item_ix: Option<usize>,
5960 intent: CompletionIntent,
5961 window: &mut Window,
5962 cx: &mut Context<Editor>,
5963 ) -> Option<Task<Result<()>>> {
5964 use language::ToOffset as _;
5965
5966 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5967 else {
5968 return None;
5969 };
5970
5971 let candidate_id = {
5972 let entries = completions_menu.entries.borrow();
5973 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5974 if self.show_edit_predictions_in_menu() {
5975 self.discard_edit_prediction(true, cx);
5976 }
5977 mat.candidate_id
5978 };
5979
5980 let completion = completions_menu
5981 .completions
5982 .borrow()
5983 .get(candidate_id)?
5984 .clone();
5985 cx.stop_propagation();
5986
5987 let buffer_handle = completions_menu.buffer.clone();
5988
5989 let CompletionEdit {
5990 new_text,
5991 snippet,
5992 replace_range,
5993 } = process_completion_for_edit(
5994 &completion,
5995 intent,
5996 &buffer_handle,
5997 &completions_menu.initial_position.text_anchor,
5998 cx,
5999 );
6000
6001 let buffer = buffer_handle.read(cx);
6002 let snapshot = self.buffer.read(cx).snapshot(cx);
6003 let newest_anchor = self.selections.newest_anchor();
6004 let replace_range_multibuffer = {
6005 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6006 let multibuffer_anchor = snapshot
6007 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
6008 .unwrap()
6009 ..snapshot
6010 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
6011 .unwrap();
6012 multibuffer_anchor.start.to_offset(&snapshot)
6013 ..multibuffer_anchor.end.to_offset(&snapshot)
6014 };
6015 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6016 return None;
6017 }
6018
6019 let old_text = buffer
6020 .text_for_range(replace_range.clone())
6021 .collect::<String>();
6022 let lookbehind = newest_anchor
6023 .start
6024 .text_anchor
6025 .to_offset(buffer)
6026 .saturating_sub(replace_range.start);
6027 let lookahead = replace_range
6028 .end
6029 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6030 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6031 let suffix = &old_text[lookbehind.min(old_text.len())..];
6032
6033 let selections = self.selections.all::<usize>(cx);
6034 let mut ranges = Vec::new();
6035 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6036
6037 for selection in &selections {
6038 let range = if selection.id == newest_anchor.id {
6039 replace_range_multibuffer.clone()
6040 } else {
6041 let mut range = selection.range();
6042
6043 // if prefix is present, don't duplicate it
6044 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6045 range.start = range.start.saturating_sub(lookbehind);
6046
6047 // if suffix is also present, mimic the newest cursor and replace it
6048 if selection.id != newest_anchor.id
6049 && snapshot.contains_str_at(range.end, suffix)
6050 {
6051 range.end += lookahead;
6052 }
6053 }
6054 range
6055 };
6056
6057 ranges.push(range.clone());
6058
6059 if !self.linked_edit_ranges.is_empty() {
6060 let start_anchor = snapshot.anchor_before(range.start);
6061 let end_anchor = snapshot.anchor_after(range.end);
6062 if let Some(ranges) = self
6063 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6064 {
6065 for (buffer, edits) in ranges {
6066 linked_edits
6067 .entry(buffer.clone())
6068 .or_default()
6069 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6070 }
6071 }
6072 }
6073 }
6074
6075 let common_prefix_len = old_text
6076 .chars()
6077 .zip(new_text.chars())
6078 .take_while(|(a, b)| a == b)
6079 .map(|(a, _)| a.len_utf8())
6080 .sum::<usize>();
6081
6082 cx.emit(EditorEvent::InputHandled {
6083 utf16_range_to_replace: None,
6084 text: new_text[common_prefix_len..].into(),
6085 });
6086
6087 self.transact(window, cx, |editor, window, cx| {
6088 if let Some(mut snippet) = snippet {
6089 snippet.text = new_text.to_string();
6090 editor
6091 .insert_snippet(&ranges, snippet, window, cx)
6092 .log_err();
6093 } else {
6094 editor.buffer.update(cx, |multi_buffer, cx| {
6095 let auto_indent = match completion.insert_text_mode {
6096 Some(InsertTextMode::AS_IS) => None,
6097 _ => editor.autoindent_mode.clone(),
6098 };
6099 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6100 multi_buffer.edit(edits, auto_indent, cx);
6101 });
6102 }
6103 for (buffer, edits) in linked_edits {
6104 buffer.update(cx, |buffer, cx| {
6105 let snapshot = buffer.snapshot();
6106 let edits = edits
6107 .into_iter()
6108 .map(|(range, text)| {
6109 use text::ToPoint as TP;
6110 let end_point = TP::to_point(&range.end, &snapshot);
6111 let start_point = TP::to_point(&range.start, &snapshot);
6112 (start_point..end_point, text)
6113 })
6114 .sorted_by_key(|(range, _)| range.start);
6115 buffer.edit(edits, None, cx);
6116 })
6117 }
6118
6119 editor.refresh_edit_prediction(true, false, window, cx);
6120 });
6121 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6122
6123 let show_new_completions_on_confirm = completion
6124 .confirm
6125 .as_ref()
6126 .is_some_and(|confirm| confirm(intent, window, cx));
6127 if show_new_completions_on_confirm {
6128 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6129 }
6130
6131 let provider = self.completion_provider.as_ref()?;
6132 drop(completion);
6133 let apply_edits = provider.apply_additional_edits_for_completion(
6134 buffer_handle,
6135 completions_menu.completions.clone(),
6136 candidate_id,
6137 true,
6138 cx,
6139 );
6140
6141 let editor_settings = EditorSettings::get_global(cx);
6142 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6143 // After the code completion is finished, users often want to know what signatures are needed.
6144 // so we should automatically call signature_help
6145 self.show_signature_help(&ShowSignatureHelp, window, cx);
6146 }
6147
6148 Some(cx.foreground_executor().spawn(async move {
6149 apply_edits.await?;
6150 Ok(())
6151 }))
6152 }
6153
6154 pub fn toggle_code_actions(
6155 &mut self,
6156 action: &ToggleCodeActions,
6157 window: &mut Window,
6158 cx: &mut Context<Self>,
6159 ) {
6160 let quick_launch = action.quick_launch;
6161 let mut context_menu = self.context_menu.borrow_mut();
6162 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6163 if code_actions.deployed_from == action.deployed_from {
6164 // Toggle if we're selecting the same one
6165 *context_menu = None;
6166 cx.notify();
6167 return;
6168 } else {
6169 // Otherwise, clear it and start a new one
6170 *context_menu = None;
6171 cx.notify();
6172 }
6173 }
6174 drop(context_menu);
6175 let snapshot = self.snapshot(window, cx);
6176 let deployed_from = action.deployed_from.clone();
6177 let action = action.clone();
6178 self.completion_tasks.clear();
6179 self.discard_edit_prediction(false, cx);
6180
6181 let multibuffer_point = match &action.deployed_from {
6182 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6183 DisplayPoint::new(*row, 0).to_point(&snapshot)
6184 }
6185 _ => self.selections.newest::<Point>(cx).head(),
6186 };
6187 let Some((buffer, buffer_row)) = snapshot
6188 .buffer_snapshot()
6189 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6190 .and_then(|(buffer_snapshot, range)| {
6191 self.buffer()
6192 .read(cx)
6193 .buffer(buffer_snapshot.remote_id())
6194 .map(|buffer| (buffer, range.start.row))
6195 })
6196 else {
6197 return;
6198 };
6199 let buffer_id = buffer.read(cx).remote_id();
6200 let tasks = self
6201 .tasks
6202 .get(&(buffer_id, buffer_row))
6203 .map(|t| Arc::new(t.to_owned()));
6204
6205 if !self.focus_handle.is_focused(window) {
6206 return;
6207 }
6208 let project = self.project.clone();
6209
6210 let code_actions_task = match deployed_from {
6211 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6212 _ => self.code_actions(buffer_row, window, cx),
6213 };
6214
6215 let runnable_task = match deployed_from {
6216 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6217 _ => {
6218 let mut task_context_task = Task::ready(None);
6219 if let Some(tasks) = &tasks
6220 && let Some(project) = project
6221 {
6222 task_context_task =
6223 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6224 }
6225
6226 cx.spawn_in(window, {
6227 let buffer = buffer.clone();
6228 async move |editor, cx| {
6229 let task_context = task_context_task.await;
6230
6231 let resolved_tasks =
6232 tasks
6233 .zip(task_context.clone())
6234 .map(|(tasks, task_context)| ResolvedTasks {
6235 templates: tasks.resolve(&task_context).collect(),
6236 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6237 multibuffer_point.row,
6238 tasks.column,
6239 )),
6240 });
6241 let debug_scenarios = editor
6242 .update(cx, |editor, cx| {
6243 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6244 })?
6245 .await;
6246 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6247 }
6248 })
6249 }
6250 };
6251
6252 cx.spawn_in(window, async move |editor, cx| {
6253 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6254 let code_actions = code_actions_task.await;
6255 let spawn_straight_away = quick_launch
6256 && resolved_tasks
6257 .as_ref()
6258 .is_some_and(|tasks| tasks.templates.len() == 1)
6259 && code_actions
6260 .as_ref()
6261 .is_none_or(|actions| actions.is_empty())
6262 && debug_scenarios.is_empty();
6263
6264 editor.update_in(cx, |editor, window, cx| {
6265 crate::hover_popover::hide_hover(editor, cx);
6266 let actions = CodeActionContents::new(
6267 resolved_tasks,
6268 code_actions,
6269 debug_scenarios,
6270 task_context.unwrap_or_default(),
6271 );
6272
6273 // Don't show the menu if there are no actions available
6274 if actions.is_empty() {
6275 cx.notify();
6276 return Task::ready(Ok(()));
6277 }
6278
6279 *editor.context_menu.borrow_mut() =
6280 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6281 buffer,
6282 actions,
6283 selected_item: Default::default(),
6284 scroll_handle: UniformListScrollHandle::default(),
6285 deployed_from,
6286 }));
6287 cx.notify();
6288 if spawn_straight_away
6289 && let Some(task) = editor.confirm_code_action(
6290 &ConfirmCodeAction { item_ix: Some(0) },
6291 window,
6292 cx,
6293 )
6294 {
6295 return task;
6296 }
6297
6298 Task::ready(Ok(()))
6299 })
6300 })
6301 .detach_and_log_err(cx);
6302 }
6303
6304 fn debug_scenarios(
6305 &mut self,
6306 resolved_tasks: &Option<ResolvedTasks>,
6307 buffer: &Entity<Buffer>,
6308 cx: &mut App,
6309 ) -> Task<Vec<task::DebugScenario>> {
6310 maybe!({
6311 let project = self.project()?;
6312 let dap_store = project.read(cx).dap_store();
6313 let mut scenarios = vec![];
6314 let resolved_tasks = resolved_tasks.as_ref()?;
6315 let buffer = buffer.read(cx);
6316 let language = buffer.language()?;
6317 let file = buffer.file();
6318 let debug_adapter = language_settings(language.name().into(), file, cx)
6319 .debuggers
6320 .first()
6321 .map(SharedString::from)
6322 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6323
6324 dap_store.update(cx, |dap_store, cx| {
6325 for (_, task) in &resolved_tasks.templates {
6326 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6327 task.original_task().clone(),
6328 debug_adapter.clone().into(),
6329 task.display_label().to_owned().into(),
6330 cx,
6331 );
6332 scenarios.push(maybe_scenario);
6333 }
6334 });
6335 Some(cx.background_spawn(async move {
6336 futures::future::join_all(scenarios)
6337 .await
6338 .into_iter()
6339 .flatten()
6340 .collect::<Vec<_>>()
6341 }))
6342 })
6343 .unwrap_or_else(|| Task::ready(vec![]))
6344 }
6345
6346 fn code_actions(
6347 &mut self,
6348 buffer_row: u32,
6349 window: &mut Window,
6350 cx: &mut Context<Self>,
6351 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6352 let mut task = self.code_actions_task.take();
6353 cx.spawn_in(window, async move |editor, cx| {
6354 while let Some(prev_task) = task {
6355 prev_task.await.log_err();
6356 task = editor
6357 .update(cx, |this, _| this.code_actions_task.take())
6358 .ok()?;
6359 }
6360
6361 editor
6362 .update(cx, |editor, cx| {
6363 editor
6364 .available_code_actions
6365 .clone()
6366 .and_then(|(location, code_actions)| {
6367 let snapshot = location.buffer.read(cx).snapshot();
6368 let point_range = location.range.to_point(&snapshot);
6369 let point_range = point_range.start.row..=point_range.end.row;
6370 if point_range.contains(&buffer_row) {
6371 Some(code_actions)
6372 } else {
6373 None
6374 }
6375 })
6376 })
6377 .ok()
6378 .flatten()
6379 })
6380 }
6381
6382 pub fn confirm_code_action(
6383 &mut self,
6384 action: &ConfirmCodeAction,
6385 window: &mut Window,
6386 cx: &mut Context<Self>,
6387 ) -> Option<Task<Result<()>>> {
6388 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6389
6390 let actions_menu =
6391 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6392 menu
6393 } else {
6394 return None;
6395 };
6396
6397 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6398 let action = actions_menu.actions.get(action_ix)?;
6399 let title = action.label();
6400 let buffer = actions_menu.buffer;
6401 let workspace = self.workspace()?;
6402
6403 match action {
6404 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6405 workspace.update(cx, |workspace, cx| {
6406 workspace.schedule_resolved_task(
6407 task_source_kind,
6408 resolved_task,
6409 false,
6410 window,
6411 cx,
6412 );
6413
6414 Some(Task::ready(Ok(())))
6415 })
6416 }
6417 CodeActionsItem::CodeAction {
6418 excerpt_id,
6419 action,
6420 provider,
6421 } => {
6422 let apply_code_action =
6423 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6424 let workspace = workspace.downgrade();
6425 Some(cx.spawn_in(window, async move |editor, cx| {
6426 let project_transaction = apply_code_action.await?;
6427 Self::open_project_transaction(
6428 &editor,
6429 workspace,
6430 project_transaction,
6431 title,
6432 cx,
6433 )
6434 .await
6435 }))
6436 }
6437 CodeActionsItem::DebugScenario(scenario) => {
6438 let context = actions_menu.actions.context;
6439
6440 workspace.update(cx, |workspace, cx| {
6441 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6442 workspace.start_debug_session(
6443 scenario,
6444 context,
6445 Some(buffer),
6446 None,
6447 window,
6448 cx,
6449 );
6450 });
6451 Some(Task::ready(Ok(())))
6452 }
6453 }
6454 }
6455
6456 pub async fn open_project_transaction(
6457 editor: &WeakEntity<Editor>,
6458 workspace: WeakEntity<Workspace>,
6459 transaction: ProjectTransaction,
6460 title: String,
6461 cx: &mut AsyncWindowContext,
6462 ) -> Result<()> {
6463 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6464 cx.update(|_, cx| {
6465 entries.sort_unstable_by_key(|(buffer, _)| {
6466 buffer.read(cx).file().map(|f| f.path().clone())
6467 });
6468 })?;
6469 if entries.is_empty() {
6470 return Ok(());
6471 }
6472
6473 // If the project transaction's edits are all contained within this editor, then
6474 // avoid opening a new editor to display them.
6475
6476 if let [(buffer, transaction)] = &*entries {
6477 let excerpt = editor.update(cx, |editor, cx| {
6478 editor
6479 .buffer()
6480 .read(cx)
6481 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6482 })?;
6483 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6484 && excerpted_buffer == *buffer
6485 {
6486 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6487 let excerpt_range = excerpt_range.to_offset(buffer);
6488 buffer
6489 .edited_ranges_for_transaction::<usize>(transaction)
6490 .all(|range| {
6491 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6492 })
6493 })?;
6494
6495 if all_edits_within_excerpt {
6496 return Ok(());
6497 }
6498 }
6499 }
6500
6501 let mut ranges_to_highlight = Vec::new();
6502 let excerpt_buffer = cx.new(|cx| {
6503 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6504 for (buffer_handle, transaction) in &entries {
6505 let edited_ranges = buffer_handle
6506 .read(cx)
6507 .edited_ranges_for_transaction::<Point>(transaction)
6508 .collect::<Vec<_>>();
6509 let (ranges, _) = multibuffer.set_excerpts_for_path(
6510 PathKey::for_buffer(buffer_handle, cx),
6511 buffer_handle.clone(),
6512 edited_ranges,
6513 multibuffer_context_lines(cx),
6514 cx,
6515 );
6516
6517 ranges_to_highlight.extend(ranges);
6518 }
6519 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6520 multibuffer
6521 })?;
6522
6523 workspace.update_in(cx, |workspace, window, cx| {
6524 let project = workspace.project().clone();
6525 let editor =
6526 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6527 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6528 editor.update(cx, |editor, cx| {
6529 editor.highlight_background::<Self>(
6530 &ranges_to_highlight,
6531 |theme| theme.colors().editor_highlighted_line_background,
6532 cx,
6533 );
6534 });
6535 })?;
6536
6537 Ok(())
6538 }
6539
6540 pub fn clear_code_action_providers(&mut self) {
6541 self.code_action_providers.clear();
6542 self.available_code_actions.take();
6543 }
6544
6545 pub fn add_code_action_provider(
6546 &mut self,
6547 provider: Rc<dyn CodeActionProvider>,
6548 window: &mut Window,
6549 cx: &mut Context<Self>,
6550 ) {
6551 if self
6552 .code_action_providers
6553 .iter()
6554 .any(|existing_provider| existing_provider.id() == provider.id())
6555 {
6556 return;
6557 }
6558
6559 self.code_action_providers.push(provider);
6560 self.refresh_code_actions(window, cx);
6561 }
6562
6563 pub fn remove_code_action_provider(
6564 &mut self,
6565 id: Arc<str>,
6566 window: &mut Window,
6567 cx: &mut Context<Self>,
6568 ) {
6569 self.code_action_providers
6570 .retain(|provider| provider.id() != id);
6571 self.refresh_code_actions(window, cx);
6572 }
6573
6574 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6575 !self.code_action_providers.is_empty()
6576 && EditorSettings::get_global(cx).toolbar.code_actions
6577 }
6578
6579 pub fn has_available_code_actions(&self) -> bool {
6580 self.available_code_actions
6581 .as_ref()
6582 .is_some_and(|(_, actions)| !actions.is_empty())
6583 }
6584
6585 fn render_inline_code_actions(
6586 &self,
6587 icon_size: ui::IconSize,
6588 display_row: DisplayRow,
6589 is_active: bool,
6590 cx: &mut Context<Self>,
6591 ) -> AnyElement {
6592 let show_tooltip = !self.context_menu_visible();
6593 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6594 .icon_size(icon_size)
6595 .shape(ui::IconButtonShape::Square)
6596 .icon_color(ui::Color::Hidden)
6597 .toggle_state(is_active)
6598 .when(show_tooltip, |this| {
6599 this.tooltip({
6600 let focus_handle = self.focus_handle.clone();
6601 move |window, cx| {
6602 Tooltip::for_action_in(
6603 "Toggle Code Actions",
6604 &ToggleCodeActions {
6605 deployed_from: None,
6606 quick_launch: false,
6607 },
6608 &focus_handle,
6609 window,
6610 cx,
6611 )
6612 }
6613 })
6614 })
6615 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6616 window.focus(&editor.focus_handle(cx));
6617 editor.toggle_code_actions(
6618 &crate::actions::ToggleCodeActions {
6619 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6620 display_row,
6621 )),
6622 quick_launch: false,
6623 },
6624 window,
6625 cx,
6626 );
6627 }))
6628 .into_any_element()
6629 }
6630
6631 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6632 &self.context_menu
6633 }
6634
6635 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6636 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6637 cx.background_executor()
6638 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6639 .await;
6640
6641 let (start_buffer, start, _, end, newest_selection) = this
6642 .update(cx, |this, cx| {
6643 let newest_selection = this.selections.newest_anchor().clone();
6644 if newest_selection.head().diff_base_anchor.is_some() {
6645 return None;
6646 }
6647 let newest_selection_adjusted = this.selections.newest_adjusted(cx);
6648 let buffer = this.buffer.read(cx);
6649
6650 let (start_buffer, start) =
6651 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6652 let (end_buffer, end) =
6653 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6654
6655 Some((start_buffer, start, end_buffer, end, newest_selection))
6656 })?
6657 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6658 .context(
6659 "Expected selection to lie in a single buffer when refreshing code actions",
6660 )?;
6661 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6662 let providers = this.code_action_providers.clone();
6663 let tasks = this
6664 .code_action_providers
6665 .iter()
6666 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6667 .collect::<Vec<_>>();
6668 (providers, tasks)
6669 })?;
6670
6671 let mut actions = Vec::new();
6672 for (provider, provider_actions) in
6673 providers.into_iter().zip(future::join_all(tasks).await)
6674 {
6675 if let Some(provider_actions) = provider_actions.log_err() {
6676 actions.extend(provider_actions.into_iter().map(|action| {
6677 AvailableCodeAction {
6678 excerpt_id: newest_selection.start.excerpt_id,
6679 action,
6680 provider: provider.clone(),
6681 }
6682 }));
6683 }
6684 }
6685
6686 this.update(cx, |this, cx| {
6687 this.available_code_actions = if actions.is_empty() {
6688 None
6689 } else {
6690 Some((
6691 Location {
6692 buffer: start_buffer,
6693 range: start..end,
6694 },
6695 actions.into(),
6696 ))
6697 };
6698 cx.notify();
6699 })
6700 }));
6701 }
6702
6703 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6704 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6705 self.show_git_blame_inline = false;
6706
6707 self.show_git_blame_inline_delay_task =
6708 Some(cx.spawn_in(window, async move |this, cx| {
6709 cx.background_executor().timer(delay).await;
6710
6711 this.update(cx, |this, cx| {
6712 this.show_git_blame_inline = true;
6713 cx.notify();
6714 })
6715 .log_err();
6716 }));
6717 }
6718 }
6719
6720 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6721 let snapshot = self.snapshot(window, cx);
6722 let cursor = self.selections.newest::<Point>(cx).head();
6723 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6724 else {
6725 return;
6726 };
6727
6728 let Some(blame) = self.blame.as_ref() else {
6729 return;
6730 };
6731
6732 let row_info = RowInfo {
6733 buffer_id: Some(buffer.remote_id()),
6734 buffer_row: Some(point.row),
6735 ..Default::default()
6736 };
6737 let Some((buffer, blame_entry)) = blame
6738 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6739 .flatten()
6740 else {
6741 return;
6742 };
6743
6744 let anchor = self.selections.newest_anchor().head();
6745 let position = self.to_pixel_point(anchor, &snapshot, window);
6746 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6747 self.show_blame_popover(
6748 buffer,
6749 &blame_entry,
6750 position + last_bounds.origin,
6751 true,
6752 cx,
6753 );
6754 };
6755 }
6756
6757 fn show_blame_popover(
6758 &mut self,
6759 buffer: BufferId,
6760 blame_entry: &BlameEntry,
6761 position: gpui::Point<Pixels>,
6762 ignore_timeout: bool,
6763 cx: &mut Context<Self>,
6764 ) {
6765 if let Some(state) = &mut self.inline_blame_popover {
6766 state.hide_task.take();
6767 } else {
6768 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6769 let blame_entry = blame_entry.clone();
6770 let show_task = cx.spawn(async move |editor, cx| {
6771 if !ignore_timeout {
6772 cx.background_executor()
6773 .timer(std::time::Duration::from_millis(blame_popover_delay))
6774 .await;
6775 }
6776 editor
6777 .update(cx, |editor, cx| {
6778 editor.inline_blame_popover_show_task.take();
6779 let Some(blame) = editor.blame.as_ref() else {
6780 return;
6781 };
6782 let blame = blame.read(cx);
6783 let details = blame.details_for_entry(buffer, &blame_entry);
6784 let markdown = cx.new(|cx| {
6785 Markdown::new(
6786 details
6787 .as_ref()
6788 .map(|message| message.message.clone())
6789 .unwrap_or_default(),
6790 None,
6791 None,
6792 cx,
6793 )
6794 });
6795 editor.inline_blame_popover = Some(InlineBlamePopover {
6796 position,
6797 hide_task: None,
6798 popover_bounds: None,
6799 popover_state: InlineBlamePopoverState {
6800 scroll_handle: ScrollHandle::new(),
6801 commit_message: details,
6802 markdown,
6803 },
6804 keyboard_grace: ignore_timeout,
6805 });
6806 cx.notify();
6807 })
6808 .ok();
6809 });
6810 self.inline_blame_popover_show_task = Some(show_task);
6811 }
6812 }
6813
6814 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6815 self.inline_blame_popover_show_task.take();
6816 if let Some(state) = &mut self.inline_blame_popover {
6817 let hide_task = cx.spawn(async move |editor, cx| {
6818 cx.background_executor()
6819 .timer(std::time::Duration::from_millis(100))
6820 .await;
6821 editor
6822 .update(cx, |editor, cx| {
6823 editor.inline_blame_popover.take();
6824 cx.notify();
6825 })
6826 .ok();
6827 });
6828 state.hide_task = Some(hide_task);
6829 }
6830 }
6831
6832 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6833 if self.pending_rename.is_some() {
6834 return None;
6835 }
6836
6837 let provider = self.semantics_provider.clone()?;
6838 let buffer = self.buffer.read(cx);
6839 let newest_selection = self.selections.newest_anchor().clone();
6840 let cursor_position = newest_selection.head();
6841 let (cursor_buffer, cursor_buffer_position) =
6842 buffer.text_anchor_for_position(cursor_position, cx)?;
6843 let (tail_buffer, tail_buffer_position) =
6844 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6845 if cursor_buffer != tail_buffer {
6846 return None;
6847 }
6848
6849 let snapshot = cursor_buffer.read(cx).snapshot();
6850 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6851 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6852 if start_word_range != end_word_range {
6853 self.document_highlights_task.take();
6854 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6855 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6856 return None;
6857 }
6858
6859 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6860 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6861 cx.background_executor()
6862 .timer(Duration::from_millis(debounce))
6863 .await;
6864
6865 let highlights = if let Some(highlights) = cx
6866 .update(|cx| {
6867 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6868 })
6869 .ok()
6870 .flatten()
6871 {
6872 highlights.await.log_err()
6873 } else {
6874 None
6875 };
6876
6877 if let Some(highlights) = highlights {
6878 this.update(cx, |this, cx| {
6879 if this.pending_rename.is_some() {
6880 return;
6881 }
6882
6883 let buffer = this.buffer.read(cx);
6884 if buffer
6885 .text_anchor_for_position(cursor_position, cx)
6886 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6887 {
6888 return;
6889 }
6890
6891 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6892 let mut write_ranges = Vec::new();
6893 let mut read_ranges = Vec::new();
6894 for highlight in highlights {
6895 let buffer_id = cursor_buffer.read(cx).remote_id();
6896 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6897 {
6898 let start = highlight
6899 .range
6900 .start
6901 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6902 let end = highlight
6903 .range
6904 .end
6905 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6906 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6907 continue;
6908 }
6909
6910 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6911 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6912 write_ranges.push(range);
6913 } else {
6914 read_ranges.push(range);
6915 }
6916 }
6917 }
6918
6919 this.highlight_background::<DocumentHighlightRead>(
6920 &read_ranges,
6921 |theme| theme.colors().editor_document_highlight_read_background,
6922 cx,
6923 );
6924 this.highlight_background::<DocumentHighlightWrite>(
6925 &write_ranges,
6926 |theme| theme.colors().editor_document_highlight_write_background,
6927 cx,
6928 );
6929 cx.notify();
6930 })
6931 .log_err();
6932 }
6933 }));
6934 None
6935 }
6936
6937 fn prepare_highlight_query_from_selection(
6938 &mut self,
6939 cx: &mut Context<Editor>,
6940 ) -> Option<(String, Range<Anchor>)> {
6941 if matches!(self.mode, EditorMode::SingleLine) {
6942 return None;
6943 }
6944 if !EditorSettings::get_global(cx).selection_highlight {
6945 return None;
6946 }
6947 if self.selections.count() != 1 || self.selections.line_mode() {
6948 return None;
6949 }
6950 let selection = self.selections.newest_anchor();
6951 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6952 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6953 ..selection.end.to_point(&multi_buffer_snapshot);
6954 // If the selection spans multiple rows OR it is empty
6955 if selection_point_range.start.row != selection_point_range.end.row
6956 || selection_point_range.start.column == selection_point_range.end.column
6957 {
6958 return None;
6959 }
6960
6961 let query = multi_buffer_snapshot
6962 .text_for_range(selection.range())
6963 .collect::<String>();
6964 if query.trim().is_empty() {
6965 return None;
6966 }
6967 Some((query, selection.range()))
6968 }
6969
6970 fn update_selection_occurrence_highlights(
6971 &mut self,
6972 query_text: String,
6973 query_range: Range<Anchor>,
6974 multi_buffer_range_to_query: Range<Point>,
6975 use_debounce: bool,
6976 window: &mut Window,
6977 cx: &mut Context<Editor>,
6978 ) -> Task<()> {
6979 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6980 cx.spawn_in(window, async move |editor, cx| {
6981 if use_debounce {
6982 cx.background_executor()
6983 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6984 .await;
6985 }
6986 let match_task = cx.background_spawn(async move {
6987 let buffer_ranges = multi_buffer_snapshot
6988 .range_to_buffer_ranges(multi_buffer_range_to_query)
6989 .into_iter()
6990 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6991 let mut match_ranges = Vec::new();
6992 let Ok(regex) = project::search::SearchQuery::text(
6993 query_text.clone(),
6994 false,
6995 false,
6996 false,
6997 Default::default(),
6998 Default::default(),
6999 false,
7000 None,
7001 ) else {
7002 return Vec::default();
7003 };
7004 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7005 match_ranges.extend(
7006 regex
7007 .search(buffer_snapshot, Some(search_range.clone()))
7008 .await
7009 .into_iter()
7010 .filter_map(|match_range| {
7011 let match_start = buffer_snapshot
7012 .anchor_after(search_range.start + match_range.start);
7013 let match_end = buffer_snapshot
7014 .anchor_before(search_range.start + match_range.end);
7015 let match_anchor_range = Anchor::range_in_buffer(
7016 excerpt_id,
7017 buffer_snapshot.remote_id(),
7018 match_start..match_end,
7019 );
7020 (match_anchor_range != query_range).then_some(match_anchor_range)
7021 }),
7022 );
7023 }
7024 match_ranges
7025 });
7026 let match_ranges = match_task.await;
7027 editor
7028 .update_in(cx, |editor, _, cx| {
7029 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7030 if !match_ranges.is_empty() {
7031 editor.highlight_background::<SelectedTextHighlight>(
7032 &match_ranges,
7033 |theme| theme.colors().editor_document_highlight_bracket_background,
7034 cx,
7035 )
7036 }
7037 })
7038 .log_err();
7039 })
7040 }
7041
7042 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7043 struct NewlineFold;
7044 let type_id = std::any::TypeId::of::<NewlineFold>();
7045 if !self.mode.is_single_line() {
7046 return;
7047 }
7048 let snapshot = self.snapshot(window, cx);
7049 if snapshot.buffer_snapshot().max_point().row == 0 {
7050 return;
7051 }
7052 let task = cx.background_spawn(async move {
7053 let new_newlines = snapshot
7054 .buffer_chars_at(0)
7055 .filter_map(|(c, i)| {
7056 if c == '\n' {
7057 Some(
7058 snapshot.buffer_snapshot().anchor_after(i)
7059 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7060 )
7061 } else {
7062 None
7063 }
7064 })
7065 .collect::<Vec<_>>();
7066 let existing_newlines = snapshot
7067 .folds_in_range(0..snapshot.buffer_snapshot().len())
7068 .filter_map(|fold| {
7069 if fold.placeholder.type_tag == Some(type_id) {
7070 Some(fold.range.start..fold.range.end)
7071 } else {
7072 None
7073 }
7074 })
7075 .collect::<Vec<_>>();
7076
7077 (new_newlines, existing_newlines)
7078 });
7079 self.folding_newlines = cx.spawn(async move |this, cx| {
7080 let (new_newlines, existing_newlines) = task.await;
7081 if new_newlines == existing_newlines {
7082 return;
7083 }
7084 let placeholder = FoldPlaceholder {
7085 render: Arc::new(move |_, _, cx| {
7086 div()
7087 .bg(cx.theme().status().hint_background)
7088 .border_b_1()
7089 .size_full()
7090 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7091 .border_color(cx.theme().status().hint)
7092 .child("\\n")
7093 .into_any()
7094 }),
7095 constrain_width: false,
7096 merge_adjacent: false,
7097 type_tag: Some(type_id),
7098 };
7099 let creases = new_newlines
7100 .into_iter()
7101 .map(|range| Crease::simple(range, placeholder.clone()))
7102 .collect();
7103 this.update(cx, |this, cx| {
7104 this.display_map.update(cx, |display_map, cx| {
7105 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7106 display_map.fold(creases, cx);
7107 });
7108 })
7109 .ok();
7110 });
7111 }
7112
7113 fn refresh_selected_text_highlights(
7114 &mut self,
7115 on_buffer_edit: bool,
7116 window: &mut Window,
7117 cx: &mut Context<Editor>,
7118 ) {
7119 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7120 else {
7121 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7122 self.quick_selection_highlight_task.take();
7123 self.debounced_selection_highlight_task.take();
7124 return;
7125 };
7126 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7127 if on_buffer_edit
7128 || self
7129 .quick_selection_highlight_task
7130 .as_ref()
7131 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7132 {
7133 let multi_buffer_visible_start = self
7134 .scroll_manager
7135 .anchor()
7136 .anchor
7137 .to_point(&multi_buffer_snapshot);
7138 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7139 multi_buffer_visible_start
7140 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7141 Bias::Left,
7142 );
7143 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7144 self.quick_selection_highlight_task = Some((
7145 query_range.clone(),
7146 self.update_selection_occurrence_highlights(
7147 query_text.clone(),
7148 query_range.clone(),
7149 multi_buffer_visible_range,
7150 false,
7151 window,
7152 cx,
7153 ),
7154 ));
7155 }
7156 if on_buffer_edit
7157 || self
7158 .debounced_selection_highlight_task
7159 .as_ref()
7160 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7161 {
7162 let multi_buffer_start = multi_buffer_snapshot
7163 .anchor_before(0)
7164 .to_point(&multi_buffer_snapshot);
7165 let multi_buffer_end = multi_buffer_snapshot
7166 .anchor_after(multi_buffer_snapshot.len())
7167 .to_point(&multi_buffer_snapshot);
7168 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7169 self.debounced_selection_highlight_task = Some((
7170 query_range.clone(),
7171 self.update_selection_occurrence_highlights(
7172 query_text,
7173 query_range,
7174 multi_buffer_full_range,
7175 true,
7176 window,
7177 cx,
7178 ),
7179 ));
7180 }
7181 }
7182
7183 pub fn refresh_edit_prediction(
7184 &mut self,
7185 debounce: bool,
7186 user_requested: bool,
7187 window: &mut Window,
7188 cx: &mut Context<Self>,
7189 ) -> Option<()> {
7190 if DisableAiSettings::get_global(cx).disable_ai {
7191 return None;
7192 }
7193
7194 let provider = self.edit_prediction_provider()?;
7195 let cursor = self.selections.newest_anchor().head();
7196 let (buffer, cursor_buffer_position) =
7197 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7198
7199 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7200 self.discard_edit_prediction(false, cx);
7201 return None;
7202 }
7203
7204 self.update_visible_edit_prediction(window, cx);
7205
7206 if !user_requested
7207 && (!self.should_show_edit_predictions()
7208 || !self.is_focused(window)
7209 || buffer.read(cx).is_empty())
7210 {
7211 self.discard_edit_prediction(false, cx);
7212 return None;
7213 }
7214
7215 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7216 Some(())
7217 }
7218
7219 fn show_edit_predictions_in_menu(&self) -> bool {
7220 match self.edit_prediction_settings {
7221 EditPredictionSettings::Disabled => false,
7222 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7223 }
7224 }
7225
7226 pub fn edit_predictions_enabled(&self) -> bool {
7227 match self.edit_prediction_settings {
7228 EditPredictionSettings::Disabled => false,
7229 EditPredictionSettings::Enabled { .. } => true,
7230 }
7231 }
7232
7233 fn edit_prediction_requires_modifier(&self) -> bool {
7234 match self.edit_prediction_settings {
7235 EditPredictionSettings::Disabled => false,
7236 EditPredictionSettings::Enabled {
7237 preview_requires_modifier,
7238 ..
7239 } => preview_requires_modifier,
7240 }
7241 }
7242
7243 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7244 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7245 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7246 self.discard_edit_prediction(false, cx);
7247 } else {
7248 let selection = self.selections.newest_anchor();
7249 let cursor = selection.head();
7250
7251 if let Some((buffer, cursor_buffer_position)) =
7252 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7253 {
7254 self.edit_prediction_settings =
7255 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7256 }
7257 }
7258 }
7259
7260 fn edit_prediction_settings_at_position(
7261 &self,
7262 buffer: &Entity<Buffer>,
7263 buffer_position: language::Anchor,
7264 cx: &App,
7265 ) -> EditPredictionSettings {
7266 if !self.mode.is_full()
7267 || !self.show_edit_predictions_override.unwrap_or(true)
7268 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7269 {
7270 return EditPredictionSettings::Disabled;
7271 }
7272
7273 let buffer = buffer.read(cx);
7274
7275 let file = buffer.file();
7276
7277 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7278 return EditPredictionSettings::Disabled;
7279 };
7280
7281 let by_provider = matches!(
7282 self.menu_edit_predictions_policy,
7283 MenuEditPredictionsPolicy::ByProvider
7284 );
7285
7286 let show_in_menu = by_provider
7287 && self
7288 .edit_prediction_provider
7289 .as_ref()
7290 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7291
7292 let preview_requires_modifier =
7293 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7294
7295 EditPredictionSettings::Enabled {
7296 show_in_menu,
7297 preview_requires_modifier,
7298 }
7299 }
7300
7301 fn should_show_edit_predictions(&self) -> bool {
7302 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7303 }
7304
7305 pub fn edit_prediction_preview_is_active(&self) -> bool {
7306 matches!(
7307 self.edit_prediction_preview,
7308 EditPredictionPreview::Active { .. }
7309 )
7310 }
7311
7312 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7313 let cursor = self.selections.newest_anchor().head();
7314 if let Some((buffer, cursor_position)) =
7315 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7316 {
7317 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7318 } else {
7319 false
7320 }
7321 }
7322
7323 pub fn supports_minimap(&self, cx: &App) -> bool {
7324 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7325 }
7326
7327 fn edit_predictions_enabled_in_buffer(
7328 &self,
7329 buffer: &Entity<Buffer>,
7330 buffer_position: language::Anchor,
7331 cx: &App,
7332 ) -> bool {
7333 maybe!({
7334 if self.read_only(cx) {
7335 return Some(false);
7336 }
7337 let provider = self.edit_prediction_provider()?;
7338 if !provider.is_enabled(buffer, buffer_position, cx) {
7339 return Some(false);
7340 }
7341 let buffer = buffer.read(cx);
7342 let Some(file) = buffer.file() else {
7343 return Some(true);
7344 };
7345 let settings = all_language_settings(Some(file), cx);
7346 Some(settings.edit_predictions_enabled_for_file(file, cx))
7347 })
7348 .unwrap_or(false)
7349 }
7350
7351 fn cycle_edit_prediction(
7352 &mut self,
7353 direction: Direction,
7354 window: &mut Window,
7355 cx: &mut Context<Self>,
7356 ) -> Option<()> {
7357 let provider = self.edit_prediction_provider()?;
7358 let cursor = self.selections.newest_anchor().head();
7359 let (buffer, cursor_buffer_position) =
7360 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7361 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7362 return None;
7363 }
7364
7365 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7366 self.update_visible_edit_prediction(window, cx);
7367
7368 Some(())
7369 }
7370
7371 pub fn show_edit_prediction(
7372 &mut self,
7373 _: &ShowEditPrediction,
7374 window: &mut Window,
7375 cx: &mut Context<Self>,
7376 ) {
7377 if !self.has_active_edit_prediction() {
7378 self.refresh_edit_prediction(false, true, window, cx);
7379 return;
7380 }
7381
7382 self.update_visible_edit_prediction(window, cx);
7383 }
7384
7385 pub fn display_cursor_names(
7386 &mut self,
7387 _: &DisplayCursorNames,
7388 window: &mut Window,
7389 cx: &mut Context<Self>,
7390 ) {
7391 self.show_cursor_names(window, cx);
7392 }
7393
7394 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7395 self.show_cursor_names = true;
7396 cx.notify();
7397 cx.spawn_in(window, async move |this, cx| {
7398 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7399 this.update(cx, |this, cx| {
7400 this.show_cursor_names = false;
7401 cx.notify()
7402 })
7403 .ok()
7404 })
7405 .detach();
7406 }
7407
7408 pub fn next_edit_prediction(
7409 &mut self,
7410 _: &NextEditPrediction,
7411 window: &mut Window,
7412 cx: &mut Context<Self>,
7413 ) {
7414 if self.has_active_edit_prediction() {
7415 self.cycle_edit_prediction(Direction::Next, window, cx);
7416 } else {
7417 let is_copilot_disabled = self
7418 .refresh_edit_prediction(false, true, window, cx)
7419 .is_none();
7420 if is_copilot_disabled {
7421 cx.propagate();
7422 }
7423 }
7424 }
7425
7426 pub fn previous_edit_prediction(
7427 &mut self,
7428 _: &PreviousEditPrediction,
7429 window: &mut Window,
7430 cx: &mut Context<Self>,
7431 ) {
7432 if self.has_active_edit_prediction() {
7433 self.cycle_edit_prediction(Direction::Prev, window, cx);
7434 } else {
7435 let is_copilot_disabled = self
7436 .refresh_edit_prediction(false, true, window, cx)
7437 .is_none();
7438 if is_copilot_disabled {
7439 cx.propagate();
7440 }
7441 }
7442 }
7443
7444 pub fn accept_edit_prediction(
7445 &mut self,
7446 _: &AcceptEditPrediction,
7447 window: &mut Window,
7448 cx: &mut Context<Self>,
7449 ) {
7450 if self.show_edit_predictions_in_menu() {
7451 self.hide_context_menu(window, cx);
7452 }
7453
7454 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7455 return;
7456 };
7457
7458 match &active_edit_prediction.completion {
7459 EditPrediction::MoveWithin { target, .. } => {
7460 let target = *target;
7461
7462 if let Some(position_map) = &self.last_position_map {
7463 if position_map
7464 .visible_row_range
7465 .contains(&target.to_display_point(&position_map.snapshot).row())
7466 || !self.edit_prediction_requires_modifier()
7467 {
7468 self.unfold_ranges(&[target..target], true, false, cx);
7469 // Note that this is also done in vim's handler of the Tab action.
7470 self.change_selections(
7471 SelectionEffects::scroll(Autoscroll::newest()),
7472 window,
7473 cx,
7474 |selections| {
7475 selections.select_anchor_ranges([target..target]);
7476 },
7477 );
7478 self.clear_row_highlights::<EditPredictionPreview>();
7479
7480 self.edit_prediction_preview
7481 .set_previous_scroll_position(None);
7482 } else {
7483 self.edit_prediction_preview
7484 .set_previous_scroll_position(Some(
7485 position_map.snapshot.scroll_anchor,
7486 ));
7487
7488 self.highlight_rows::<EditPredictionPreview>(
7489 target..target,
7490 cx.theme().colors().editor_highlighted_line_background,
7491 RowHighlightOptions {
7492 autoscroll: true,
7493 ..Default::default()
7494 },
7495 cx,
7496 );
7497 self.request_autoscroll(Autoscroll::fit(), cx);
7498 }
7499 }
7500 }
7501 EditPrediction::MoveOutside { snapshot, target } => {
7502 if let Some(workspace) = self.workspace() {
7503 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7504 .detach_and_log_err(cx);
7505 }
7506 }
7507 EditPrediction::Edit { edits, .. } => {
7508 self.report_edit_prediction_event(
7509 active_edit_prediction.completion_id.clone(),
7510 true,
7511 cx,
7512 );
7513
7514 if let Some(provider) = self.edit_prediction_provider() {
7515 provider.accept(cx);
7516 }
7517
7518 // Store the transaction ID and selections before applying the edit
7519 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7520
7521 let snapshot = self.buffer.read(cx).snapshot(cx);
7522 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7523
7524 self.buffer.update(cx, |buffer, cx| {
7525 buffer.edit(edits.iter().cloned(), None, cx)
7526 });
7527
7528 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7529 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7530 });
7531
7532 let selections = self.selections.disjoint_anchors_arc();
7533 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7534 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7535 if has_new_transaction {
7536 self.selection_history
7537 .insert_transaction(transaction_id_now, selections);
7538 }
7539 }
7540
7541 self.update_visible_edit_prediction(window, cx);
7542 if self.active_edit_prediction.is_none() {
7543 self.refresh_edit_prediction(true, true, window, cx);
7544 }
7545
7546 cx.notify();
7547 }
7548 }
7549
7550 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7551 }
7552
7553 pub fn accept_partial_edit_prediction(
7554 &mut self,
7555 _: &AcceptPartialEditPrediction,
7556 window: &mut Window,
7557 cx: &mut Context<Self>,
7558 ) {
7559 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7560 return;
7561 };
7562 if self.selections.count() != 1 {
7563 return;
7564 }
7565
7566 match &active_edit_prediction.completion {
7567 EditPrediction::MoveWithin { target, .. } => {
7568 let target = *target;
7569 self.change_selections(
7570 SelectionEffects::scroll(Autoscroll::newest()),
7571 window,
7572 cx,
7573 |selections| {
7574 selections.select_anchor_ranges([target..target]);
7575 },
7576 );
7577 }
7578 EditPrediction::MoveOutside { snapshot, target } => {
7579 if let Some(workspace) = self.workspace() {
7580 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7581 .detach_and_log_err(cx);
7582 }
7583 }
7584 EditPrediction::Edit { edits, .. } => {
7585 self.report_edit_prediction_event(
7586 active_edit_prediction.completion_id.clone(),
7587 true,
7588 cx,
7589 );
7590
7591 // Find an insertion that starts at the cursor position.
7592 let snapshot = self.buffer.read(cx).snapshot(cx);
7593 let cursor_offset = self.selections.newest::<usize>(cx).head();
7594 let insertion = edits.iter().find_map(|(range, text)| {
7595 let range = range.to_offset(&snapshot);
7596 if range.is_empty() && range.start == cursor_offset {
7597 Some(text)
7598 } else {
7599 None
7600 }
7601 });
7602
7603 if let Some(text) = insertion {
7604 let mut partial_completion = text
7605 .chars()
7606 .by_ref()
7607 .take_while(|c| c.is_alphabetic())
7608 .collect::<String>();
7609 if partial_completion.is_empty() {
7610 partial_completion = text
7611 .chars()
7612 .by_ref()
7613 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7614 .collect::<String>();
7615 }
7616
7617 cx.emit(EditorEvent::InputHandled {
7618 utf16_range_to_replace: None,
7619 text: partial_completion.clone().into(),
7620 });
7621
7622 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7623
7624 self.refresh_edit_prediction(true, true, window, cx);
7625 cx.notify();
7626 } else {
7627 self.accept_edit_prediction(&Default::default(), window, cx);
7628 }
7629 }
7630 }
7631 }
7632
7633 fn discard_edit_prediction(
7634 &mut self,
7635 should_report_edit_prediction_event: bool,
7636 cx: &mut Context<Self>,
7637 ) -> bool {
7638 if should_report_edit_prediction_event {
7639 let completion_id = self
7640 .active_edit_prediction
7641 .as_ref()
7642 .and_then(|active_completion| active_completion.completion_id.clone());
7643
7644 self.report_edit_prediction_event(completion_id, false, cx);
7645 }
7646
7647 if let Some(provider) = self.edit_prediction_provider() {
7648 provider.discard(cx);
7649 }
7650
7651 self.take_active_edit_prediction(cx)
7652 }
7653
7654 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7655 let Some(provider) = self.edit_prediction_provider() else {
7656 return;
7657 };
7658
7659 let Some((_, buffer, _)) = self
7660 .buffer
7661 .read(cx)
7662 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7663 else {
7664 return;
7665 };
7666
7667 let extension = buffer
7668 .read(cx)
7669 .file()
7670 .and_then(|file| Some(file.path().extension()?.to_string()));
7671
7672 let event_type = match accepted {
7673 true => "Edit Prediction Accepted",
7674 false => "Edit Prediction Discarded",
7675 };
7676 telemetry::event!(
7677 event_type,
7678 provider = provider.name(),
7679 prediction_id = id,
7680 suggestion_accepted = accepted,
7681 file_extension = extension,
7682 );
7683 }
7684
7685 fn open_editor_at_anchor(
7686 snapshot: &language::BufferSnapshot,
7687 target: language::Anchor,
7688 workspace: &Entity<Workspace>,
7689 window: &mut Window,
7690 cx: &mut App,
7691 ) -> Task<Result<()>> {
7692 workspace.update(cx, |workspace, cx| {
7693 let path = snapshot.file().map(|file| file.full_path(cx));
7694 let Some(path) =
7695 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7696 else {
7697 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7698 };
7699 let target = text::ToPoint::to_point(&target, snapshot);
7700 let item = workspace.open_path(path, None, true, window, cx);
7701 window.spawn(cx, async move |cx| {
7702 let Some(editor) = item.await?.downcast::<Editor>() else {
7703 return Ok(());
7704 };
7705 editor
7706 .update_in(cx, |editor, window, cx| {
7707 editor.go_to_singleton_buffer_point(target, window, cx);
7708 })
7709 .ok();
7710 anyhow::Ok(())
7711 })
7712 })
7713 }
7714
7715 pub fn has_active_edit_prediction(&self) -> bool {
7716 self.active_edit_prediction.is_some()
7717 }
7718
7719 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7720 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7721 return false;
7722 };
7723
7724 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7725 self.clear_highlights::<EditPredictionHighlight>(cx);
7726 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7727 true
7728 }
7729
7730 /// Returns true when we're displaying the edit prediction popover below the cursor
7731 /// like we are not previewing and the LSP autocomplete menu is visible
7732 /// or we are in `when_holding_modifier` mode.
7733 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7734 if self.edit_prediction_preview_is_active()
7735 || !self.show_edit_predictions_in_menu()
7736 || !self.edit_predictions_enabled()
7737 {
7738 return false;
7739 }
7740
7741 if self.has_visible_completions_menu() {
7742 return true;
7743 }
7744
7745 has_completion && self.edit_prediction_requires_modifier()
7746 }
7747
7748 fn handle_modifiers_changed(
7749 &mut self,
7750 modifiers: Modifiers,
7751 position_map: &PositionMap,
7752 window: &mut Window,
7753 cx: &mut Context<Self>,
7754 ) {
7755 if self.show_edit_predictions_in_menu() {
7756 self.update_edit_prediction_preview(&modifiers, window, cx);
7757 }
7758
7759 self.update_selection_mode(&modifiers, position_map, window, cx);
7760
7761 let mouse_position = window.mouse_position();
7762 if !position_map.text_hitbox.is_hovered(window) {
7763 return;
7764 }
7765
7766 self.update_hovered_link(
7767 position_map.point_for_position(mouse_position),
7768 &position_map.snapshot,
7769 modifiers,
7770 window,
7771 cx,
7772 )
7773 }
7774
7775 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7776 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7777 if invert {
7778 match multi_cursor_setting {
7779 MultiCursorModifier::Alt => modifiers.alt,
7780 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7781 }
7782 } else {
7783 match multi_cursor_setting {
7784 MultiCursorModifier::Alt => modifiers.secondary(),
7785 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7786 }
7787 }
7788 }
7789
7790 fn columnar_selection_mode(
7791 modifiers: &Modifiers,
7792 cx: &mut Context<Self>,
7793 ) -> Option<ColumnarMode> {
7794 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7795 if Self::multi_cursor_modifier(false, modifiers, cx) {
7796 Some(ColumnarMode::FromMouse)
7797 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7798 Some(ColumnarMode::FromSelection)
7799 } else {
7800 None
7801 }
7802 } else {
7803 None
7804 }
7805 }
7806
7807 fn update_selection_mode(
7808 &mut self,
7809 modifiers: &Modifiers,
7810 position_map: &PositionMap,
7811 window: &mut Window,
7812 cx: &mut Context<Self>,
7813 ) {
7814 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7815 return;
7816 };
7817 if self.selections.pending_anchor().is_none() {
7818 return;
7819 }
7820
7821 let mouse_position = window.mouse_position();
7822 let point_for_position = position_map.point_for_position(mouse_position);
7823 let position = point_for_position.previous_valid;
7824
7825 self.select(
7826 SelectPhase::BeginColumnar {
7827 position,
7828 reset: false,
7829 mode,
7830 goal_column: point_for_position.exact_unclipped.column(),
7831 },
7832 window,
7833 cx,
7834 );
7835 }
7836
7837 fn update_edit_prediction_preview(
7838 &mut self,
7839 modifiers: &Modifiers,
7840 window: &mut Window,
7841 cx: &mut Context<Self>,
7842 ) {
7843 let mut modifiers_held = false;
7844 if let Some(accept_keystroke) = self
7845 .accept_edit_prediction_keybind(false, window, cx)
7846 .keystroke()
7847 {
7848 modifiers_held = modifiers_held
7849 || (accept_keystroke.modifiers() == modifiers
7850 && accept_keystroke.modifiers().modified());
7851 };
7852 if let Some(accept_partial_keystroke) = self
7853 .accept_edit_prediction_keybind(true, window, cx)
7854 .keystroke()
7855 {
7856 modifiers_held = modifiers_held
7857 || (accept_partial_keystroke.modifiers() == modifiers
7858 && accept_partial_keystroke.modifiers().modified());
7859 }
7860
7861 if modifiers_held {
7862 if matches!(
7863 self.edit_prediction_preview,
7864 EditPredictionPreview::Inactive { .. }
7865 ) {
7866 self.edit_prediction_preview = EditPredictionPreview::Active {
7867 previous_scroll_position: None,
7868 since: Instant::now(),
7869 };
7870
7871 self.update_visible_edit_prediction(window, cx);
7872 cx.notify();
7873 }
7874 } else if let EditPredictionPreview::Active {
7875 previous_scroll_position,
7876 since,
7877 } = self.edit_prediction_preview
7878 {
7879 if let (Some(previous_scroll_position), Some(position_map)) =
7880 (previous_scroll_position, self.last_position_map.as_ref())
7881 {
7882 self.set_scroll_position(
7883 previous_scroll_position
7884 .scroll_position(&position_map.snapshot.display_snapshot),
7885 window,
7886 cx,
7887 );
7888 }
7889
7890 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7891 released_too_fast: since.elapsed() < Duration::from_millis(200),
7892 };
7893 self.clear_row_highlights::<EditPredictionPreview>();
7894 self.update_visible_edit_prediction(window, cx);
7895 cx.notify();
7896 }
7897 }
7898
7899 fn update_visible_edit_prediction(
7900 &mut self,
7901 _window: &mut Window,
7902 cx: &mut Context<Self>,
7903 ) -> Option<()> {
7904 if DisableAiSettings::get_global(cx).disable_ai {
7905 return None;
7906 }
7907
7908 if self.ime_transaction.is_some() {
7909 self.discard_edit_prediction(false, cx);
7910 return None;
7911 }
7912
7913 let selection = self.selections.newest_anchor();
7914 let cursor = selection.head();
7915 let multibuffer = self.buffer.read(cx).snapshot(cx);
7916 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7917 let excerpt_id = cursor.excerpt_id;
7918
7919 let show_in_menu = self.show_edit_predictions_in_menu();
7920 let completions_menu_has_precedence = !show_in_menu
7921 && (self.context_menu.borrow().is_some()
7922 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7923
7924 if completions_menu_has_precedence
7925 || !offset_selection.is_empty()
7926 || self
7927 .active_edit_prediction
7928 .as_ref()
7929 .is_some_and(|completion| {
7930 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7931 return false;
7932 };
7933 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7934 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7935 !invalidation_range.contains(&offset_selection.head())
7936 })
7937 {
7938 self.discard_edit_prediction(false, cx);
7939 return None;
7940 }
7941
7942 self.take_active_edit_prediction(cx);
7943 let Some(provider) = self.edit_prediction_provider() else {
7944 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7945 return None;
7946 };
7947
7948 let (buffer, cursor_buffer_position) =
7949 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7950
7951 self.edit_prediction_settings =
7952 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7953
7954 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7955
7956 if self.edit_prediction_indent_conflict {
7957 let cursor_point = cursor.to_point(&multibuffer);
7958
7959 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7960
7961 if let Some((_, indent)) = indents.iter().next()
7962 && indent.len == cursor_point.column
7963 {
7964 self.edit_prediction_indent_conflict = false;
7965 }
7966 }
7967
7968 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7969
7970 let (completion_id, edits, edit_preview) = match edit_prediction {
7971 edit_prediction::EditPrediction::Local {
7972 id,
7973 edits,
7974 edit_preview,
7975 } => (id, edits, edit_preview),
7976 edit_prediction::EditPrediction::Jump {
7977 id,
7978 snapshot,
7979 target,
7980 } => {
7981 self.stale_edit_prediction_in_menu = None;
7982 self.active_edit_prediction = Some(EditPredictionState {
7983 inlay_ids: vec![],
7984 completion: EditPrediction::MoveOutside { snapshot, target },
7985 completion_id: id,
7986 invalidation_range: None,
7987 });
7988 cx.notify();
7989 return Some(());
7990 }
7991 };
7992
7993 let edits = edits
7994 .into_iter()
7995 .flat_map(|(range, new_text)| {
7996 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7997 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7998 Some((start..end, new_text))
7999 })
8000 .collect::<Vec<_>>();
8001 if edits.is_empty() {
8002 return None;
8003 }
8004
8005 let first_edit_start = edits.first().unwrap().0.start;
8006 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8007 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8008
8009 let last_edit_end = edits.last().unwrap().0.end;
8010 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8011 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8012
8013 let cursor_row = cursor.to_point(&multibuffer).row;
8014
8015 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8016
8017 let mut inlay_ids = Vec::new();
8018 let invalidation_row_range;
8019 let move_invalidation_row_range = if cursor_row < edit_start_row {
8020 Some(cursor_row..edit_end_row)
8021 } else if cursor_row > edit_end_row {
8022 Some(edit_start_row..cursor_row)
8023 } else {
8024 None
8025 };
8026 let supports_jump = self
8027 .edit_prediction_provider
8028 .as_ref()
8029 .map(|provider| provider.provider.supports_jump_to_edit())
8030 .unwrap_or(true);
8031
8032 let is_move = supports_jump
8033 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8034 let completion = if is_move {
8035 invalidation_row_range =
8036 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8037 let target = first_edit_start;
8038 EditPrediction::MoveWithin { target, snapshot }
8039 } else {
8040 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8041 && !self.edit_predictions_hidden_for_vim_mode;
8042
8043 if show_completions_in_buffer {
8044 if edits
8045 .iter()
8046 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8047 {
8048 let mut inlays = Vec::new();
8049 for (range, new_text) in &edits {
8050 let inlay = Inlay::edit_prediction(
8051 post_inc(&mut self.next_inlay_id),
8052 range.start,
8053 new_text.as_str(),
8054 );
8055 inlay_ids.push(inlay.id);
8056 inlays.push(inlay);
8057 }
8058
8059 self.splice_inlays(&[], inlays, cx);
8060 } else {
8061 let background_color = cx.theme().status().deleted_background;
8062 self.highlight_text::<EditPredictionHighlight>(
8063 edits.iter().map(|(range, _)| range.clone()).collect(),
8064 HighlightStyle {
8065 background_color: Some(background_color),
8066 ..Default::default()
8067 },
8068 cx,
8069 );
8070 }
8071 }
8072
8073 invalidation_row_range = edit_start_row..edit_end_row;
8074
8075 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8076 if provider.show_tab_accept_marker() {
8077 EditDisplayMode::TabAccept
8078 } else {
8079 EditDisplayMode::Inline
8080 }
8081 } else {
8082 EditDisplayMode::DiffPopover
8083 };
8084
8085 EditPrediction::Edit {
8086 edits,
8087 edit_preview,
8088 display_mode,
8089 snapshot,
8090 }
8091 };
8092
8093 let invalidation_range = multibuffer
8094 .anchor_before(Point::new(invalidation_row_range.start, 0))
8095 ..multibuffer.anchor_after(Point::new(
8096 invalidation_row_range.end,
8097 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8098 ));
8099
8100 self.stale_edit_prediction_in_menu = None;
8101 self.active_edit_prediction = Some(EditPredictionState {
8102 inlay_ids,
8103 completion,
8104 completion_id,
8105 invalidation_range: Some(invalidation_range),
8106 });
8107
8108 cx.notify();
8109
8110 Some(())
8111 }
8112
8113 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8114 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8115 }
8116
8117 fn clear_tasks(&mut self) {
8118 self.tasks.clear()
8119 }
8120
8121 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8122 if self.tasks.insert(key, value).is_some() {
8123 // This case should hopefully be rare, but just in case...
8124 log::error!(
8125 "multiple different run targets found on a single line, only the last target will be rendered"
8126 )
8127 }
8128 }
8129
8130 /// Get all display points of breakpoints that will be rendered within editor
8131 ///
8132 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8133 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8134 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8135 fn active_breakpoints(
8136 &self,
8137 range: Range<DisplayRow>,
8138 window: &mut Window,
8139 cx: &mut Context<Self>,
8140 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8141 let mut breakpoint_display_points = HashMap::default();
8142
8143 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8144 return breakpoint_display_points;
8145 };
8146
8147 let snapshot = self.snapshot(window, cx);
8148
8149 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8150 let Some(project) = self.project() else {
8151 return breakpoint_display_points;
8152 };
8153
8154 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8155 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8156
8157 for (buffer_snapshot, range, excerpt_id) in
8158 multi_buffer_snapshot.range_to_buffer_ranges(range)
8159 {
8160 let Some(buffer) = project
8161 .read(cx)
8162 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8163 else {
8164 continue;
8165 };
8166 let breakpoints = breakpoint_store.read(cx).breakpoints(
8167 &buffer,
8168 Some(
8169 buffer_snapshot.anchor_before(range.start)
8170 ..buffer_snapshot.anchor_after(range.end),
8171 ),
8172 buffer_snapshot,
8173 cx,
8174 );
8175 for (breakpoint, state) in breakpoints {
8176 let multi_buffer_anchor =
8177 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8178 let position = multi_buffer_anchor
8179 .to_point(multi_buffer_snapshot)
8180 .to_display_point(&snapshot);
8181
8182 breakpoint_display_points.insert(
8183 position.row(),
8184 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8185 );
8186 }
8187 }
8188
8189 breakpoint_display_points
8190 }
8191
8192 fn breakpoint_context_menu(
8193 &self,
8194 anchor: Anchor,
8195 window: &mut Window,
8196 cx: &mut Context<Self>,
8197 ) -> Entity<ui::ContextMenu> {
8198 let weak_editor = cx.weak_entity();
8199 let focus_handle = self.focus_handle(cx);
8200
8201 let row = self
8202 .buffer
8203 .read(cx)
8204 .snapshot(cx)
8205 .summary_for_anchor::<Point>(&anchor)
8206 .row;
8207
8208 let breakpoint = self
8209 .breakpoint_at_row(row, window, cx)
8210 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8211
8212 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8213 "Edit Log Breakpoint"
8214 } else {
8215 "Set Log Breakpoint"
8216 };
8217
8218 let condition_breakpoint_msg = if breakpoint
8219 .as_ref()
8220 .is_some_and(|bp| bp.1.condition.is_some())
8221 {
8222 "Edit Condition Breakpoint"
8223 } else {
8224 "Set Condition Breakpoint"
8225 };
8226
8227 let hit_condition_breakpoint_msg = if breakpoint
8228 .as_ref()
8229 .is_some_and(|bp| bp.1.hit_condition.is_some())
8230 {
8231 "Edit Hit Condition Breakpoint"
8232 } else {
8233 "Set Hit Condition Breakpoint"
8234 };
8235
8236 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8237 "Unset Breakpoint"
8238 } else {
8239 "Set Breakpoint"
8240 };
8241
8242 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8243
8244 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8245 BreakpointState::Enabled => Some("Disable"),
8246 BreakpointState::Disabled => Some("Enable"),
8247 });
8248
8249 let (anchor, breakpoint) =
8250 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8251
8252 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8253 menu.on_blur_subscription(Subscription::new(|| {}))
8254 .context(focus_handle)
8255 .when(run_to_cursor, |this| {
8256 let weak_editor = weak_editor.clone();
8257 this.entry("Run to cursor", None, move |window, cx| {
8258 weak_editor
8259 .update(cx, |editor, cx| {
8260 editor.change_selections(
8261 SelectionEffects::no_scroll(),
8262 window,
8263 cx,
8264 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8265 );
8266 })
8267 .ok();
8268
8269 window.dispatch_action(Box::new(RunToCursor), cx);
8270 })
8271 .separator()
8272 })
8273 .when_some(toggle_state_msg, |this, msg| {
8274 this.entry(msg, None, {
8275 let weak_editor = weak_editor.clone();
8276 let breakpoint = breakpoint.clone();
8277 move |_window, cx| {
8278 weak_editor
8279 .update(cx, |this, cx| {
8280 this.edit_breakpoint_at_anchor(
8281 anchor,
8282 breakpoint.as_ref().clone(),
8283 BreakpointEditAction::InvertState,
8284 cx,
8285 );
8286 })
8287 .log_err();
8288 }
8289 })
8290 })
8291 .entry(set_breakpoint_msg, None, {
8292 let weak_editor = weak_editor.clone();
8293 let breakpoint = breakpoint.clone();
8294 move |_window, cx| {
8295 weak_editor
8296 .update(cx, |this, cx| {
8297 this.edit_breakpoint_at_anchor(
8298 anchor,
8299 breakpoint.as_ref().clone(),
8300 BreakpointEditAction::Toggle,
8301 cx,
8302 );
8303 })
8304 .log_err();
8305 }
8306 })
8307 .entry(log_breakpoint_msg, None, {
8308 let breakpoint = breakpoint.clone();
8309 let weak_editor = weak_editor.clone();
8310 move |window, cx| {
8311 weak_editor
8312 .update(cx, |this, cx| {
8313 this.add_edit_breakpoint_block(
8314 anchor,
8315 breakpoint.as_ref(),
8316 BreakpointPromptEditAction::Log,
8317 window,
8318 cx,
8319 );
8320 })
8321 .log_err();
8322 }
8323 })
8324 .entry(condition_breakpoint_msg, None, {
8325 let breakpoint = breakpoint.clone();
8326 let weak_editor = weak_editor.clone();
8327 move |window, cx| {
8328 weak_editor
8329 .update(cx, |this, cx| {
8330 this.add_edit_breakpoint_block(
8331 anchor,
8332 breakpoint.as_ref(),
8333 BreakpointPromptEditAction::Condition,
8334 window,
8335 cx,
8336 );
8337 })
8338 .log_err();
8339 }
8340 })
8341 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8342 weak_editor
8343 .update(cx, |this, cx| {
8344 this.add_edit_breakpoint_block(
8345 anchor,
8346 breakpoint.as_ref(),
8347 BreakpointPromptEditAction::HitCondition,
8348 window,
8349 cx,
8350 );
8351 })
8352 .log_err();
8353 })
8354 })
8355 }
8356
8357 fn render_breakpoint(
8358 &self,
8359 position: Anchor,
8360 row: DisplayRow,
8361 breakpoint: &Breakpoint,
8362 state: Option<BreakpointSessionState>,
8363 cx: &mut Context<Self>,
8364 ) -> IconButton {
8365 let is_rejected = state.is_some_and(|s| !s.verified);
8366 // Is it a breakpoint that shows up when hovering over gutter?
8367 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8368 (false, false),
8369 |PhantomBreakpointIndicator {
8370 is_active,
8371 display_row,
8372 collides_with_existing_breakpoint,
8373 }| {
8374 (
8375 is_active && display_row == row,
8376 collides_with_existing_breakpoint,
8377 )
8378 },
8379 );
8380
8381 let (color, icon) = {
8382 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8383 (false, false) => ui::IconName::DebugBreakpoint,
8384 (true, false) => ui::IconName::DebugLogBreakpoint,
8385 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8386 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8387 };
8388
8389 let color = if is_phantom {
8390 Color::Hint
8391 } else if is_rejected {
8392 Color::Disabled
8393 } else {
8394 Color::Debugger
8395 };
8396
8397 (color, icon)
8398 };
8399
8400 let breakpoint = Arc::from(breakpoint.clone());
8401
8402 let alt_as_text = gpui::Keystroke {
8403 modifiers: Modifiers::secondary_key(),
8404 ..Default::default()
8405 };
8406 let primary_action_text = if breakpoint.is_disabled() {
8407 "Enable breakpoint"
8408 } else if is_phantom && !collides_with_existing {
8409 "Set breakpoint"
8410 } else {
8411 "Unset breakpoint"
8412 };
8413 let focus_handle = self.focus_handle.clone();
8414
8415 let meta = if is_rejected {
8416 SharedString::from("No executable code is associated with this line.")
8417 } else if collides_with_existing && !breakpoint.is_disabled() {
8418 SharedString::from(format!(
8419 "{alt_as_text}-click to disable,\nright-click for more options."
8420 ))
8421 } else {
8422 SharedString::from("Right-click for more options.")
8423 };
8424 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8425 .icon_size(IconSize::XSmall)
8426 .size(ui::ButtonSize::None)
8427 .when(is_rejected, |this| {
8428 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8429 })
8430 .icon_color(color)
8431 .style(ButtonStyle::Transparent)
8432 .on_click(cx.listener({
8433 move |editor, event: &ClickEvent, window, cx| {
8434 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8435 BreakpointEditAction::InvertState
8436 } else {
8437 BreakpointEditAction::Toggle
8438 };
8439
8440 window.focus(&editor.focus_handle(cx));
8441 editor.edit_breakpoint_at_anchor(
8442 position,
8443 breakpoint.as_ref().clone(),
8444 edit_action,
8445 cx,
8446 );
8447 }
8448 }))
8449 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8450 editor.set_breakpoint_context_menu(
8451 row,
8452 Some(position),
8453 event.position(),
8454 window,
8455 cx,
8456 );
8457 }))
8458 .tooltip(move |window, cx| {
8459 Tooltip::with_meta_in(
8460 primary_action_text,
8461 Some(&ToggleBreakpoint),
8462 meta.clone(),
8463 &focus_handle,
8464 window,
8465 cx,
8466 )
8467 })
8468 }
8469
8470 fn build_tasks_context(
8471 project: &Entity<Project>,
8472 buffer: &Entity<Buffer>,
8473 buffer_row: u32,
8474 tasks: &Arc<RunnableTasks>,
8475 cx: &mut Context<Self>,
8476 ) -> Task<Option<task::TaskContext>> {
8477 let position = Point::new(buffer_row, tasks.column);
8478 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8479 let location = Location {
8480 buffer: buffer.clone(),
8481 range: range_start..range_start,
8482 };
8483 // Fill in the environmental variables from the tree-sitter captures
8484 let mut captured_task_variables = TaskVariables::default();
8485 for (capture_name, value) in tasks.extra_variables.clone() {
8486 captured_task_variables.insert(
8487 task::VariableName::Custom(capture_name.into()),
8488 value.clone(),
8489 );
8490 }
8491 project.update(cx, |project, cx| {
8492 project.task_store().update(cx, |task_store, cx| {
8493 task_store.task_context_for_location(captured_task_variables, location, cx)
8494 })
8495 })
8496 }
8497
8498 pub fn spawn_nearest_task(
8499 &mut self,
8500 action: &SpawnNearestTask,
8501 window: &mut Window,
8502 cx: &mut Context<Self>,
8503 ) {
8504 let Some((workspace, _)) = self.workspace.clone() else {
8505 return;
8506 };
8507 let Some(project) = self.project.clone() else {
8508 return;
8509 };
8510
8511 // Try to find a closest, enclosing node using tree-sitter that has a task
8512 let Some((buffer, buffer_row, tasks)) = self
8513 .find_enclosing_node_task(cx)
8514 // Or find the task that's closest in row-distance.
8515 .or_else(|| self.find_closest_task(cx))
8516 else {
8517 return;
8518 };
8519
8520 let reveal_strategy = action.reveal;
8521 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8522 cx.spawn_in(window, async move |_, cx| {
8523 let context = task_context.await?;
8524 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8525
8526 let resolved = &mut resolved_task.resolved;
8527 resolved.reveal = reveal_strategy;
8528
8529 workspace
8530 .update_in(cx, |workspace, window, cx| {
8531 workspace.schedule_resolved_task(
8532 task_source_kind,
8533 resolved_task,
8534 false,
8535 window,
8536 cx,
8537 );
8538 })
8539 .ok()
8540 })
8541 .detach();
8542 }
8543
8544 fn find_closest_task(
8545 &mut self,
8546 cx: &mut Context<Self>,
8547 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8548 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8549
8550 let ((buffer_id, row), tasks) = self
8551 .tasks
8552 .iter()
8553 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8554
8555 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8556 let tasks = Arc::new(tasks.to_owned());
8557 Some((buffer, *row, tasks))
8558 }
8559
8560 fn find_enclosing_node_task(
8561 &mut self,
8562 cx: &mut Context<Self>,
8563 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8564 let snapshot = self.buffer.read(cx).snapshot(cx);
8565 let offset = self.selections.newest::<usize>(cx).head();
8566 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8567 let buffer_id = excerpt.buffer().remote_id();
8568
8569 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8570 let mut cursor = layer.node().walk();
8571
8572 while cursor.goto_first_child_for_byte(offset).is_some() {
8573 if cursor.node().end_byte() == offset {
8574 cursor.goto_next_sibling();
8575 }
8576 }
8577
8578 // Ascend to the smallest ancestor that contains the range and has a task.
8579 loop {
8580 let node = cursor.node();
8581 let node_range = node.byte_range();
8582 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8583
8584 // Check if this node contains our offset
8585 if node_range.start <= offset && node_range.end >= offset {
8586 // If it contains offset, check for task
8587 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8588 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8589 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8590 }
8591 }
8592
8593 if !cursor.goto_parent() {
8594 break;
8595 }
8596 }
8597 None
8598 }
8599
8600 fn render_run_indicator(
8601 &self,
8602 _style: &EditorStyle,
8603 is_active: bool,
8604 row: DisplayRow,
8605 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8606 cx: &mut Context<Self>,
8607 ) -> IconButton {
8608 let color = Color::Muted;
8609 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8610
8611 IconButton::new(
8612 ("run_indicator", row.0 as usize),
8613 ui::IconName::PlayOutlined,
8614 )
8615 .shape(ui::IconButtonShape::Square)
8616 .icon_size(IconSize::XSmall)
8617 .icon_color(color)
8618 .toggle_state(is_active)
8619 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8620 let quick_launch = match e {
8621 ClickEvent::Keyboard(_) => true,
8622 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8623 };
8624
8625 window.focus(&editor.focus_handle(cx));
8626 editor.toggle_code_actions(
8627 &ToggleCodeActions {
8628 deployed_from: Some(CodeActionSource::RunMenu(row)),
8629 quick_launch,
8630 },
8631 window,
8632 cx,
8633 );
8634 }))
8635 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8636 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8637 }))
8638 }
8639
8640 pub fn context_menu_visible(&self) -> bool {
8641 !self.edit_prediction_preview_is_active()
8642 && self
8643 .context_menu
8644 .borrow()
8645 .as_ref()
8646 .is_some_and(|menu| menu.visible())
8647 }
8648
8649 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8650 self.context_menu
8651 .borrow()
8652 .as_ref()
8653 .map(|menu| menu.origin())
8654 }
8655
8656 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8657 self.context_menu_options = Some(options);
8658 }
8659
8660 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8661 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8662
8663 fn render_edit_prediction_popover(
8664 &mut self,
8665 text_bounds: &Bounds<Pixels>,
8666 content_origin: gpui::Point<Pixels>,
8667 right_margin: Pixels,
8668 editor_snapshot: &EditorSnapshot,
8669 visible_row_range: Range<DisplayRow>,
8670 scroll_top: ScrollOffset,
8671 scroll_bottom: ScrollOffset,
8672 line_layouts: &[LineWithInvisibles],
8673 line_height: Pixels,
8674 scroll_position: gpui::Point<ScrollOffset>,
8675 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8676 newest_selection_head: Option<DisplayPoint>,
8677 editor_width: Pixels,
8678 style: &EditorStyle,
8679 window: &mut Window,
8680 cx: &mut App,
8681 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8682 if self.mode().is_minimap() {
8683 return None;
8684 }
8685 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8686
8687 if self.edit_prediction_visible_in_cursor_popover(true) {
8688 return None;
8689 }
8690
8691 match &active_edit_prediction.completion {
8692 EditPrediction::MoveWithin { target, .. } => {
8693 let target_display_point = target.to_display_point(editor_snapshot);
8694
8695 if self.edit_prediction_requires_modifier() {
8696 if !self.edit_prediction_preview_is_active() {
8697 return None;
8698 }
8699
8700 self.render_edit_prediction_modifier_jump_popover(
8701 text_bounds,
8702 content_origin,
8703 visible_row_range,
8704 line_layouts,
8705 line_height,
8706 scroll_pixel_position,
8707 newest_selection_head,
8708 target_display_point,
8709 window,
8710 cx,
8711 )
8712 } else {
8713 self.render_edit_prediction_eager_jump_popover(
8714 text_bounds,
8715 content_origin,
8716 editor_snapshot,
8717 visible_row_range,
8718 scroll_top,
8719 scroll_bottom,
8720 line_height,
8721 scroll_pixel_position,
8722 target_display_point,
8723 editor_width,
8724 window,
8725 cx,
8726 )
8727 }
8728 }
8729 EditPrediction::Edit {
8730 display_mode: EditDisplayMode::Inline,
8731 ..
8732 } => None,
8733 EditPrediction::Edit {
8734 display_mode: EditDisplayMode::TabAccept,
8735 edits,
8736 ..
8737 } => {
8738 let range = &edits.first()?.0;
8739 let target_display_point = range.end.to_display_point(editor_snapshot);
8740
8741 self.render_edit_prediction_end_of_line_popover(
8742 "Accept",
8743 editor_snapshot,
8744 visible_row_range,
8745 target_display_point,
8746 line_height,
8747 scroll_pixel_position,
8748 content_origin,
8749 editor_width,
8750 window,
8751 cx,
8752 )
8753 }
8754 EditPrediction::Edit {
8755 edits,
8756 edit_preview,
8757 display_mode: EditDisplayMode::DiffPopover,
8758 snapshot,
8759 } => self.render_edit_prediction_diff_popover(
8760 text_bounds,
8761 content_origin,
8762 right_margin,
8763 editor_snapshot,
8764 visible_row_range,
8765 line_layouts,
8766 line_height,
8767 scroll_position,
8768 scroll_pixel_position,
8769 newest_selection_head,
8770 editor_width,
8771 style,
8772 edits,
8773 edit_preview,
8774 snapshot,
8775 window,
8776 cx,
8777 ),
8778 EditPrediction::MoveOutside { snapshot, .. } => {
8779 let file_name = snapshot
8780 .file()
8781 .map(|file| file.file_name(cx))
8782 .unwrap_or("untitled");
8783 let mut element = self
8784 .render_edit_prediction_line_popover(
8785 format!("Jump to {file_name}"),
8786 Some(IconName::ZedPredict),
8787 window,
8788 cx,
8789 )
8790 .into_any();
8791
8792 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8793 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8794 let origin_y = text_bounds.size.height - size.height - px(30.);
8795 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8796 element.prepaint_at(origin, window, cx);
8797
8798 Some((element, origin))
8799 }
8800 }
8801 }
8802
8803 fn render_edit_prediction_modifier_jump_popover(
8804 &mut self,
8805 text_bounds: &Bounds<Pixels>,
8806 content_origin: gpui::Point<Pixels>,
8807 visible_row_range: Range<DisplayRow>,
8808 line_layouts: &[LineWithInvisibles],
8809 line_height: Pixels,
8810 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8811 newest_selection_head: Option<DisplayPoint>,
8812 target_display_point: DisplayPoint,
8813 window: &mut Window,
8814 cx: &mut App,
8815 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8816 let scrolled_content_origin =
8817 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8818
8819 const SCROLL_PADDING_Y: Pixels = px(12.);
8820
8821 if target_display_point.row() < visible_row_range.start {
8822 return self.render_edit_prediction_scroll_popover(
8823 |_| SCROLL_PADDING_Y,
8824 IconName::ArrowUp,
8825 visible_row_range,
8826 line_layouts,
8827 newest_selection_head,
8828 scrolled_content_origin,
8829 window,
8830 cx,
8831 );
8832 } else if target_display_point.row() >= visible_row_range.end {
8833 return self.render_edit_prediction_scroll_popover(
8834 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8835 IconName::ArrowDown,
8836 visible_row_range,
8837 line_layouts,
8838 newest_selection_head,
8839 scrolled_content_origin,
8840 window,
8841 cx,
8842 );
8843 }
8844
8845 const POLE_WIDTH: Pixels = px(2.);
8846
8847 let line_layout =
8848 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8849 let target_column = target_display_point.column() as usize;
8850
8851 let target_x = line_layout.x_for_index(target_column);
8852 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8853 - scroll_pixel_position.y;
8854
8855 let flag_on_right = target_x < text_bounds.size.width / 2.;
8856
8857 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8858 border_color.l += 0.001;
8859
8860 let mut element = v_flex()
8861 .items_end()
8862 .when(flag_on_right, |el| el.items_start())
8863 .child(if flag_on_right {
8864 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8865 .rounded_bl(px(0.))
8866 .rounded_tl(px(0.))
8867 .border_l_2()
8868 .border_color(border_color)
8869 } else {
8870 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8871 .rounded_br(px(0.))
8872 .rounded_tr(px(0.))
8873 .border_r_2()
8874 .border_color(border_color)
8875 })
8876 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8877 .into_any();
8878
8879 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8880
8881 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8882 - point(
8883 if flag_on_right {
8884 POLE_WIDTH
8885 } else {
8886 size.width - POLE_WIDTH
8887 },
8888 size.height - line_height,
8889 );
8890
8891 origin.x = origin.x.max(content_origin.x);
8892
8893 element.prepaint_at(origin, window, cx);
8894
8895 Some((element, origin))
8896 }
8897
8898 fn render_edit_prediction_scroll_popover(
8899 &mut self,
8900 to_y: impl Fn(Size<Pixels>) -> Pixels,
8901 scroll_icon: IconName,
8902 visible_row_range: Range<DisplayRow>,
8903 line_layouts: &[LineWithInvisibles],
8904 newest_selection_head: Option<DisplayPoint>,
8905 scrolled_content_origin: gpui::Point<Pixels>,
8906 window: &mut Window,
8907 cx: &mut App,
8908 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8909 let mut element = self
8910 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8911 .into_any();
8912
8913 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8914
8915 let cursor = newest_selection_head?;
8916 let cursor_row_layout =
8917 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8918 let cursor_column = cursor.column() as usize;
8919
8920 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8921
8922 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8923
8924 element.prepaint_at(origin, window, cx);
8925 Some((element, origin))
8926 }
8927
8928 fn render_edit_prediction_eager_jump_popover(
8929 &mut self,
8930 text_bounds: &Bounds<Pixels>,
8931 content_origin: gpui::Point<Pixels>,
8932 editor_snapshot: &EditorSnapshot,
8933 visible_row_range: Range<DisplayRow>,
8934 scroll_top: ScrollOffset,
8935 scroll_bottom: ScrollOffset,
8936 line_height: Pixels,
8937 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8938 target_display_point: DisplayPoint,
8939 editor_width: Pixels,
8940 window: &mut Window,
8941 cx: &mut App,
8942 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8943 if target_display_point.row().as_f64() < scroll_top {
8944 let mut element = self
8945 .render_edit_prediction_line_popover(
8946 "Jump to Edit",
8947 Some(IconName::ArrowUp),
8948 window,
8949 cx,
8950 )
8951 .into_any();
8952
8953 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8954 let offset = point(
8955 (text_bounds.size.width - size.width) / 2.,
8956 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8957 );
8958
8959 let origin = text_bounds.origin + offset;
8960 element.prepaint_at(origin, window, cx);
8961 Some((element, origin))
8962 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8963 let mut element = self
8964 .render_edit_prediction_line_popover(
8965 "Jump to Edit",
8966 Some(IconName::ArrowDown),
8967 window,
8968 cx,
8969 )
8970 .into_any();
8971
8972 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8973 let offset = point(
8974 (text_bounds.size.width - size.width) / 2.,
8975 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8976 );
8977
8978 let origin = text_bounds.origin + offset;
8979 element.prepaint_at(origin, window, cx);
8980 Some((element, origin))
8981 } else {
8982 self.render_edit_prediction_end_of_line_popover(
8983 "Jump to Edit",
8984 editor_snapshot,
8985 visible_row_range,
8986 target_display_point,
8987 line_height,
8988 scroll_pixel_position,
8989 content_origin,
8990 editor_width,
8991 window,
8992 cx,
8993 )
8994 }
8995 }
8996
8997 fn render_edit_prediction_end_of_line_popover(
8998 self: &mut Editor,
8999 label: &'static str,
9000 editor_snapshot: &EditorSnapshot,
9001 visible_row_range: Range<DisplayRow>,
9002 target_display_point: DisplayPoint,
9003 line_height: Pixels,
9004 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9005 content_origin: gpui::Point<Pixels>,
9006 editor_width: Pixels,
9007 window: &mut Window,
9008 cx: &mut App,
9009 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9010 let target_line_end = DisplayPoint::new(
9011 target_display_point.row(),
9012 editor_snapshot.line_len(target_display_point.row()),
9013 );
9014
9015 let mut element = self
9016 .render_edit_prediction_line_popover(label, None, window, cx)
9017 .into_any();
9018
9019 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9020
9021 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9022
9023 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9024 let mut origin = start_point
9025 + line_origin
9026 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9027 origin.x = origin.x.max(content_origin.x);
9028
9029 let max_x = content_origin.x + editor_width - size.width;
9030
9031 if origin.x > max_x {
9032 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9033
9034 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9035 origin.y += offset;
9036 IconName::ArrowUp
9037 } else {
9038 origin.y -= offset;
9039 IconName::ArrowDown
9040 };
9041
9042 element = self
9043 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9044 .into_any();
9045
9046 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9047
9048 origin.x = content_origin.x + editor_width - size.width - px(2.);
9049 }
9050
9051 element.prepaint_at(origin, window, cx);
9052 Some((element, origin))
9053 }
9054
9055 fn render_edit_prediction_diff_popover(
9056 self: &Editor,
9057 text_bounds: &Bounds<Pixels>,
9058 content_origin: gpui::Point<Pixels>,
9059 right_margin: Pixels,
9060 editor_snapshot: &EditorSnapshot,
9061 visible_row_range: Range<DisplayRow>,
9062 line_layouts: &[LineWithInvisibles],
9063 line_height: Pixels,
9064 scroll_position: gpui::Point<ScrollOffset>,
9065 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9066 newest_selection_head: Option<DisplayPoint>,
9067 editor_width: Pixels,
9068 style: &EditorStyle,
9069 edits: &Vec<(Range<Anchor>, String)>,
9070 edit_preview: &Option<language::EditPreview>,
9071 snapshot: &language::BufferSnapshot,
9072 window: &mut Window,
9073 cx: &mut App,
9074 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9075 let edit_start = edits
9076 .first()
9077 .unwrap()
9078 .0
9079 .start
9080 .to_display_point(editor_snapshot);
9081 let edit_end = edits
9082 .last()
9083 .unwrap()
9084 .0
9085 .end
9086 .to_display_point(editor_snapshot);
9087
9088 let is_visible = visible_row_range.contains(&edit_start.row())
9089 || visible_row_range.contains(&edit_end.row());
9090 if !is_visible {
9091 return None;
9092 }
9093
9094 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9095 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9096 } else {
9097 // Fallback for providers without edit_preview
9098 crate::edit_prediction_fallback_text(edits, cx)
9099 };
9100
9101 let styled_text = highlighted_edits.to_styled_text(&style.text);
9102 let line_count = highlighted_edits.text.lines().count();
9103
9104 const BORDER_WIDTH: Pixels = px(1.);
9105
9106 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9107 let has_keybind = keybind.is_some();
9108
9109 let mut element = h_flex()
9110 .items_start()
9111 .child(
9112 h_flex()
9113 .bg(cx.theme().colors().editor_background)
9114 .border(BORDER_WIDTH)
9115 .shadow_xs()
9116 .border_color(cx.theme().colors().border)
9117 .rounded_l_lg()
9118 .when(line_count > 1, |el| el.rounded_br_lg())
9119 .pr_1()
9120 .child(styled_text),
9121 )
9122 .child(
9123 h_flex()
9124 .h(line_height + BORDER_WIDTH * 2.)
9125 .px_1p5()
9126 .gap_1()
9127 // Workaround: For some reason, there's a gap if we don't do this
9128 .ml(-BORDER_WIDTH)
9129 .shadow(vec![gpui::BoxShadow {
9130 color: gpui::black().opacity(0.05),
9131 offset: point(px(1.), px(1.)),
9132 blur_radius: px(2.),
9133 spread_radius: px(0.),
9134 }])
9135 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9136 .border(BORDER_WIDTH)
9137 .border_color(cx.theme().colors().border)
9138 .rounded_r_lg()
9139 .id("edit_prediction_diff_popover_keybind")
9140 .when(!has_keybind, |el| {
9141 let status_colors = cx.theme().status();
9142
9143 el.bg(status_colors.error_background)
9144 .border_color(status_colors.error.opacity(0.6))
9145 .child(Icon::new(IconName::Info).color(Color::Error))
9146 .cursor_default()
9147 .hoverable_tooltip(move |_window, cx| {
9148 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9149 })
9150 })
9151 .children(keybind),
9152 )
9153 .into_any();
9154
9155 let longest_row =
9156 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9157 let longest_line_width = if visible_row_range.contains(&longest_row) {
9158 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9159 } else {
9160 layout_line(
9161 longest_row,
9162 editor_snapshot,
9163 style,
9164 editor_width,
9165 |_| false,
9166 window,
9167 cx,
9168 )
9169 .width
9170 };
9171
9172 let viewport_bounds =
9173 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9174 right: -right_margin,
9175 ..Default::default()
9176 });
9177
9178 let x_after_longest = Pixels::from(
9179 ScrollPixelOffset::from(
9180 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9181 ) - scroll_pixel_position.x,
9182 );
9183
9184 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9185
9186 // Fully visible if it can be displayed within the window (allow overlapping other
9187 // panes). However, this is only allowed if the popover starts within text_bounds.
9188 let can_position_to_the_right = x_after_longest < text_bounds.right()
9189 && x_after_longest + element_bounds.width < viewport_bounds.right();
9190
9191 let mut origin = if can_position_to_the_right {
9192 point(
9193 x_after_longest,
9194 text_bounds.origin.y
9195 + Pixels::from(
9196 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9197 - scroll_pixel_position.y,
9198 ),
9199 )
9200 } else {
9201 let cursor_row = newest_selection_head.map(|head| head.row());
9202 let above_edit = edit_start
9203 .row()
9204 .0
9205 .checked_sub(line_count as u32)
9206 .map(DisplayRow);
9207 let below_edit = Some(edit_end.row() + 1);
9208 let above_cursor =
9209 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9210 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9211
9212 // Place the edit popover adjacent to the edit if there is a location
9213 // available that is onscreen and does not obscure the cursor. Otherwise,
9214 // place it adjacent to the cursor.
9215 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9216 .into_iter()
9217 .flatten()
9218 .find(|&start_row| {
9219 let end_row = start_row + line_count as u32;
9220 visible_row_range.contains(&start_row)
9221 && visible_row_range.contains(&end_row)
9222 && cursor_row
9223 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9224 })?;
9225
9226 content_origin
9227 + point(
9228 Pixels::from(-scroll_pixel_position.x),
9229 Pixels::from(
9230 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9231 ),
9232 )
9233 };
9234
9235 origin.x -= BORDER_WIDTH;
9236
9237 window.defer_draw(element, origin, 1);
9238
9239 // Do not return an element, since it will already be drawn due to defer_draw.
9240 None
9241 }
9242
9243 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9244 px(30.)
9245 }
9246
9247 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9248 if self.read_only(cx) {
9249 cx.theme().players().read_only()
9250 } else {
9251 self.style.as_ref().unwrap().local_player
9252 }
9253 }
9254
9255 fn render_edit_prediction_accept_keybind(
9256 &self,
9257 window: &mut Window,
9258 cx: &App,
9259 ) -> Option<AnyElement> {
9260 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9261 let accept_keystroke = accept_binding.keystroke()?;
9262
9263 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9264
9265 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9266 Color::Accent
9267 } else {
9268 Color::Muted
9269 };
9270
9271 h_flex()
9272 .px_0p5()
9273 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9274 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9275 .text_size(TextSize::XSmall.rems(cx))
9276 .child(h_flex().children(ui::render_modifiers(
9277 accept_keystroke.modifiers(),
9278 PlatformStyle::platform(),
9279 Some(modifiers_color),
9280 Some(IconSize::XSmall.rems().into()),
9281 true,
9282 )))
9283 .when(is_platform_style_mac, |parent| {
9284 parent.child(accept_keystroke.key().to_string())
9285 })
9286 .when(!is_platform_style_mac, |parent| {
9287 parent.child(
9288 Key::new(
9289 util::capitalize(accept_keystroke.key()),
9290 Some(Color::Default),
9291 )
9292 .size(Some(IconSize::XSmall.rems().into())),
9293 )
9294 })
9295 .into_any()
9296 .into()
9297 }
9298
9299 fn render_edit_prediction_line_popover(
9300 &self,
9301 label: impl Into<SharedString>,
9302 icon: Option<IconName>,
9303 window: &mut Window,
9304 cx: &App,
9305 ) -> Stateful<Div> {
9306 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9307
9308 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9309 let has_keybind = keybind.is_some();
9310
9311 h_flex()
9312 .id("ep-line-popover")
9313 .py_0p5()
9314 .pl_1()
9315 .pr(padding_right)
9316 .gap_1()
9317 .rounded_md()
9318 .border_1()
9319 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9320 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9321 .shadow_xs()
9322 .when(!has_keybind, |el| {
9323 let status_colors = cx.theme().status();
9324
9325 el.bg(status_colors.error_background)
9326 .border_color(status_colors.error.opacity(0.6))
9327 .pl_2()
9328 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9329 .cursor_default()
9330 .hoverable_tooltip(move |_window, cx| {
9331 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9332 })
9333 })
9334 .children(keybind)
9335 .child(
9336 Label::new(label)
9337 .size(LabelSize::Small)
9338 .when(!has_keybind, |el| {
9339 el.color(cx.theme().status().error.into()).strikethrough()
9340 }),
9341 )
9342 .when(!has_keybind, |el| {
9343 el.child(
9344 h_flex().ml_1().child(
9345 Icon::new(IconName::Info)
9346 .size(IconSize::Small)
9347 .color(cx.theme().status().error.into()),
9348 ),
9349 )
9350 })
9351 .when_some(icon, |element, icon| {
9352 element.child(
9353 div()
9354 .mt(px(1.5))
9355 .child(Icon::new(icon).size(IconSize::Small)),
9356 )
9357 })
9358 }
9359
9360 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9361 let accent_color = cx.theme().colors().text_accent;
9362 let editor_bg_color = cx.theme().colors().editor_background;
9363 editor_bg_color.blend(accent_color.opacity(0.1))
9364 }
9365
9366 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9367 let accent_color = cx.theme().colors().text_accent;
9368 let editor_bg_color = cx.theme().colors().editor_background;
9369 editor_bg_color.blend(accent_color.opacity(0.6))
9370 }
9371 fn get_prediction_provider_icon_name(
9372 provider: &Option<RegisteredEditPredictionProvider>,
9373 ) -> IconName {
9374 match provider {
9375 Some(provider) => match provider.provider.name() {
9376 "copilot" => IconName::Copilot,
9377 "supermaven" => IconName::Supermaven,
9378 _ => IconName::ZedPredict,
9379 },
9380 None => IconName::ZedPredict,
9381 }
9382 }
9383
9384 fn render_edit_prediction_cursor_popover(
9385 &self,
9386 min_width: Pixels,
9387 max_width: Pixels,
9388 cursor_point: Point,
9389 style: &EditorStyle,
9390 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9391 _window: &Window,
9392 cx: &mut Context<Editor>,
9393 ) -> Option<AnyElement> {
9394 let provider = self.edit_prediction_provider.as_ref()?;
9395 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9396
9397 let is_refreshing = provider.provider.is_refreshing(cx);
9398
9399 fn pending_completion_container(icon: IconName) -> Div {
9400 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9401 }
9402
9403 let completion = match &self.active_edit_prediction {
9404 Some(prediction) => {
9405 if !self.has_visible_completions_menu() {
9406 const RADIUS: Pixels = px(6.);
9407 const BORDER_WIDTH: Pixels = px(1.);
9408
9409 return Some(
9410 h_flex()
9411 .elevation_2(cx)
9412 .border(BORDER_WIDTH)
9413 .border_color(cx.theme().colors().border)
9414 .when(accept_keystroke.is_none(), |el| {
9415 el.border_color(cx.theme().status().error)
9416 })
9417 .rounded(RADIUS)
9418 .rounded_tl(px(0.))
9419 .overflow_hidden()
9420 .child(div().px_1p5().child(match &prediction.completion {
9421 EditPrediction::MoveWithin { target, snapshot } => {
9422 use text::ToPoint as _;
9423 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9424 {
9425 Icon::new(IconName::ZedPredictDown)
9426 } else {
9427 Icon::new(IconName::ZedPredictUp)
9428 }
9429 }
9430 EditPrediction::MoveOutside { .. } => {
9431 // TODO [zeta2] custom icon for external jump?
9432 Icon::new(provider_icon)
9433 }
9434 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9435 }))
9436 .child(
9437 h_flex()
9438 .gap_1()
9439 .py_1()
9440 .px_2()
9441 .rounded_r(RADIUS - BORDER_WIDTH)
9442 .border_l_1()
9443 .border_color(cx.theme().colors().border)
9444 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9445 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9446 el.child(
9447 Label::new("Hold")
9448 .size(LabelSize::Small)
9449 .when(accept_keystroke.is_none(), |el| {
9450 el.strikethrough()
9451 })
9452 .line_height_style(LineHeightStyle::UiLabel),
9453 )
9454 })
9455 .id("edit_prediction_cursor_popover_keybind")
9456 .when(accept_keystroke.is_none(), |el| {
9457 let status_colors = cx.theme().status();
9458
9459 el.bg(status_colors.error_background)
9460 .border_color(status_colors.error.opacity(0.6))
9461 .child(Icon::new(IconName::Info).color(Color::Error))
9462 .cursor_default()
9463 .hoverable_tooltip(move |_window, cx| {
9464 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9465 .into()
9466 })
9467 })
9468 .when_some(
9469 accept_keystroke.as_ref(),
9470 |el, accept_keystroke| {
9471 el.child(h_flex().children(ui::render_modifiers(
9472 accept_keystroke.modifiers(),
9473 PlatformStyle::platform(),
9474 Some(Color::Default),
9475 Some(IconSize::XSmall.rems().into()),
9476 false,
9477 )))
9478 },
9479 ),
9480 )
9481 .into_any(),
9482 );
9483 }
9484
9485 self.render_edit_prediction_cursor_popover_preview(
9486 prediction,
9487 cursor_point,
9488 style,
9489 cx,
9490 )?
9491 }
9492
9493 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9494 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9495 stale_completion,
9496 cursor_point,
9497 style,
9498 cx,
9499 )?,
9500
9501 None => pending_completion_container(provider_icon)
9502 .child(Label::new("...").size(LabelSize::Small)),
9503 },
9504
9505 None => pending_completion_container(provider_icon)
9506 .child(Label::new("...").size(LabelSize::Small)),
9507 };
9508
9509 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9510 completion
9511 .with_animation(
9512 "loading-completion",
9513 Animation::new(Duration::from_secs(2))
9514 .repeat()
9515 .with_easing(pulsating_between(0.4, 0.8)),
9516 |label, delta| label.opacity(delta),
9517 )
9518 .into_any_element()
9519 } else {
9520 completion.into_any_element()
9521 };
9522
9523 let has_completion = self.active_edit_prediction.is_some();
9524
9525 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9526 Some(
9527 h_flex()
9528 .min_w(min_width)
9529 .max_w(max_width)
9530 .flex_1()
9531 .elevation_2(cx)
9532 .border_color(cx.theme().colors().border)
9533 .child(
9534 div()
9535 .flex_1()
9536 .py_1()
9537 .px_2()
9538 .overflow_hidden()
9539 .child(completion),
9540 )
9541 .when_some(accept_keystroke, |el, accept_keystroke| {
9542 if !accept_keystroke.modifiers().modified() {
9543 return el;
9544 }
9545
9546 el.child(
9547 h_flex()
9548 .h_full()
9549 .border_l_1()
9550 .rounded_r_lg()
9551 .border_color(cx.theme().colors().border)
9552 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9553 .gap_1()
9554 .py_1()
9555 .px_2()
9556 .child(
9557 h_flex()
9558 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9559 .when(is_platform_style_mac, |parent| parent.gap_1())
9560 .child(h_flex().children(ui::render_modifiers(
9561 accept_keystroke.modifiers(),
9562 PlatformStyle::platform(),
9563 Some(if !has_completion {
9564 Color::Muted
9565 } else {
9566 Color::Default
9567 }),
9568 None,
9569 false,
9570 ))),
9571 )
9572 .child(Label::new("Preview").into_any_element())
9573 .opacity(if has_completion { 1.0 } else { 0.4 }),
9574 )
9575 })
9576 .into_any(),
9577 )
9578 }
9579
9580 fn render_edit_prediction_cursor_popover_preview(
9581 &self,
9582 completion: &EditPredictionState,
9583 cursor_point: Point,
9584 style: &EditorStyle,
9585 cx: &mut Context<Editor>,
9586 ) -> Option<Div> {
9587 use text::ToPoint as _;
9588
9589 fn render_relative_row_jump(
9590 prefix: impl Into<String>,
9591 current_row: u32,
9592 target_row: u32,
9593 ) -> Div {
9594 let (row_diff, arrow) = if target_row < current_row {
9595 (current_row - target_row, IconName::ArrowUp)
9596 } else {
9597 (target_row - current_row, IconName::ArrowDown)
9598 };
9599
9600 h_flex()
9601 .child(
9602 Label::new(format!("{}{}", prefix.into(), row_diff))
9603 .color(Color::Muted)
9604 .size(LabelSize::Small),
9605 )
9606 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9607 }
9608
9609 let supports_jump = self
9610 .edit_prediction_provider
9611 .as_ref()
9612 .map(|provider| provider.provider.supports_jump_to_edit())
9613 .unwrap_or(true);
9614
9615 match &completion.completion {
9616 EditPrediction::MoveWithin {
9617 target, snapshot, ..
9618 } => {
9619 if !supports_jump {
9620 return None;
9621 }
9622
9623 Some(
9624 h_flex()
9625 .px_2()
9626 .gap_2()
9627 .flex_1()
9628 .child(
9629 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9630 Icon::new(IconName::ZedPredictDown)
9631 } else {
9632 Icon::new(IconName::ZedPredictUp)
9633 },
9634 )
9635 .child(Label::new("Jump to Edit")),
9636 )
9637 }
9638 EditPrediction::MoveOutside { snapshot, .. } => {
9639 let file_name = snapshot
9640 .file()
9641 .map(|file| file.file_name(cx))
9642 .unwrap_or("untitled");
9643 Some(
9644 h_flex()
9645 .px_2()
9646 .gap_2()
9647 .flex_1()
9648 .child(Icon::new(IconName::ZedPredict))
9649 .child(Label::new(format!("Jump to {file_name}"))),
9650 )
9651 }
9652 EditPrediction::Edit {
9653 edits,
9654 edit_preview,
9655 snapshot,
9656 display_mode: _,
9657 } => {
9658 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9659
9660 let (highlighted_edits, has_more_lines) =
9661 if let Some(edit_preview) = edit_preview.as_ref() {
9662 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9663 .first_line_preview()
9664 } else {
9665 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9666 };
9667
9668 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9669 .with_default_highlights(&style.text, highlighted_edits.highlights);
9670
9671 let preview = h_flex()
9672 .gap_1()
9673 .min_w_16()
9674 .child(styled_text)
9675 .when(has_more_lines, |parent| parent.child("…"));
9676
9677 let left = if supports_jump && first_edit_row != cursor_point.row {
9678 render_relative_row_jump("", cursor_point.row, first_edit_row)
9679 .into_any_element()
9680 } else {
9681 let icon_name =
9682 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9683 Icon::new(icon_name).into_any_element()
9684 };
9685
9686 Some(
9687 h_flex()
9688 .h_full()
9689 .flex_1()
9690 .gap_2()
9691 .pr_1()
9692 .overflow_x_hidden()
9693 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9694 .child(left)
9695 .child(preview),
9696 )
9697 }
9698 }
9699 }
9700
9701 pub fn render_context_menu(
9702 &self,
9703 style: &EditorStyle,
9704 max_height_in_lines: u32,
9705 window: &mut Window,
9706 cx: &mut Context<Editor>,
9707 ) -> Option<AnyElement> {
9708 let menu = self.context_menu.borrow();
9709 let menu = menu.as_ref()?;
9710 if !menu.visible() {
9711 return None;
9712 };
9713 Some(menu.render(style, max_height_in_lines, window, cx))
9714 }
9715
9716 fn render_context_menu_aside(
9717 &mut self,
9718 max_size: Size<Pixels>,
9719 window: &mut Window,
9720 cx: &mut Context<Editor>,
9721 ) -> Option<AnyElement> {
9722 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9723 if menu.visible() {
9724 menu.render_aside(max_size, window, cx)
9725 } else {
9726 None
9727 }
9728 })
9729 }
9730
9731 fn hide_context_menu(
9732 &mut self,
9733 window: &mut Window,
9734 cx: &mut Context<Self>,
9735 ) -> Option<CodeContextMenu> {
9736 cx.notify();
9737 self.completion_tasks.clear();
9738 let context_menu = self.context_menu.borrow_mut().take();
9739 self.stale_edit_prediction_in_menu.take();
9740 self.update_visible_edit_prediction(window, cx);
9741 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9742 && let Some(completion_provider) = &self.completion_provider
9743 {
9744 completion_provider.selection_changed(None, window, cx);
9745 }
9746 context_menu
9747 }
9748
9749 fn show_snippet_choices(
9750 &mut self,
9751 choices: &Vec<String>,
9752 selection: Range<Anchor>,
9753 cx: &mut Context<Self>,
9754 ) {
9755 let Some((_, buffer, _)) = self
9756 .buffer()
9757 .read(cx)
9758 .excerpt_containing(selection.start, cx)
9759 else {
9760 return;
9761 };
9762 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9763 else {
9764 return;
9765 };
9766 if buffer != end_buffer {
9767 log::error!("expected anchor range to have matching buffer IDs");
9768 return;
9769 }
9770
9771 let id = post_inc(&mut self.next_completion_id);
9772 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9773 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9774 CompletionsMenu::new_snippet_choices(
9775 id,
9776 true,
9777 choices,
9778 selection,
9779 buffer,
9780 snippet_sort_order,
9781 ),
9782 ));
9783 }
9784
9785 pub fn insert_snippet(
9786 &mut self,
9787 insertion_ranges: &[Range<usize>],
9788 snippet: Snippet,
9789 window: &mut Window,
9790 cx: &mut Context<Self>,
9791 ) -> Result<()> {
9792 struct Tabstop<T> {
9793 is_end_tabstop: bool,
9794 ranges: Vec<Range<T>>,
9795 choices: Option<Vec<String>>,
9796 }
9797
9798 let tabstops = self.buffer.update(cx, |buffer, cx| {
9799 let snippet_text: Arc<str> = snippet.text.clone().into();
9800 let edits = insertion_ranges
9801 .iter()
9802 .cloned()
9803 .map(|range| (range, snippet_text.clone()));
9804 let autoindent_mode = AutoindentMode::Block {
9805 original_indent_columns: Vec::new(),
9806 };
9807 buffer.edit(edits, Some(autoindent_mode), cx);
9808
9809 let snapshot = &*buffer.read(cx);
9810 let snippet = &snippet;
9811 snippet
9812 .tabstops
9813 .iter()
9814 .map(|tabstop| {
9815 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9816 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9817 });
9818 let mut tabstop_ranges = tabstop
9819 .ranges
9820 .iter()
9821 .flat_map(|tabstop_range| {
9822 let mut delta = 0_isize;
9823 insertion_ranges.iter().map(move |insertion_range| {
9824 let insertion_start = insertion_range.start as isize + delta;
9825 delta +=
9826 snippet.text.len() as isize - insertion_range.len() as isize;
9827
9828 let start = ((insertion_start + tabstop_range.start) as usize)
9829 .min(snapshot.len());
9830 let end = ((insertion_start + tabstop_range.end) as usize)
9831 .min(snapshot.len());
9832 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9833 })
9834 })
9835 .collect::<Vec<_>>();
9836 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9837
9838 Tabstop {
9839 is_end_tabstop,
9840 ranges: tabstop_ranges,
9841 choices: tabstop.choices.clone(),
9842 }
9843 })
9844 .collect::<Vec<_>>()
9845 });
9846 if let Some(tabstop) = tabstops.first() {
9847 self.change_selections(Default::default(), window, cx, |s| {
9848 // Reverse order so that the first range is the newest created selection.
9849 // Completions will use it and autoscroll will prioritize it.
9850 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9851 });
9852
9853 if let Some(choices) = &tabstop.choices
9854 && let Some(selection) = tabstop.ranges.first()
9855 {
9856 self.show_snippet_choices(choices, selection.clone(), cx)
9857 }
9858
9859 // If we're already at the last tabstop and it's at the end of the snippet,
9860 // we're done, we don't need to keep the state around.
9861 if !tabstop.is_end_tabstop {
9862 let choices = tabstops
9863 .iter()
9864 .map(|tabstop| tabstop.choices.clone())
9865 .collect();
9866
9867 let ranges = tabstops
9868 .into_iter()
9869 .map(|tabstop| tabstop.ranges)
9870 .collect::<Vec<_>>();
9871
9872 self.snippet_stack.push(SnippetState {
9873 active_index: 0,
9874 ranges,
9875 choices,
9876 });
9877 }
9878
9879 // Check whether the just-entered snippet ends with an auto-closable bracket.
9880 if self.autoclose_regions.is_empty() {
9881 let snapshot = self.buffer.read(cx).snapshot(cx);
9882 let mut all_selections = self.selections.all::<Point>(cx);
9883 for selection in &mut all_selections {
9884 let selection_head = selection.head();
9885 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9886 continue;
9887 };
9888
9889 let mut bracket_pair = None;
9890 let max_lookup_length = scope
9891 .brackets()
9892 .map(|(pair, _)| {
9893 pair.start
9894 .as_str()
9895 .chars()
9896 .count()
9897 .max(pair.end.as_str().chars().count())
9898 })
9899 .max();
9900 if let Some(max_lookup_length) = max_lookup_length {
9901 let next_text = snapshot
9902 .chars_at(selection_head)
9903 .take(max_lookup_length)
9904 .collect::<String>();
9905 let prev_text = snapshot
9906 .reversed_chars_at(selection_head)
9907 .take(max_lookup_length)
9908 .collect::<String>();
9909
9910 for (pair, enabled) in scope.brackets() {
9911 if enabled
9912 && pair.close
9913 && prev_text.starts_with(pair.start.as_str())
9914 && next_text.starts_with(pair.end.as_str())
9915 {
9916 bracket_pair = Some(pair.clone());
9917 break;
9918 }
9919 }
9920 }
9921
9922 if let Some(pair) = bracket_pair {
9923 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9924 let autoclose_enabled =
9925 self.use_autoclose && snapshot_settings.use_autoclose;
9926 if autoclose_enabled {
9927 let start = snapshot.anchor_after(selection_head);
9928 let end = snapshot.anchor_after(selection_head);
9929 self.autoclose_regions.push(AutocloseRegion {
9930 selection_id: selection.id,
9931 range: start..end,
9932 pair,
9933 });
9934 }
9935 }
9936 }
9937 }
9938 }
9939 Ok(())
9940 }
9941
9942 pub fn move_to_next_snippet_tabstop(
9943 &mut self,
9944 window: &mut Window,
9945 cx: &mut Context<Self>,
9946 ) -> bool {
9947 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9948 }
9949
9950 pub fn move_to_prev_snippet_tabstop(
9951 &mut self,
9952 window: &mut Window,
9953 cx: &mut Context<Self>,
9954 ) -> bool {
9955 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9956 }
9957
9958 pub fn move_to_snippet_tabstop(
9959 &mut self,
9960 bias: Bias,
9961 window: &mut Window,
9962 cx: &mut Context<Self>,
9963 ) -> bool {
9964 if let Some(mut snippet) = self.snippet_stack.pop() {
9965 match bias {
9966 Bias::Left => {
9967 if snippet.active_index > 0 {
9968 snippet.active_index -= 1;
9969 } else {
9970 self.snippet_stack.push(snippet);
9971 return false;
9972 }
9973 }
9974 Bias::Right => {
9975 if snippet.active_index + 1 < snippet.ranges.len() {
9976 snippet.active_index += 1;
9977 } else {
9978 self.snippet_stack.push(snippet);
9979 return false;
9980 }
9981 }
9982 }
9983 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9984 self.change_selections(Default::default(), window, cx, |s| {
9985 // Reverse order so that the first range is the newest created selection.
9986 // Completions will use it and autoscroll will prioritize it.
9987 s.select_ranges(current_ranges.iter().rev().cloned())
9988 });
9989
9990 if let Some(choices) = &snippet.choices[snippet.active_index]
9991 && let Some(selection) = current_ranges.first()
9992 {
9993 self.show_snippet_choices(choices, selection.clone(), cx);
9994 }
9995
9996 // If snippet state is not at the last tabstop, push it back on the stack
9997 if snippet.active_index + 1 < snippet.ranges.len() {
9998 self.snippet_stack.push(snippet);
9999 }
10000 return true;
10001 }
10002 }
10003
10004 false
10005 }
10006
10007 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10008 self.transact(window, cx, |this, window, cx| {
10009 this.select_all(&SelectAll, window, cx);
10010 this.insert("", window, cx);
10011 });
10012 }
10013
10014 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10015 if self.read_only(cx) {
10016 return;
10017 }
10018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10019 self.transact(window, cx, |this, window, cx| {
10020 this.select_autoclose_pair(window, cx);
10021 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10022 if !this.linked_edit_ranges.is_empty() {
10023 let selections = this.selections.all::<MultiBufferPoint>(cx);
10024 let snapshot = this.buffer.read(cx).snapshot(cx);
10025
10026 for selection in selections.iter() {
10027 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10028 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10029 if selection_start.buffer_id != selection_end.buffer_id {
10030 continue;
10031 }
10032 if let Some(ranges) =
10033 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10034 {
10035 for (buffer, entries) in ranges {
10036 linked_ranges.entry(buffer).or_default().extend(entries);
10037 }
10038 }
10039 }
10040 }
10041
10042 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10043 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10044 for selection in &mut selections {
10045 if selection.is_empty() {
10046 let old_head = selection.head();
10047 let mut new_head =
10048 movement::left(&display_map, old_head.to_display_point(&display_map))
10049 .to_point(&display_map);
10050 if let Some((buffer, line_buffer_range)) = display_map
10051 .buffer_snapshot()
10052 .buffer_line_for_row(MultiBufferRow(old_head.row))
10053 {
10054 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10055 let indent_len = match indent_size.kind {
10056 IndentKind::Space => {
10057 buffer.settings_at(line_buffer_range.start, cx).tab_size
10058 }
10059 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10060 };
10061 if old_head.column <= indent_size.len && old_head.column > 0 {
10062 let indent_len = indent_len.get();
10063 new_head = cmp::min(
10064 new_head,
10065 MultiBufferPoint::new(
10066 old_head.row,
10067 ((old_head.column - 1) / indent_len) * indent_len,
10068 ),
10069 );
10070 }
10071 }
10072
10073 selection.set_head(new_head, SelectionGoal::None);
10074 }
10075 }
10076
10077 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10078 this.insert("", window, cx);
10079 let empty_str: Arc<str> = Arc::from("");
10080 for (buffer, edits) in linked_ranges {
10081 let snapshot = buffer.read(cx).snapshot();
10082 use text::ToPoint as TP;
10083
10084 let edits = edits
10085 .into_iter()
10086 .map(|range| {
10087 let end_point = TP::to_point(&range.end, &snapshot);
10088 let mut start_point = TP::to_point(&range.start, &snapshot);
10089
10090 if end_point == start_point {
10091 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10092 .saturating_sub(1);
10093 start_point =
10094 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10095 };
10096
10097 (start_point..end_point, empty_str.clone())
10098 })
10099 .sorted_by_key(|(range, _)| range.start)
10100 .collect::<Vec<_>>();
10101 buffer.update(cx, |this, cx| {
10102 this.edit(edits, None, cx);
10103 })
10104 }
10105 this.refresh_edit_prediction(true, false, window, cx);
10106 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10107 });
10108 }
10109
10110 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10111 if self.read_only(cx) {
10112 return;
10113 }
10114 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10115 self.transact(window, cx, |this, window, cx| {
10116 this.change_selections(Default::default(), window, cx, |s| {
10117 s.move_with(|map, selection| {
10118 if selection.is_empty() {
10119 let cursor = movement::right(map, selection.head());
10120 selection.end = cursor;
10121 selection.reversed = true;
10122 selection.goal = SelectionGoal::None;
10123 }
10124 })
10125 });
10126 this.insert("", window, cx);
10127 this.refresh_edit_prediction(true, false, window, cx);
10128 });
10129 }
10130
10131 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10132 if self.mode.is_single_line() {
10133 cx.propagate();
10134 return;
10135 }
10136
10137 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10138 if self.move_to_prev_snippet_tabstop(window, cx) {
10139 return;
10140 }
10141 self.outdent(&Outdent, window, cx);
10142 }
10143
10144 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10145 if self.mode.is_single_line() {
10146 cx.propagate();
10147 return;
10148 }
10149
10150 if self.move_to_next_snippet_tabstop(window, cx) {
10151 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10152 return;
10153 }
10154 if self.read_only(cx) {
10155 return;
10156 }
10157 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10158 let mut selections = self.selections.all_adjusted(cx);
10159 let buffer = self.buffer.read(cx);
10160 let snapshot = buffer.snapshot(cx);
10161 let rows_iter = selections.iter().map(|s| s.head().row);
10162 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10163
10164 let has_some_cursor_in_whitespace = selections
10165 .iter()
10166 .filter(|selection| selection.is_empty())
10167 .any(|selection| {
10168 let cursor = selection.head();
10169 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10170 cursor.column < current_indent.len
10171 });
10172
10173 let mut edits = Vec::new();
10174 let mut prev_edited_row = 0;
10175 let mut row_delta = 0;
10176 for selection in &mut selections {
10177 if selection.start.row != prev_edited_row {
10178 row_delta = 0;
10179 }
10180 prev_edited_row = selection.end.row;
10181
10182 // If the selection is non-empty, then increase the indentation of the selected lines.
10183 if !selection.is_empty() {
10184 row_delta =
10185 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10186 continue;
10187 }
10188
10189 let cursor = selection.head();
10190 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10191 if let Some(suggested_indent) =
10192 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10193 {
10194 // Don't do anything if already at suggested indent
10195 // and there is any other cursor which is not
10196 if has_some_cursor_in_whitespace
10197 && cursor.column == current_indent.len
10198 && current_indent.len == suggested_indent.len
10199 {
10200 continue;
10201 }
10202
10203 // Adjust line and move cursor to suggested indent
10204 // if cursor is not at suggested indent
10205 if cursor.column < suggested_indent.len
10206 && cursor.column <= current_indent.len
10207 && current_indent.len <= suggested_indent.len
10208 {
10209 selection.start = Point::new(cursor.row, suggested_indent.len);
10210 selection.end = selection.start;
10211 if row_delta == 0 {
10212 edits.extend(Buffer::edit_for_indent_size_adjustment(
10213 cursor.row,
10214 current_indent,
10215 suggested_indent,
10216 ));
10217 row_delta = suggested_indent.len - current_indent.len;
10218 }
10219 continue;
10220 }
10221
10222 // If current indent is more than suggested indent
10223 // only move cursor to current indent and skip indent
10224 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10225 selection.start = Point::new(cursor.row, current_indent.len);
10226 selection.end = selection.start;
10227 continue;
10228 }
10229 }
10230
10231 // Otherwise, insert a hard or soft tab.
10232 let settings = buffer.language_settings_at(cursor, cx);
10233 let tab_size = if settings.hard_tabs {
10234 IndentSize::tab()
10235 } else {
10236 let tab_size = settings.tab_size.get();
10237 let indent_remainder = snapshot
10238 .text_for_range(Point::new(cursor.row, 0)..cursor)
10239 .flat_map(str::chars)
10240 .fold(row_delta % tab_size, |counter: u32, c| {
10241 if c == '\t' {
10242 0
10243 } else {
10244 (counter + 1) % tab_size
10245 }
10246 });
10247
10248 let chars_to_next_tab_stop = tab_size - indent_remainder;
10249 IndentSize::spaces(chars_to_next_tab_stop)
10250 };
10251 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10252 selection.end = selection.start;
10253 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10254 row_delta += tab_size.len;
10255 }
10256
10257 self.transact(window, cx, |this, window, cx| {
10258 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10259 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10260 this.refresh_edit_prediction(true, false, window, cx);
10261 });
10262 }
10263
10264 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10265 if self.read_only(cx) {
10266 return;
10267 }
10268 if self.mode.is_single_line() {
10269 cx.propagate();
10270 return;
10271 }
10272
10273 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10274 let mut selections = self.selections.all::<Point>(cx);
10275 let mut prev_edited_row = 0;
10276 let mut row_delta = 0;
10277 let mut edits = Vec::new();
10278 let buffer = self.buffer.read(cx);
10279 let snapshot = buffer.snapshot(cx);
10280 for selection in &mut selections {
10281 if selection.start.row != prev_edited_row {
10282 row_delta = 0;
10283 }
10284 prev_edited_row = selection.end.row;
10285
10286 row_delta =
10287 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10288 }
10289
10290 self.transact(window, cx, |this, window, cx| {
10291 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10292 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10293 });
10294 }
10295
10296 fn indent_selection(
10297 buffer: &MultiBuffer,
10298 snapshot: &MultiBufferSnapshot,
10299 selection: &mut Selection<Point>,
10300 edits: &mut Vec<(Range<Point>, String)>,
10301 delta_for_start_row: u32,
10302 cx: &App,
10303 ) -> u32 {
10304 let settings = buffer.language_settings_at(selection.start, cx);
10305 let tab_size = settings.tab_size.get();
10306 let indent_kind = if settings.hard_tabs {
10307 IndentKind::Tab
10308 } else {
10309 IndentKind::Space
10310 };
10311 let mut start_row = selection.start.row;
10312 let mut end_row = selection.end.row + 1;
10313
10314 // If a selection ends at the beginning of a line, don't indent
10315 // that last line.
10316 if selection.end.column == 0 && selection.end.row > selection.start.row {
10317 end_row -= 1;
10318 }
10319
10320 // Avoid re-indenting a row that has already been indented by a
10321 // previous selection, but still update this selection's column
10322 // to reflect that indentation.
10323 if delta_for_start_row > 0 {
10324 start_row += 1;
10325 selection.start.column += delta_for_start_row;
10326 if selection.end.row == selection.start.row {
10327 selection.end.column += delta_for_start_row;
10328 }
10329 }
10330
10331 let mut delta_for_end_row = 0;
10332 let has_multiple_rows = start_row + 1 != end_row;
10333 for row in start_row..end_row {
10334 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10335 let indent_delta = match (current_indent.kind, indent_kind) {
10336 (IndentKind::Space, IndentKind::Space) => {
10337 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10338 IndentSize::spaces(columns_to_next_tab_stop)
10339 }
10340 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10341 (_, IndentKind::Tab) => IndentSize::tab(),
10342 };
10343
10344 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10345 0
10346 } else {
10347 selection.start.column
10348 };
10349 let row_start = Point::new(row, start);
10350 edits.push((
10351 row_start..row_start,
10352 indent_delta.chars().collect::<String>(),
10353 ));
10354
10355 // Update this selection's endpoints to reflect the indentation.
10356 if row == selection.start.row {
10357 selection.start.column += indent_delta.len;
10358 }
10359 if row == selection.end.row {
10360 selection.end.column += indent_delta.len;
10361 delta_for_end_row = indent_delta.len;
10362 }
10363 }
10364
10365 if selection.start.row == selection.end.row {
10366 delta_for_start_row + delta_for_end_row
10367 } else {
10368 delta_for_end_row
10369 }
10370 }
10371
10372 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10373 if self.read_only(cx) {
10374 return;
10375 }
10376 if self.mode.is_single_line() {
10377 cx.propagate();
10378 return;
10379 }
10380
10381 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10382 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10383 let selections = self.selections.all::<Point>(cx);
10384 let mut deletion_ranges = Vec::new();
10385 let mut last_outdent = None;
10386 {
10387 let buffer = self.buffer.read(cx);
10388 let snapshot = buffer.snapshot(cx);
10389 for selection in &selections {
10390 let settings = buffer.language_settings_at(selection.start, cx);
10391 let tab_size = settings.tab_size.get();
10392 let mut rows = selection.spanned_rows(false, &display_map);
10393
10394 // Avoid re-outdenting a row that has already been outdented by a
10395 // previous selection.
10396 if let Some(last_row) = last_outdent
10397 && last_row == rows.start
10398 {
10399 rows.start = rows.start.next_row();
10400 }
10401 let has_multiple_rows = rows.len() > 1;
10402 for row in rows.iter_rows() {
10403 let indent_size = snapshot.indent_size_for_line(row);
10404 if indent_size.len > 0 {
10405 let deletion_len = match indent_size.kind {
10406 IndentKind::Space => {
10407 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10408 if columns_to_prev_tab_stop == 0 {
10409 tab_size
10410 } else {
10411 columns_to_prev_tab_stop
10412 }
10413 }
10414 IndentKind::Tab => 1,
10415 };
10416 let start = if has_multiple_rows
10417 || deletion_len > selection.start.column
10418 || indent_size.len < selection.start.column
10419 {
10420 0
10421 } else {
10422 selection.start.column - deletion_len
10423 };
10424 deletion_ranges.push(
10425 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10426 );
10427 last_outdent = Some(row);
10428 }
10429 }
10430 }
10431 }
10432
10433 self.transact(window, cx, |this, window, cx| {
10434 this.buffer.update(cx, |buffer, cx| {
10435 let empty_str: Arc<str> = Arc::default();
10436 buffer.edit(
10437 deletion_ranges
10438 .into_iter()
10439 .map(|range| (range, empty_str.clone())),
10440 None,
10441 cx,
10442 );
10443 });
10444 let selections = this.selections.all::<usize>(cx);
10445 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10446 });
10447 }
10448
10449 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10450 if self.read_only(cx) {
10451 return;
10452 }
10453 if self.mode.is_single_line() {
10454 cx.propagate();
10455 return;
10456 }
10457
10458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10459 let selections = self
10460 .selections
10461 .all::<usize>(cx)
10462 .into_iter()
10463 .map(|s| s.range());
10464
10465 self.transact(window, cx, |this, window, cx| {
10466 this.buffer.update(cx, |buffer, cx| {
10467 buffer.autoindent_ranges(selections, cx);
10468 });
10469 let selections = this.selections.all::<usize>(cx);
10470 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10471 });
10472 }
10473
10474 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10475 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10477 let selections = self.selections.all::<Point>(cx);
10478
10479 let mut new_cursors = Vec::new();
10480 let mut edit_ranges = Vec::new();
10481 let mut selections = selections.iter().peekable();
10482 while let Some(selection) = selections.next() {
10483 let mut rows = selection.spanned_rows(false, &display_map);
10484
10485 // Accumulate contiguous regions of rows that we want to delete.
10486 while let Some(next_selection) = selections.peek() {
10487 let next_rows = next_selection.spanned_rows(false, &display_map);
10488 if next_rows.start <= rows.end {
10489 rows.end = next_rows.end;
10490 selections.next().unwrap();
10491 } else {
10492 break;
10493 }
10494 }
10495
10496 let buffer = display_map.buffer_snapshot();
10497 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10498 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10499 // If there's a line after the range, delete the \n from the end of the row range
10500 (
10501 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10502 rows.end,
10503 )
10504 } else {
10505 // If there isn't a line after the range, delete the \n from the line before the
10506 // start of the row range
10507 edit_start = edit_start.saturating_sub(1);
10508 (buffer.len(), rows.start.previous_row())
10509 };
10510
10511 let text_layout_details = self.text_layout_details(window);
10512 let x = display_map.x_for_display_point(
10513 selection.head().to_display_point(&display_map),
10514 &text_layout_details,
10515 );
10516 let row = Point::new(target_row.0, 0)
10517 .to_display_point(&display_map)
10518 .row();
10519 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10520
10521 new_cursors.push((
10522 selection.id,
10523 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10524 SelectionGoal::None,
10525 ));
10526 edit_ranges.push(edit_start..edit_end);
10527 }
10528
10529 self.transact(window, cx, |this, window, cx| {
10530 let buffer = this.buffer.update(cx, |buffer, cx| {
10531 let empty_str: Arc<str> = Arc::default();
10532 buffer.edit(
10533 edit_ranges
10534 .into_iter()
10535 .map(|range| (range, empty_str.clone())),
10536 None,
10537 cx,
10538 );
10539 buffer.snapshot(cx)
10540 });
10541 let new_selections = new_cursors
10542 .into_iter()
10543 .map(|(id, cursor, goal)| {
10544 let cursor = cursor.to_point(&buffer);
10545 Selection {
10546 id,
10547 start: cursor,
10548 end: cursor,
10549 reversed: false,
10550 goal,
10551 }
10552 })
10553 .collect();
10554
10555 this.change_selections(Default::default(), window, cx, |s| {
10556 s.select(new_selections);
10557 });
10558 });
10559 }
10560
10561 pub fn join_lines_impl(
10562 &mut self,
10563 insert_whitespace: bool,
10564 window: &mut Window,
10565 cx: &mut Context<Self>,
10566 ) {
10567 if self.read_only(cx) {
10568 return;
10569 }
10570 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10571 for selection in self.selections.all::<Point>(cx) {
10572 let start = MultiBufferRow(selection.start.row);
10573 // Treat single line selections as if they include the next line. Otherwise this action
10574 // would do nothing for single line selections individual cursors.
10575 let end = if selection.start.row == selection.end.row {
10576 MultiBufferRow(selection.start.row + 1)
10577 } else {
10578 MultiBufferRow(selection.end.row)
10579 };
10580
10581 if let Some(last_row_range) = row_ranges.last_mut()
10582 && start <= last_row_range.end
10583 {
10584 last_row_range.end = end;
10585 continue;
10586 }
10587 row_ranges.push(start..end);
10588 }
10589
10590 let snapshot = self.buffer.read(cx).snapshot(cx);
10591 let mut cursor_positions = Vec::new();
10592 for row_range in &row_ranges {
10593 let anchor = snapshot.anchor_before(Point::new(
10594 row_range.end.previous_row().0,
10595 snapshot.line_len(row_range.end.previous_row()),
10596 ));
10597 cursor_positions.push(anchor..anchor);
10598 }
10599
10600 self.transact(window, cx, |this, window, cx| {
10601 for row_range in row_ranges.into_iter().rev() {
10602 for row in row_range.iter_rows().rev() {
10603 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10604 let next_line_row = row.next_row();
10605 let indent = snapshot.indent_size_for_line(next_line_row);
10606 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10607
10608 let replace =
10609 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10610 " "
10611 } else {
10612 ""
10613 };
10614
10615 this.buffer.update(cx, |buffer, cx| {
10616 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10617 });
10618 }
10619 }
10620
10621 this.change_selections(Default::default(), window, cx, |s| {
10622 s.select_anchor_ranges(cursor_positions)
10623 });
10624 });
10625 }
10626
10627 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10628 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10629 self.join_lines_impl(true, window, cx);
10630 }
10631
10632 pub fn sort_lines_case_sensitive(
10633 &mut self,
10634 _: &SortLinesCaseSensitive,
10635 window: &mut Window,
10636 cx: &mut Context<Self>,
10637 ) {
10638 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10639 }
10640
10641 pub fn sort_lines_by_length(
10642 &mut self,
10643 _: &SortLinesByLength,
10644 window: &mut Window,
10645 cx: &mut Context<Self>,
10646 ) {
10647 self.manipulate_immutable_lines(window, cx, |lines| {
10648 lines.sort_by_key(|&line| line.chars().count())
10649 })
10650 }
10651
10652 pub fn sort_lines_case_insensitive(
10653 &mut self,
10654 _: &SortLinesCaseInsensitive,
10655 window: &mut Window,
10656 cx: &mut Context<Self>,
10657 ) {
10658 self.manipulate_immutable_lines(window, cx, |lines| {
10659 lines.sort_by_key(|line| line.to_lowercase())
10660 })
10661 }
10662
10663 pub fn unique_lines_case_insensitive(
10664 &mut self,
10665 _: &UniqueLinesCaseInsensitive,
10666 window: &mut Window,
10667 cx: &mut Context<Self>,
10668 ) {
10669 self.manipulate_immutable_lines(window, cx, |lines| {
10670 let mut seen = HashSet::default();
10671 lines.retain(|line| seen.insert(line.to_lowercase()));
10672 })
10673 }
10674
10675 pub fn unique_lines_case_sensitive(
10676 &mut self,
10677 _: &UniqueLinesCaseSensitive,
10678 window: &mut Window,
10679 cx: &mut Context<Self>,
10680 ) {
10681 self.manipulate_immutable_lines(window, cx, |lines| {
10682 let mut seen = HashSet::default();
10683 lines.retain(|line| seen.insert(*line));
10684 })
10685 }
10686
10687 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10688 let snapshot = self.buffer.read(cx).snapshot(cx);
10689 for selection in self.selections.disjoint_anchors_arc().iter() {
10690 if snapshot
10691 .language_at(selection.start)
10692 .and_then(|lang| lang.config().wrap_characters.as_ref())
10693 .is_some()
10694 {
10695 return true;
10696 }
10697 }
10698 false
10699 }
10700
10701 fn wrap_selections_in_tag(
10702 &mut self,
10703 _: &WrapSelectionsInTag,
10704 window: &mut Window,
10705 cx: &mut Context<Self>,
10706 ) {
10707 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10708
10709 let snapshot = self.buffer.read(cx).snapshot(cx);
10710
10711 let mut edits = Vec::new();
10712 let mut boundaries = Vec::new();
10713
10714 for selection in self.selections.all::<Point>(cx).iter() {
10715 let Some(wrap_config) = snapshot
10716 .language_at(selection.start)
10717 .and_then(|lang| lang.config().wrap_characters.clone())
10718 else {
10719 continue;
10720 };
10721
10722 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10723 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10724
10725 let start_before = snapshot.anchor_before(selection.start);
10726 let end_after = snapshot.anchor_after(selection.end);
10727
10728 edits.push((start_before..start_before, open_tag));
10729 edits.push((end_after..end_after, close_tag));
10730
10731 boundaries.push((
10732 start_before,
10733 end_after,
10734 wrap_config.start_prefix.len(),
10735 wrap_config.end_suffix.len(),
10736 ));
10737 }
10738
10739 if edits.is_empty() {
10740 return;
10741 }
10742
10743 self.transact(window, cx, |this, window, cx| {
10744 let buffer = this.buffer.update(cx, |buffer, cx| {
10745 buffer.edit(edits, None, cx);
10746 buffer.snapshot(cx)
10747 });
10748
10749 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10750 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10751 boundaries.into_iter()
10752 {
10753 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10754 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10755 new_selections.push(open_offset..open_offset);
10756 new_selections.push(close_offset..close_offset);
10757 }
10758
10759 this.change_selections(Default::default(), window, cx, |s| {
10760 s.select_ranges(new_selections);
10761 });
10762
10763 this.request_autoscroll(Autoscroll::fit(), cx);
10764 });
10765 }
10766
10767 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10768 let Some(project) = self.project.clone() else {
10769 return;
10770 };
10771 self.reload(project, window, cx)
10772 .detach_and_notify_err(window, cx);
10773 }
10774
10775 pub fn restore_file(
10776 &mut self,
10777 _: &::git::RestoreFile,
10778 window: &mut Window,
10779 cx: &mut Context<Self>,
10780 ) {
10781 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10782 let mut buffer_ids = HashSet::default();
10783 let snapshot = self.buffer().read(cx).snapshot(cx);
10784 for selection in self.selections.all::<usize>(cx) {
10785 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10786 }
10787
10788 let buffer = self.buffer().read(cx);
10789 let ranges = buffer_ids
10790 .into_iter()
10791 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10792 .collect::<Vec<_>>();
10793
10794 self.restore_hunks_in_ranges(ranges, window, cx);
10795 }
10796
10797 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10798 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10799 let selections = self
10800 .selections
10801 .all(cx)
10802 .into_iter()
10803 .map(|s| s.range())
10804 .collect();
10805 self.restore_hunks_in_ranges(selections, window, cx);
10806 }
10807
10808 pub fn restore_hunks_in_ranges(
10809 &mut self,
10810 ranges: Vec<Range<Point>>,
10811 window: &mut Window,
10812 cx: &mut Context<Editor>,
10813 ) {
10814 let mut revert_changes = HashMap::default();
10815 let chunk_by = self
10816 .snapshot(window, cx)
10817 .hunks_for_ranges(ranges)
10818 .into_iter()
10819 .chunk_by(|hunk| hunk.buffer_id);
10820 for (buffer_id, hunks) in &chunk_by {
10821 let hunks = hunks.collect::<Vec<_>>();
10822 for hunk in &hunks {
10823 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10824 }
10825 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10826 }
10827 drop(chunk_by);
10828 if !revert_changes.is_empty() {
10829 self.transact(window, cx, |editor, window, cx| {
10830 editor.restore(revert_changes, window, cx);
10831 });
10832 }
10833 }
10834
10835 pub fn open_active_item_in_terminal(
10836 &mut self,
10837 _: &OpenInTerminal,
10838 window: &mut Window,
10839 cx: &mut Context<Self>,
10840 ) {
10841 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10842 let project_path = buffer.read(cx).project_path(cx)?;
10843 let project = self.project()?.read(cx);
10844 let entry = project.entry_for_path(&project_path, cx)?;
10845 let parent = match &entry.canonical_path {
10846 Some(canonical_path) => canonical_path.to_path_buf(),
10847 None => project.absolute_path(&project_path, cx)?,
10848 }
10849 .parent()?
10850 .to_path_buf();
10851 Some(parent)
10852 }) {
10853 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10854 }
10855 }
10856
10857 fn set_breakpoint_context_menu(
10858 &mut self,
10859 display_row: DisplayRow,
10860 position: Option<Anchor>,
10861 clicked_point: gpui::Point<Pixels>,
10862 window: &mut Window,
10863 cx: &mut Context<Self>,
10864 ) {
10865 let source = self
10866 .buffer
10867 .read(cx)
10868 .snapshot(cx)
10869 .anchor_before(Point::new(display_row.0, 0u32));
10870
10871 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10872
10873 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10874 self,
10875 source,
10876 clicked_point,
10877 context_menu,
10878 window,
10879 cx,
10880 );
10881 }
10882
10883 fn add_edit_breakpoint_block(
10884 &mut self,
10885 anchor: Anchor,
10886 breakpoint: &Breakpoint,
10887 edit_action: BreakpointPromptEditAction,
10888 window: &mut Window,
10889 cx: &mut Context<Self>,
10890 ) {
10891 let weak_editor = cx.weak_entity();
10892 let bp_prompt = cx.new(|cx| {
10893 BreakpointPromptEditor::new(
10894 weak_editor,
10895 anchor,
10896 breakpoint.clone(),
10897 edit_action,
10898 window,
10899 cx,
10900 )
10901 });
10902
10903 let height = bp_prompt.update(cx, |this, cx| {
10904 this.prompt
10905 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10906 });
10907 let cloned_prompt = bp_prompt.clone();
10908 let blocks = vec![BlockProperties {
10909 style: BlockStyle::Sticky,
10910 placement: BlockPlacement::Above(anchor),
10911 height: Some(height),
10912 render: Arc::new(move |cx| {
10913 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10914 cloned_prompt.clone().into_any_element()
10915 }),
10916 priority: 0,
10917 }];
10918
10919 let focus_handle = bp_prompt.focus_handle(cx);
10920 window.focus(&focus_handle);
10921
10922 let block_ids = self.insert_blocks(blocks, None, cx);
10923 bp_prompt.update(cx, |prompt, _| {
10924 prompt.add_block_ids(block_ids);
10925 });
10926 }
10927
10928 pub(crate) fn breakpoint_at_row(
10929 &self,
10930 row: u32,
10931 window: &mut Window,
10932 cx: &mut Context<Self>,
10933 ) -> Option<(Anchor, Breakpoint)> {
10934 let snapshot = self.snapshot(window, cx);
10935 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10936
10937 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10938 }
10939
10940 pub(crate) fn breakpoint_at_anchor(
10941 &self,
10942 breakpoint_position: Anchor,
10943 snapshot: &EditorSnapshot,
10944 cx: &mut Context<Self>,
10945 ) -> Option<(Anchor, Breakpoint)> {
10946 let buffer = self
10947 .buffer
10948 .read(cx)
10949 .buffer_for_anchor(breakpoint_position, cx)?;
10950
10951 let enclosing_excerpt = breakpoint_position.excerpt_id;
10952 let buffer_snapshot = buffer.read(cx).snapshot();
10953
10954 let row = buffer_snapshot
10955 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10956 .row;
10957
10958 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10959 let anchor_end = snapshot
10960 .buffer_snapshot()
10961 .anchor_after(Point::new(row, line_len));
10962
10963 self.breakpoint_store
10964 .as_ref()?
10965 .read_with(cx, |breakpoint_store, cx| {
10966 breakpoint_store
10967 .breakpoints(
10968 &buffer,
10969 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10970 &buffer_snapshot,
10971 cx,
10972 )
10973 .next()
10974 .and_then(|(bp, _)| {
10975 let breakpoint_row = buffer_snapshot
10976 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10977 .row;
10978
10979 if breakpoint_row == row {
10980 snapshot
10981 .buffer_snapshot()
10982 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10983 .map(|position| (position, bp.bp.clone()))
10984 } else {
10985 None
10986 }
10987 })
10988 })
10989 }
10990
10991 pub fn edit_log_breakpoint(
10992 &mut self,
10993 _: &EditLogBreakpoint,
10994 window: &mut Window,
10995 cx: &mut Context<Self>,
10996 ) {
10997 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10998 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10999 message: None,
11000 state: BreakpointState::Enabled,
11001 condition: None,
11002 hit_condition: None,
11003 });
11004
11005 self.add_edit_breakpoint_block(
11006 anchor,
11007 &breakpoint,
11008 BreakpointPromptEditAction::Log,
11009 window,
11010 cx,
11011 );
11012 }
11013 }
11014
11015 fn breakpoints_at_cursors(
11016 &self,
11017 window: &mut Window,
11018 cx: &mut Context<Self>,
11019 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11020 let snapshot = self.snapshot(window, cx);
11021 let cursors = self
11022 .selections
11023 .disjoint_anchors_arc()
11024 .iter()
11025 .map(|selection| {
11026 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11027
11028 let breakpoint_position = self
11029 .breakpoint_at_row(cursor_position.row, window, cx)
11030 .map(|bp| bp.0)
11031 .unwrap_or_else(|| {
11032 snapshot
11033 .display_snapshot
11034 .buffer_snapshot()
11035 .anchor_after(Point::new(cursor_position.row, 0))
11036 });
11037
11038 let breakpoint = self
11039 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11040 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11041
11042 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11043 })
11044 // 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.
11045 .collect::<HashMap<Anchor, _>>();
11046
11047 cursors.into_iter().collect()
11048 }
11049
11050 pub fn enable_breakpoint(
11051 &mut self,
11052 _: &crate::actions::EnableBreakpoint,
11053 window: &mut Window,
11054 cx: &mut Context<Self>,
11055 ) {
11056 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11057 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11058 continue;
11059 };
11060 self.edit_breakpoint_at_anchor(
11061 anchor,
11062 breakpoint,
11063 BreakpointEditAction::InvertState,
11064 cx,
11065 );
11066 }
11067 }
11068
11069 pub fn disable_breakpoint(
11070 &mut self,
11071 _: &crate::actions::DisableBreakpoint,
11072 window: &mut Window,
11073 cx: &mut Context<Self>,
11074 ) {
11075 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11076 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11077 continue;
11078 };
11079 self.edit_breakpoint_at_anchor(
11080 anchor,
11081 breakpoint,
11082 BreakpointEditAction::InvertState,
11083 cx,
11084 );
11085 }
11086 }
11087
11088 pub fn toggle_breakpoint(
11089 &mut self,
11090 _: &crate::actions::ToggleBreakpoint,
11091 window: &mut Window,
11092 cx: &mut Context<Self>,
11093 ) {
11094 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11095 if let Some(breakpoint) = breakpoint {
11096 self.edit_breakpoint_at_anchor(
11097 anchor,
11098 breakpoint,
11099 BreakpointEditAction::Toggle,
11100 cx,
11101 );
11102 } else {
11103 self.edit_breakpoint_at_anchor(
11104 anchor,
11105 Breakpoint::new_standard(),
11106 BreakpointEditAction::Toggle,
11107 cx,
11108 );
11109 }
11110 }
11111 }
11112
11113 pub fn edit_breakpoint_at_anchor(
11114 &mut self,
11115 breakpoint_position: Anchor,
11116 breakpoint: Breakpoint,
11117 edit_action: BreakpointEditAction,
11118 cx: &mut Context<Self>,
11119 ) {
11120 let Some(breakpoint_store) = &self.breakpoint_store else {
11121 return;
11122 };
11123
11124 let Some(buffer) = self
11125 .buffer
11126 .read(cx)
11127 .buffer_for_anchor(breakpoint_position, cx)
11128 else {
11129 return;
11130 };
11131
11132 breakpoint_store.update(cx, |breakpoint_store, cx| {
11133 breakpoint_store.toggle_breakpoint(
11134 buffer,
11135 BreakpointWithPosition {
11136 position: breakpoint_position.text_anchor,
11137 bp: breakpoint,
11138 },
11139 edit_action,
11140 cx,
11141 );
11142 });
11143
11144 cx.notify();
11145 }
11146
11147 #[cfg(any(test, feature = "test-support"))]
11148 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11149 self.breakpoint_store.clone()
11150 }
11151
11152 pub fn prepare_restore_change(
11153 &self,
11154 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11155 hunk: &MultiBufferDiffHunk,
11156 cx: &mut App,
11157 ) -> Option<()> {
11158 if hunk.is_created_file() {
11159 return None;
11160 }
11161 let buffer = self.buffer.read(cx);
11162 let diff = buffer.diff_for(hunk.buffer_id)?;
11163 let buffer = buffer.buffer(hunk.buffer_id)?;
11164 let buffer = buffer.read(cx);
11165 let original_text = diff
11166 .read(cx)
11167 .base_text()
11168 .as_rope()
11169 .slice(hunk.diff_base_byte_range.clone());
11170 let buffer_snapshot = buffer.snapshot();
11171 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11172 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11173 probe
11174 .0
11175 .start
11176 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11177 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11178 }) {
11179 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11180 Some(())
11181 } else {
11182 None
11183 }
11184 }
11185
11186 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11187 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11188 }
11189
11190 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11191 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11192 }
11193
11194 fn manipulate_lines<M>(
11195 &mut self,
11196 window: &mut Window,
11197 cx: &mut Context<Self>,
11198 mut manipulate: M,
11199 ) where
11200 M: FnMut(&str) -> LineManipulationResult,
11201 {
11202 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11203
11204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11205 let buffer = self.buffer.read(cx).snapshot(cx);
11206
11207 let mut edits = Vec::new();
11208
11209 let selections = self.selections.all::<Point>(cx);
11210 let mut selections = selections.iter().peekable();
11211 let mut contiguous_row_selections = Vec::new();
11212 let mut new_selections = Vec::new();
11213 let mut added_lines = 0;
11214 let mut removed_lines = 0;
11215
11216 while let Some(selection) = selections.next() {
11217 let (start_row, end_row) = consume_contiguous_rows(
11218 &mut contiguous_row_selections,
11219 selection,
11220 &display_map,
11221 &mut selections,
11222 );
11223
11224 let start_point = Point::new(start_row.0, 0);
11225 let end_point = Point::new(
11226 end_row.previous_row().0,
11227 buffer.line_len(end_row.previous_row()),
11228 );
11229 let text = buffer
11230 .text_for_range(start_point..end_point)
11231 .collect::<String>();
11232
11233 let LineManipulationResult {
11234 new_text,
11235 line_count_before,
11236 line_count_after,
11237 } = manipulate(&text);
11238
11239 edits.push((start_point..end_point, new_text));
11240
11241 // Selections must change based on added and removed line count
11242 let start_row =
11243 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11244 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11245 new_selections.push(Selection {
11246 id: selection.id,
11247 start: start_row,
11248 end: end_row,
11249 goal: SelectionGoal::None,
11250 reversed: selection.reversed,
11251 });
11252
11253 if line_count_after > line_count_before {
11254 added_lines += line_count_after - line_count_before;
11255 } else if line_count_before > line_count_after {
11256 removed_lines += line_count_before - line_count_after;
11257 }
11258 }
11259
11260 self.transact(window, cx, |this, window, cx| {
11261 let buffer = this.buffer.update(cx, |buffer, cx| {
11262 buffer.edit(edits, None, cx);
11263 buffer.snapshot(cx)
11264 });
11265
11266 // Recalculate offsets on newly edited buffer
11267 let new_selections = new_selections
11268 .iter()
11269 .map(|s| {
11270 let start_point = Point::new(s.start.0, 0);
11271 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11272 Selection {
11273 id: s.id,
11274 start: buffer.point_to_offset(start_point),
11275 end: buffer.point_to_offset(end_point),
11276 goal: s.goal,
11277 reversed: s.reversed,
11278 }
11279 })
11280 .collect();
11281
11282 this.change_selections(Default::default(), window, cx, |s| {
11283 s.select(new_selections);
11284 });
11285
11286 this.request_autoscroll(Autoscroll::fit(), cx);
11287 });
11288 }
11289
11290 fn manipulate_immutable_lines<Fn>(
11291 &mut self,
11292 window: &mut Window,
11293 cx: &mut Context<Self>,
11294 mut callback: Fn,
11295 ) where
11296 Fn: FnMut(&mut Vec<&str>),
11297 {
11298 self.manipulate_lines(window, cx, |text| {
11299 let mut lines: Vec<&str> = text.split('\n').collect();
11300 let line_count_before = lines.len();
11301
11302 callback(&mut lines);
11303
11304 LineManipulationResult {
11305 new_text: lines.join("\n"),
11306 line_count_before,
11307 line_count_after: lines.len(),
11308 }
11309 });
11310 }
11311
11312 fn manipulate_mutable_lines<Fn>(
11313 &mut self,
11314 window: &mut Window,
11315 cx: &mut Context<Self>,
11316 mut callback: Fn,
11317 ) where
11318 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11319 {
11320 self.manipulate_lines(window, cx, |text| {
11321 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11322 let line_count_before = lines.len();
11323
11324 callback(&mut lines);
11325
11326 LineManipulationResult {
11327 new_text: lines.join("\n"),
11328 line_count_before,
11329 line_count_after: lines.len(),
11330 }
11331 });
11332 }
11333
11334 pub fn convert_indentation_to_spaces(
11335 &mut self,
11336 _: &ConvertIndentationToSpaces,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) {
11340 let settings = self.buffer.read(cx).language_settings(cx);
11341 let tab_size = settings.tab_size.get() as usize;
11342
11343 self.manipulate_mutable_lines(window, cx, |lines| {
11344 // Allocates a reasonably sized scratch buffer once for the whole loop
11345 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11346 // Avoids recomputing spaces that could be inserted many times
11347 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11348 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11349 .collect();
11350
11351 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11352 let mut chars = line.as_ref().chars();
11353 let mut col = 0;
11354 let mut changed = false;
11355
11356 for ch in chars.by_ref() {
11357 match ch {
11358 ' ' => {
11359 reindented_line.push(' ');
11360 col += 1;
11361 }
11362 '\t' => {
11363 // \t are converted to spaces depending on the current column
11364 let spaces_len = tab_size - (col % tab_size);
11365 reindented_line.extend(&space_cache[spaces_len - 1]);
11366 col += spaces_len;
11367 changed = true;
11368 }
11369 _ => {
11370 // If we dont append before break, the character is consumed
11371 reindented_line.push(ch);
11372 break;
11373 }
11374 }
11375 }
11376
11377 if !changed {
11378 reindented_line.clear();
11379 continue;
11380 }
11381 // Append the rest of the line and replace old reference with new one
11382 reindented_line.extend(chars);
11383 *line = Cow::Owned(reindented_line.clone());
11384 reindented_line.clear();
11385 }
11386 });
11387 }
11388
11389 pub fn convert_indentation_to_tabs(
11390 &mut self,
11391 _: &ConvertIndentationToTabs,
11392 window: &mut Window,
11393 cx: &mut Context<Self>,
11394 ) {
11395 let settings = self.buffer.read(cx).language_settings(cx);
11396 let tab_size = settings.tab_size.get() as usize;
11397
11398 self.manipulate_mutable_lines(window, cx, |lines| {
11399 // Allocates a reasonably sized buffer once for the whole loop
11400 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11401 // Avoids recomputing spaces that could be inserted many times
11402 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11403 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11404 .collect();
11405
11406 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11407 let mut chars = line.chars();
11408 let mut spaces_count = 0;
11409 let mut first_non_indent_char = None;
11410 let mut changed = false;
11411
11412 for ch in chars.by_ref() {
11413 match ch {
11414 ' ' => {
11415 // Keep track of spaces. Append \t when we reach tab_size
11416 spaces_count += 1;
11417 changed = true;
11418 if spaces_count == tab_size {
11419 reindented_line.push('\t');
11420 spaces_count = 0;
11421 }
11422 }
11423 '\t' => {
11424 reindented_line.push('\t');
11425 spaces_count = 0;
11426 }
11427 _ => {
11428 // Dont append it yet, we might have remaining spaces
11429 first_non_indent_char = Some(ch);
11430 break;
11431 }
11432 }
11433 }
11434
11435 if !changed {
11436 reindented_line.clear();
11437 continue;
11438 }
11439 // Remaining spaces that didn't make a full tab stop
11440 if spaces_count > 0 {
11441 reindented_line.extend(&space_cache[spaces_count - 1]);
11442 }
11443 // If we consume an extra character that was not indentation, add it back
11444 if let Some(extra_char) = first_non_indent_char {
11445 reindented_line.push(extra_char);
11446 }
11447 // Append the rest of the line and replace old reference with new one
11448 reindented_line.extend(chars);
11449 *line = Cow::Owned(reindented_line.clone());
11450 reindented_line.clear();
11451 }
11452 });
11453 }
11454
11455 pub fn convert_to_upper_case(
11456 &mut self,
11457 _: &ConvertToUpperCase,
11458 window: &mut Window,
11459 cx: &mut Context<Self>,
11460 ) {
11461 self.manipulate_text(window, cx, |text| text.to_uppercase())
11462 }
11463
11464 pub fn convert_to_lower_case(
11465 &mut self,
11466 _: &ConvertToLowerCase,
11467 window: &mut Window,
11468 cx: &mut Context<Self>,
11469 ) {
11470 self.manipulate_text(window, cx, |text| text.to_lowercase())
11471 }
11472
11473 pub fn convert_to_title_case(
11474 &mut self,
11475 _: &ConvertToTitleCase,
11476 window: &mut Window,
11477 cx: &mut Context<Self>,
11478 ) {
11479 self.manipulate_text(window, cx, |text| {
11480 text.split('\n')
11481 .map(|line| line.to_case(Case::Title))
11482 .join("\n")
11483 })
11484 }
11485
11486 pub fn convert_to_snake_case(
11487 &mut self,
11488 _: &ConvertToSnakeCase,
11489 window: &mut Window,
11490 cx: &mut Context<Self>,
11491 ) {
11492 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11493 }
11494
11495 pub fn convert_to_kebab_case(
11496 &mut self,
11497 _: &ConvertToKebabCase,
11498 window: &mut Window,
11499 cx: &mut Context<Self>,
11500 ) {
11501 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11502 }
11503
11504 pub fn convert_to_upper_camel_case(
11505 &mut self,
11506 _: &ConvertToUpperCamelCase,
11507 window: &mut Window,
11508 cx: &mut Context<Self>,
11509 ) {
11510 self.manipulate_text(window, cx, |text| {
11511 text.split('\n')
11512 .map(|line| line.to_case(Case::UpperCamel))
11513 .join("\n")
11514 })
11515 }
11516
11517 pub fn convert_to_lower_camel_case(
11518 &mut self,
11519 _: &ConvertToLowerCamelCase,
11520 window: &mut Window,
11521 cx: &mut Context<Self>,
11522 ) {
11523 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11524 }
11525
11526 pub fn convert_to_opposite_case(
11527 &mut self,
11528 _: &ConvertToOppositeCase,
11529 window: &mut Window,
11530 cx: &mut Context<Self>,
11531 ) {
11532 self.manipulate_text(window, cx, |text| {
11533 text.chars()
11534 .fold(String::with_capacity(text.len()), |mut t, c| {
11535 if c.is_uppercase() {
11536 t.extend(c.to_lowercase());
11537 } else {
11538 t.extend(c.to_uppercase());
11539 }
11540 t
11541 })
11542 })
11543 }
11544
11545 pub fn convert_to_sentence_case(
11546 &mut self,
11547 _: &ConvertToSentenceCase,
11548 window: &mut Window,
11549 cx: &mut Context<Self>,
11550 ) {
11551 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11552 }
11553
11554 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11555 self.manipulate_text(window, cx, |text| {
11556 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11557 if has_upper_case_characters {
11558 text.to_lowercase()
11559 } else {
11560 text.to_uppercase()
11561 }
11562 })
11563 }
11564
11565 pub fn convert_to_rot13(
11566 &mut self,
11567 _: &ConvertToRot13,
11568 window: &mut Window,
11569 cx: &mut Context<Self>,
11570 ) {
11571 self.manipulate_text(window, cx, |text| {
11572 text.chars()
11573 .map(|c| match c {
11574 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11575 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11576 _ => c,
11577 })
11578 .collect()
11579 })
11580 }
11581
11582 pub fn convert_to_rot47(
11583 &mut self,
11584 _: &ConvertToRot47,
11585 window: &mut Window,
11586 cx: &mut Context<Self>,
11587 ) {
11588 self.manipulate_text(window, cx, |text| {
11589 text.chars()
11590 .map(|c| {
11591 let code_point = c as u32;
11592 if code_point >= 33 && code_point <= 126 {
11593 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11594 }
11595 c
11596 })
11597 .collect()
11598 })
11599 }
11600
11601 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11602 where
11603 Fn: FnMut(&str) -> String,
11604 {
11605 let buffer = self.buffer.read(cx).snapshot(cx);
11606
11607 let mut new_selections = Vec::new();
11608 let mut edits = Vec::new();
11609 let mut selection_adjustment = 0i32;
11610
11611 for selection in self.selections.all_adjusted(cx) {
11612 let selection_is_empty = selection.is_empty();
11613
11614 let (start, end) = if selection_is_empty {
11615 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11616 (word_range.start, word_range.end)
11617 } else {
11618 (
11619 buffer.point_to_offset(selection.start),
11620 buffer.point_to_offset(selection.end),
11621 )
11622 };
11623
11624 let text = buffer.text_for_range(start..end).collect::<String>();
11625 let old_length = text.len() as i32;
11626 let text = callback(&text);
11627
11628 new_selections.push(Selection {
11629 start: (start as i32 - selection_adjustment) as usize,
11630 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11631 goal: SelectionGoal::None,
11632 id: selection.id,
11633 reversed: selection.reversed,
11634 });
11635
11636 selection_adjustment += old_length - text.len() as i32;
11637
11638 edits.push((start..end, text));
11639 }
11640
11641 self.transact(window, cx, |this, window, cx| {
11642 this.buffer.update(cx, |buffer, cx| {
11643 buffer.edit(edits, None, cx);
11644 });
11645
11646 this.change_selections(Default::default(), window, cx, |s| {
11647 s.select(new_selections);
11648 });
11649
11650 this.request_autoscroll(Autoscroll::fit(), cx);
11651 });
11652 }
11653
11654 pub fn move_selection_on_drop(
11655 &mut self,
11656 selection: &Selection<Anchor>,
11657 target: DisplayPoint,
11658 is_cut: bool,
11659 window: &mut Window,
11660 cx: &mut Context<Self>,
11661 ) {
11662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11663 let buffer = display_map.buffer_snapshot();
11664 let mut edits = Vec::new();
11665 let insert_point = display_map
11666 .clip_point(target, Bias::Left)
11667 .to_point(&display_map);
11668 let text = buffer
11669 .text_for_range(selection.start..selection.end)
11670 .collect::<String>();
11671 if is_cut {
11672 edits.push(((selection.start..selection.end), String::new()));
11673 }
11674 let insert_anchor = buffer.anchor_before(insert_point);
11675 edits.push(((insert_anchor..insert_anchor), text));
11676 let last_edit_start = insert_anchor.bias_left(buffer);
11677 let last_edit_end = insert_anchor.bias_right(buffer);
11678 self.transact(window, cx, |this, window, cx| {
11679 this.buffer.update(cx, |buffer, cx| {
11680 buffer.edit(edits, None, cx);
11681 });
11682 this.change_selections(Default::default(), window, cx, |s| {
11683 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11684 });
11685 });
11686 }
11687
11688 pub fn clear_selection_drag_state(&mut self) {
11689 self.selection_drag_state = SelectionDragState::None;
11690 }
11691
11692 pub fn duplicate(
11693 &mut self,
11694 upwards: bool,
11695 whole_lines: bool,
11696 window: &mut Window,
11697 cx: &mut Context<Self>,
11698 ) {
11699 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11700
11701 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11702 let buffer = display_map.buffer_snapshot();
11703 let selections = self.selections.all::<Point>(cx);
11704
11705 let mut edits = Vec::new();
11706 let mut selections_iter = selections.iter().peekable();
11707 while let Some(selection) = selections_iter.next() {
11708 let mut rows = selection.spanned_rows(false, &display_map);
11709 // duplicate line-wise
11710 if whole_lines || selection.start == selection.end {
11711 // Avoid duplicating the same lines twice.
11712 while let Some(next_selection) = selections_iter.peek() {
11713 let next_rows = next_selection.spanned_rows(false, &display_map);
11714 if next_rows.start < rows.end {
11715 rows.end = next_rows.end;
11716 selections_iter.next().unwrap();
11717 } else {
11718 break;
11719 }
11720 }
11721
11722 // Copy the text from the selected row region and splice it either at the start
11723 // or end of the region.
11724 let start = Point::new(rows.start.0, 0);
11725 let end = Point::new(
11726 rows.end.previous_row().0,
11727 buffer.line_len(rows.end.previous_row()),
11728 );
11729
11730 let mut text = buffer.text_for_range(start..end).collect::<String>();
11731
11732 let insert_location = if upwards {
11733 // When duplicating upward, we need to insert before the current line.
11734 // If we're on the last line and it doesn't end with a newline,
11735 // we need to add a newline before the duplicated content.
11736 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11737 && buffer.max_point().column > 0
11738 && !text.ends_with('\n');
11739
11740 if needs_leading_newline {
11741 text.insert(0, '\n');
11742 end
11743 } else {
11744 text.push('\n');
11745 Point::new(rows.end.0, 0)
11746 }
11747 } else {
11748 text.push('\n');
11749 start
11750 };
11751 edits.push((insert_location..insert_location, text));
11752 } else {
11753 // duplicate character-wise
11754 let start = selection.start;
11755 let end = selection.end;
11756 let text = buffer.text_for_range(start..end).collect::<String>();
11757 edits.push((selection.end..selection.end, text));
11758 }
11759 }
11760
11761 self.transact(window, cx, |this, _, cx| {
11762 this.buffer.update(cx, |buffer, cx| {
11763 buffer.edit(edits, None, cx);
11764 });
11765
11766 this.request_autoscroll(Autoscroll::fit(), cx);
11767 });
11768 }
11769
11770 pub fn duplicate_line_up(
11771 &mut self,
11772 _: &DuplicateLineUp,
11773 window: &mut Window,
11774 cx: &mut Context<Self>,
11775 ) {
11776 self.duplicate(true, true, window, cx);
11777 }
11778
11779 pub fn duplicate_line_down(
11780 &mut self,
11781 _: &DuplicateLineDown,
11782 window: &mut Window,
11783 cx: &mut Context<Self>,
11784 ) {
11785 self.duplicate(false, true, window, cx);
11786 }
11787
11788 pub fn duplicate_selection(
11789 &mut self,
11790 _: &DuplicateSelection,
11791 window: &mut Window,
11792 cx: &mut Context<Self>,
11793 ) {
11794 self.duplicate(false, false, window, cx);
11795 }
11796
11797 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11798 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11799 if self.mode.is_single_line() {
11800 cx.propagate();
11801 return;
11802 }
11803
11804 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11805 let buffer = self.buffer.read(cx).snapshot(cx);
11806
11807 let mut edits = Vec::new();
11808 let mut unfold_ranges = Vec::new();
11809 let mut refold_creases = Vec::new();
11810
11811 let selections = self.selections.all::<Point>(cx);
11812 let mut selections = selections.iter().peekable();
11813 let mut contiguous_row_selections = Vec::new();
11814 let mut new_selections = Vec::new();
11815
11816 while let Some(selection) = selections.next() {
11817 // Find all the selections that span a contiguous row range
11818 let (start_row, end_row) = consume_contiguous_rows(
11819 &mut contiguous_row_selections,
11820 selection,
11821 &display_map,
11822 &mut selections,
11823 );
11824
11825 // Move the text spanned by the row range to be before the line preceding the row range
11826 if start_row.0 > 0 {
11827 let range_to_move = Point::new(
11828 start_row.previous_row().0,
11829 buffer.line_len(start_row.previous_row()),
11830 )
11831 ..Point::new(
11832 end_row.previous_row().0,
11833 buffer.line_len(end_row.previous_row()),
11834 );
11835 let insertion_point = display_map
11836 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11837 .0;
11838
11839 // Don't move lines across excerpts
11840 if buffer
11841 .excerpt_containing(insertion_point..range_to_move.end)
11842 .is_some()
11843 {
11844 let text = buffer
11845 .text_for_range(range_to_move.clone())
11846 .flat_map(|s| s.chars())
11847 .skip(1)
11848 .chain(['\n'])
11849 .collect::<String>();
11850
11851 edits.push((
11852 buffer.anchor_after(range_to_move.start)
11853 ..buffer.anchor_before(range_to_move.end),
11854 String::new(),
11855 ));
11856 let insertion_anchor = buffer.anchor_after(insertion_point);
11857 edits.push((insertion_anchor..insertion_anchor, text));
11858
11859 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11860
11861 // Move selections up
11862 new_selections.extend(contiguous_row_selections.drain(..).map(
11863 |mut selection| {
11864 selection.start.row -= row_delta;
11865 selection.end.row -= row_delta;
11866 selection
11867 },
11868 ));
11869
11870 // Move folds up
11871 unfold_ranges.push(range_to_move.clone());
11872 for fold in display_map.folds_in_range(
11873 buffer.anchor_before(range_to_move.start)
11874 ..buffer.anchor_after(range_to_move.end),
11875 ) {
11876 let mut start = fold.range.start.to_point(&buffer);
11877 let mut end = fold.range.end.to_point(&buffer);
11878 start.row -= row_delta;
11879 end.row -= row_delta;
11880 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11881 }
11882 }
11883 }
11884
11885 // If we didn't move line(s), preserve the existing selections
11886 new_selections.append(&mut contiguous_row_selections);
11887 }
11888
11889 self.transact(window, cx, |this, window, cx| {
11890 this.unfold_ranges(&unfold_ranges, true, true, cx);
11891 this.buffer.update(cx, |buffer, cx| {
11892 for (range, text) in edits {
11893 buffer.edit([(range, text)], None, cx);
11894 }
11895 });
11896 this.fold_creases(refold_creases, true, window, cx);
11897 this.change_selections(Default::default(), window, cx, |s| {
11898 s.select(new_selections);
11899 })
11900 });
11901 }
11902
11903 pub fn move_line_down(
11904 &mut self,
11905 _: &MoveLineDown,
11906 window: &mut Window,
11907 cx: &mut Context<Self>,
11908 ) {
11909 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11910 if self.mode.is_single_line() {
11911 cx.propagate();
11912 return;
11913 }
11914
11915 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11916 let buffer = self.buffer.read(cx).snapshot(cx);
11917
11918 let mut edits = Vec::new();
11919 let mut unfold_ranges = Vec::new();
11920 let mut refold_creases = Vec::new();
11921
11922 let selections = self.selections.all::<Point>(cx);
11923 let mut selections = selections.iter().peekable();
11924 let mut contiguous_row_selections = Vec::new();
11925 let mut new_selections = Vec::new();
11926
11927 while let Some(selection) = selections.next() {
11928 // Find all the selections that span a contiguous row range
11929 let (start_row, end_row) = consume_contiguous_rows(
11930 &mut contiguous_row_selections,
11931 selection,
11932 &display_map,
11933 &mut selections,
11934 );
11935
11936 // Move the text spanned by the row range to be after the last line of the row range
11937 if end_row.0 <= buffer.max_point().row {
11938 let range_to_move =
11939 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11940 let insertion_point = display_map
11941 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11942 .0;
11943
11944 // Don't move lines across excerpt boundaries
11945 if buffer
11946 .excerpt_containing(range_to_move.start..insertion_point)
11947 .is_some()
11948 {
11949 let mut text = String::from("\n");
11950 text.extend(buffer.text_for_range(range_to_move.clone()));
11951 text.pop(); // Drop trailing newline
11952 edits.push((
11953 buffer.anchor_after(range_to_move.start)
11954 ..buffer.anchor_before(range_to_move.end),
11955 String::new(),
11956 ));
11957 let insertion_anchor = buffer.anchor_after(insertion_point);
11958 edits.push((insertion_anchor..insertion_anchor, text));
11959
11960 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11961
11962 // Move selections down
11963 new_selections.extend(contiguous_row_selections.drain(..).map(
11964 |mut selection| {
11965 selection.start.row += row_delta;
11966 selection.end.row += row_delta;
11967 selection
11968 },
11969 ));
11970
11971 // Move folds down
11972 unfold_ranges.push(range_to_move.clone());
11973 for fold in display_map.folds_in_range(
11974 buffer.anchor_before(range_to_move.start)
11975 ..buffer.anchor_after(range_to_move.end),
11976 ) {
11977 let mut start = fold.range.start.to_point(&buffer);
11978 let mut end = fold.range.end.to_point(&buffer);
11979 start.row += row_delta;
11980 end.row += row_delta;
11981 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11982 }
11983 }
11984 }
11985
11986 // If we didn't move line(s), preserve the existing selections
11987 new_selections.append(&mut contiguous_row_selections);
11988 }
11989
11990 self.transact(window, cx, |this, window, cx| {
11991 this.unfold_ranges(&unfold_ranges, true, true, cx);
11992 this.buffer.update(cx, |buffer, cx| {
11993 for (range, text) in edits {
11994 buffer.edit([(range, text)], None, cx);
11995 }
11996 });
11997 this.fold_creases(refold_creases, true, window, cx);
11998 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11999 });
12000 }
12001
12002 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12003 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12004 let text_layout_details = &self.text_layout_details(window);
12005 self.transact(window, cx, |this, window, cx| {
12006 let edits = this.change_selections(Default::default(), window, cx, |s| {
12007 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12008 s.move_with(|display_map, selection| {
12009 if !selection.is_empty() {
12010 return;
12011 }
12012
12013 let mut head = selection.head();
12014 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12015 if head.column() == display_map.line_len(head.row()) {
12016 transpose_offset = display_map
12017 .buffer_snapshot()
12018 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12019 }
12020
12021 if transpose_offset == 0 {
12022 return;
12023 }
12024
12025 *head.column_mut() += 1;
12026 head = display_map.clip_point(head, Bias::Right);
12027 let goal = SelectionGoal::HorizontalPosition(
12028 display_map
12029 .x_for_display_point(head, text_layout_details)
12030 .into(),
12031 );
12032 selection.collapse_to(head, goal);
12033
12034 let transpose_start = display_map
12035 .buffer_snapshot()
12036 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12037 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12038 let transpose_end = display_map
12039 .buffer_snapshot()
12040 .clip_offset(transpose_offset + 1, Bias::Right);
12041 if let Some(ch) = display_map
12042 .buffer_snapshot()
12043 .chars_at(transpose_start)
12044 .next()
12045 {
12046 edits.push((transpose_start..transpose_offset, String::new()));
12047 edits.push((transpose_end..transpose_end, ch.to_string()));
12048 }
12049 }
12050 });
12051 edits
12052 });
12053 this.buffer
12054 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12055 let selections = this.selections.all::<usize>(cx);
12056 this.change_selections(Default::default(), window, cx, |s| {
12057 s.select(selections);
12058 });
12059 });
12060 }
12061
12062 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12063 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12064 if self.mode.is_single_line() {
12065 cx.propagate();
12066 return;
12067 }
12068
12069 self.rewrap_impl(RewrapOptions::default(), cx)
12070 }
12071
12072 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12073 let buffer = self.buffer.read(cx).snapshot(cx);
12074 let selections = self.selections.all::<Point>(cx);
12075
12076 #[derive(Clone, Debug, PartialEq)]
12077 enum CommentFormat {
12078 /// single line comment, with prefix for line
12079 Line(String),
12080 /// single line within a block comment, with prefix for line
12081 BlockLine(String),
12082 /// a single line of a block comment that includes the initial delimiter
12083 BlockCommentWithStart(BlockCommentConfig),
12084 /// a single line of a block comment that includes the ending delimiter
12085 BlockCommentWithEnd(BlockCommentConfig),
12086 }
12087
12088 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12089 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12090 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12091 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12092 .peekable();
12093
12094 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12095 row
12096 } else {
12097 return Vec::new();
12098 };
12099
12100 let language_settings = buffer.language_settings_at(selection.head(), cx);
12101 let language_scope = buffer.language_scope_at(selection.head());
12102
12103 let indent_and_prefix_for_row =
12104 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12105 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12106 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12107 &language_scope
12108 {
12109 let indent_end = Point::new(row, indent.len);
12110 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12111 let line_text_after_indent = buffer
12112 .text_for_range(indent_end..line_end)
12113 .collect::<String>();
12114
12115 let is_within_comment_override = buffer
12116 .language_scope_at(indent_end)
12117 .is_some_and(|scope| scope.override_name() == Some("comment"));
12118 let comment_delimiters = if is_within_comment_override {
12119 // we are within a comment syntax node, but we don't
12120 // yet know what kind of comment: block, doc or line
12121 match (
12122 language_scope.documentation_comment(),
12123 language_scope.block_comment(),
12124 ) {
12125 (Some(config), _) | (_, Some(config))
12126 if buffer.contains_str_at(indent_end, &config.start) =>
12127 {
12128 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12129 }
12130 (Some(config), _) | (_, Some(config))
12131 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12132 {
12133 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12134 }
12135 (Some(config), _) | (_, Some(config))
12136 if buffer.contains_str_at(indent_end, &config.prefix) =>
12137 {
12138 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12139 }
12140 (_, _) => language_scope
12141 .line_comment_prefixes()
12142 .iter()
12143 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12144 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12145 }
12146 } else {
12147 // we not in an overridden comment node, but we may
12148 // be within a non-overridden line comment node
12149 language_scope
12150 .line_comment_prefixes()
12151 .iter()
12152 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12153 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12154 };
12155
12156 let rewrap_prefix = language_scope
12157 .rewrap_prefixes()
12158 .iter()
12159 .find_map(|prefix_regex| {
12160 prefix_regex.find(&line_text_after_indent).map(|mat| {
12161 if mat.start() == 0 {
12162 Some(mat.as_str().to_string())
12163 } else {
12164 None
12165 }
12166 })
12167 })
12168 .flatten();
12169 (comment_delimiters, rewrap_prefix)
12170 } else {
12171 (None, None)
12172 };
12173 (indent, comment_prefix, rewrap_prefix)
12174 };
12175
12176 let mut ranges = Vec::new();
12177 let from_empty_selection = selection.is_empty();
12178
12179 let mut current_range_start = first_row;
12180 let mut prev_row = first_row;
12181 let (
12182 mut current_range_indent,
12183 mut current_range_comment_delimiters,
12184 mut current_range_rewrap_prefix,
12185 ) = indent_and_prefix_for_row(first_row);
12186
12187 for row in non_blank_rows_iter.skip(1) {
12188 let has_paragraph_break = row > prev_row + 1;
12189
12190 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12191 indent_and_prefix_for_row(row);
12192
12193 let has_indent_change = row_indent != current_range_indent;
12194 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12195
12196 let has_boundary_change = has_comment_change
12197 || row_rewrap_prefix.is_some()
12198 || (has_indent_change && current_range_comment_delimiters.is_some());
12199
12200 if has_paragraph_break || has_boundary_change {
12201 ranges.push((
12202 language_settings.clone(),
12203 Point::new(current_range_start, 0)
12204 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12205 current_range_indent,
12206 current_range_comment_delimiters.clone(),
12207 current_range_rewrap_prefix.clone(),
12208 from_empty_selection,
12209 ));
12210 current_range_start = row;
12211 current_range_indent = row_indent;
12212 current_range_comment_delimiters = row_comment_delimiters;
12213 current_range_rewrap_prefix = row_rewrap_prefix;
12214 }
12215 prev_row = row;
12216 }
12217
12218 ranges.push((
12219 language_settings.clone(),
12220 Point::new(current_range_start, 0)
12221 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12222 current_range_indent,
12223 current_range_comment_delimiters,
12224 current_range_rewrap_prefix,
12225 from_empty_selection,
12226 ));
12227
12228 ranges
12229 });
12230
12231 let mut edits = Vec::new();
12232 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12233
12234 for (
12235 language_settings,
12236 wrap_range,
12237 mut indent_size,
12238 comment_prefix,
12239 rewrap_prefix,
12240 from_empty_selection,
12241 ) in wrap_ranges
12242 {
12243 let mut start_row = wrap_range.start.row;
12244 let mut end_row = wrap_range.end.row;
12245
12246 // Skip selections that overlap with a range that has already been rewrapped.
12247 let selection_range = start_row..end_row;
12248 if rewrapped_row_ranges
12249 .iter()
12250 .any(|range| range.overlaps(&selection_range))
12251 {
12252 continue;
12253 }
12254
12255 let tab_size = language_settings.tab_size;
12256
12257 let (line_prefix, inside_comment) = match &comment_prefix {
12258 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12259 (Some(prefix.as_str()), true)
12260 }
12261 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12262 (Some(prefix.as_ref()), true)
12263 }
12264 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12265 start: _,
12266 end: _,
12267 prefix,
12268 tab_size,
12269 })) => {
12270 indent_size.len += tab_size;
12271 (Some(prefix.as_ref()), true)
12272 }
12273 None => (None, false),
12274 };
12275 let indent_prefix = indent_size.chars().collect::<String>();
12276 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12277
12278 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12279 RewrapBehavior::InComments => inside_comment,
12280 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12281 RewrapBehavior::Anywhere => true,
12282 };
12283
12284 let should_rewrap = options.override_language_settings
12285 || allow_rewrap_based_on_language
12286 || self.hard_wrap.is_some();
12287 if !should_rewrap {
12288 continue;
12289 }
12290
12291 if from_empty_selection {
12292 'expand_upwards: while start_row > 0 {
12293 let prev_row = start_row - 1;
12294 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12295 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12296 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12297 {
12298 start_row = prev_row;
12299 } else {
12300 break 'expand_upwards;
12301 }
12302 }
12303
12304 'expand_downwards: while end_row < buffer.max_point().row {
12305 let next_row = end_row + 1;
12306 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12307 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12308 && !buffer.is_line_blank(MultiBufferRow(next_row))
12309 {
12310 end_row = next_row;
12311 } else {
12312 break 'expand_downwards;
12313 }
12314 }
12315 }
12316
12317 let start = Point::new(start_row, 0);
12318 let start_offset = ToOffset::to_offset(&start, &buffer);
12319 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12320 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12321 let mut first_line_delimiter = None;
12322 let mut last_line_delimiter = None;
12323 let Some(lines_without_prefixes) = selection_text
12324 .lines()
12325 .enumerate()
12326 .map(|(ix, line)| {
12327 let line_trimmed = line.trim_start();
12328 if rewrap_prefix.is_some() && ix > 0 {
12329 Ok(line_trimmed)
12330 } else if let Some(
12331 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12332 start,
12333 prefix,
12334 end,
12335 tab_size,
12336 })
12337 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12338 start,
12339 prefix,
12340 end,
12341 tab_size,
12342 }),
12343 ) = &comment_prefix
12344 {
12345 let line_trimmed = line_trimmed
12346 .strip_prefix(start.as_ref())
12347 .map(|s| {
12348 let mut indent_size = indent_size;
12349 indent_size.len -= tab_size;
12350 let indent_prefix: String = indent_size.chars().collect();
12351 first_line_delimiter = Some((indent_prefix, start));
12352 s.trim_start()
12353 })
12354 .unwrap_or(line_trimmed);
12355 let line_trimmed = line_trimmed
12356 .strip_suffix(end.as_ref())
12357 .map(|s| {
12358 last_line_delimiter = Some(end);
12359 s.trim_end()
12360 })
12361 .unwrap_or(line_trimmed);
12362 let line_trimmed = line_trimmed
12363 .strip_prefix(prefix.as_ref())
12364 .unwrap_or(line_trimmed);
12365 Ok(line_trimmed)
12366 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12367 line_trimmed.strip_prefix(prefix).with_context(|| {
12368 format!("line did not start with prefix {prefix:?}: {line:?}")
12369 })
12370 } else {
12371 line_trimmed
12372 .strip_prefix(&line_prefix.trim_start())
12373 .with_context(|| {
12374 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12375 })
12376 }
12377 })
12378 .collect::<Result<Vec<_>, _>>()
12379 .log_err()
12380 else {
12381 continue;
12382 };
12383
12384 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12385 buffer
12386 .language_settings_at(Point::new(start_row, 0), cx)
12387 .preferred_line_length as usize
12388 });
12389
12390 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12391 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12392 } else {
12393 line_prefix.clone()
12394 };
12395
12396 let wrapped_text = {
12397 let mut wrapped_text = wrap_with_prefix(
12398 line_prefix,
12399 subsequent_lines_prefix,
12400 lines_without_prefixes.join("\n"),
12401 wrap_column,
12402 tab_size,
12403 options.preserve_existing_whitespace,
12404 );
12405
12406 if let Some((indent, delimiter)) = first_line_delimiter {
12407 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12408 }
12409 if let Some(last_line) = last_line_delimiter {
12410 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12411 }
12412
12413 wrapped_text
12414 };
12415
12416 // TODO: should always use char-based diff while still supporting cursor behavior that
12417 // matches vim.
12418 let mut diff_options = DiffOptions::default();
12419 if options.override_language_settings {
12420 diff_options.max_word_diff_len = 0;
12421 diff_options.max_word_diff_line_count = 0;
12422 } else {
12423 diff_options.max_word_diff_len = usize::MAX;
12424 diff_options.max_word_diff_line_count = usize::MAX;
12425 }
12426
12427 for (old_range, new_text) in
12428 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12429 {
12430 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12431 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12432 edits.push((edit_start..edit_end, new_text));
12433 }
12434
12435 rewrapped_row_ranges.push(start_row..=end_row);
12436 }
12437
12438 self.buffer
12439 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12440 }
12441
12442 pub fn cut_common(
12443 &mut self,
12444 cut_no_selection_line: bool,
12445 window: &mut Window,
12446 cx: &mut Context<Self>,
12447 ) -> ClipboardItem {
12448 let mut text = String::new();
12449 let buffer = self.buffer.read(cx).snapshot(cx);
12450 let mut selections = self.selections.all::<Point>(cx);
12451 let mut clipboard_selections = Vec::with_capacity(selections.len());
12452 {
12453 let max_point = buffer.max_point();
12454 let mut is_first = true;
12455 for selection in &mut selections {
12456 let is_entire_line =
12457 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12458 if is_entire_line {
12459 selection.start = Point::new(selection.start.row, 0);
12460 if !selection.is_empty() && selection.end.column == 0 {
12461 selection.end = cmp::min(max_point, selection.end);
12462 } else {
12463 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12464 }
12465 selection.goal = SelectionGoal::None;
12466 }
12467 if is_first {
12468 is_first = false;
12469 } else {
12470 text += "\n";
12471 }
12472 let mut len = 0;
12473 for chunk in buffer.text_for_range(selection.start..selection.end) {
12474 text.push_str(chunk);
12475 len += chunk.len();
12476 }
12477 clipboard_selections.push(ClipboardSelection {
12478 len,
12479 is_entire_line,
12480 first_line_indent: buffer
12481 .indent_size_for_line(MultiBufferRow(selection.start.row))
12482 .len,
12483 });
12484 }
12485 }
12486
12487 self.transact(window, cx, |this, window, cx| {
12488 this.change_selections(Default::default(), window, cx, |s| {
12489 s.select(selections);
12490 });
12491 this.insert("", window, cx);
12492 });
12493 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12494 }
12495
12496 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12497 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12498 let item = self.cut_common(true, window, cx);
12499 cx.write_to_clipboard(item);
12500 }
12501
12502 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12503 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12504 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12505 s.move_with(|snapshot, sel| {
12506 if sel.is_empty() {
12507 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12508 }
12509 if sel.is_empty() {
12510 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12511 }
12512 });
12513 });
12514 let item = self.cut_common(false, window, cx);
12515 cx.set_global(KillRing(item))
12516 }
12517
12518 pub fn kill_ring_yank(
12519 &mut self,
12520 _: &KillRingYank,
12521 window: &mut Window,
12522 cx: &mut Context<Self>,
12523 ) {
12524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12525 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12526 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12527 (kill_ring.text().to_string(), kill_ring.metadata_json())
12528 } else {
12529 return;
12530 }
12531 } else {
12532 return;
12533 };
12534 self.do_paste(&text, metadata, false, window, cx);
12535 }
12536
12537 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12538 self.do_copy(true, cx);
12539 }
12540
12541 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12542 self.do_copy(false, cx);
12543 }
12544
12545 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12546 let selections = self.selections.all::<Point>(cx);
12547 let buffer = self.buffer.read(cx).read(cx);
12548 let mut text = String::new();
12549
12550 let mut clipboard_selections = Vec::with_capacity(selections.len());
12551 {
12552 let max_point = buffer.max_point();
12553 let mut is_first = true;
12554 for selection in &selections {
12555 let mut start = selection.start;
12556 let mut end = selection.end;
12557 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12558 let mut add_trailing_newline = false;
12559 if is_entire_line {
12560 start = Point::new(start.row, 0);
12561 let next_line_start = Point::new(end.row + 1, 0);
12562 if next_line_start <= max_point {
12563 end = next_line_start;
12564 } else {
12565 // We're on the last line without a trailing newline.
12566 // Copy to the end of the line and add a newline afterwards.
12567 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12568 add_trailing_newline = true;
12569 }
12570 }
12571
12572 let mut trimmed_selections = Vec::new();
12573 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12574 let row = MultiBufferRow(start.row);
12575 let first_indent = buffer.indent_size_for_line(row);
12576 if first_indent.len == 0 || start.column > first_indent.len {
12577 trimmed_selections.push(start..end);
12578 } else {
12579 trimmed_selections.push(
12580 Point::new(row.0, first_indent.len)
12581 ..Point::new(row.0, buffer.line_len(row)),
12582 );
12583 for row in start.row + 1..=end.row {
12584 let mut line_len = buffer.line_len(MultiBufferRow(row));
12585 if row == end.row {
12586 line_len = end.column;
12587 }
12588 if line_len == 0 {
12589 trimmed_selections
12590 .push(Point::new(row, 0)..Point::new(row, line_len));
12591 continue;
12592 }
12593 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12594 if row_indent_size.len >= first_indent.len {
12595 trimmed_selections.push(
12596 Point::new(row, first_indent.len)..Point::new(row, line_len),
12597 );
12598 } else {
12599 trimmed_selections.clear();
12600 trimmed_selections.push(start..end);
12601 break;
12602 }
12603 }
12604 }
12605 } else {
12606 trimmed_selections.push(start..end);
12607 }
12608
12609 for trimmed_range in trimmed_selections {
12610 if is_first {
12611 is_first = false;
12612 } else {
12613 text += "\n";
12614 }
12615 let mut len = 0;
12616 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12617 text.push_str(chunk);
12618 len += chunk.len();
12619 }
12620 if add_trailing_newline {
12621 text.push('\n');
12622 len += 1;
12623 }
12624 clipboard_selections.push(ClipboardSelection {
12625 len,
12626 is_entire_line,
12627 first_line_indent: buffer
12628 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12629 .len,
12630 });
12631 }
12632 }
12633 }
12634
12635 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12636 text,
12637 clipboard_selections,
12638 ));
12639 }
12640
12641 pub fn do_paste(
12642 &mut self,
12643 text: &String,
12644 clipboard_selections: Option<Vec<ClipboardSelection>>,
12645 handle_entire_lines: bool,
12646 window: &mut Window,
12647 cx: &mut Context<Self>,
12648 ) {
12649 if self.read_only(cx) {
12650 return;
12651 }
12652
12653 let clipboard_text = Cow::Borrowed(text.as_str());
12654
12655 self.transact(window, cx, |this, window, cx| {
12656 let had_active_edit_prediction = this.has_active_edit_prediction();
12657 let old_selections = this.selections.all::<usize>(cx);
12658 let cursor_offset = this.selections.last::<usize>(cx).head();
12659
12660 if let Some(mut clipboard_selections) = clipboard_selections {
12661 let all_selections_were_entire_line =
12662 clipboard_selections.iter().all(|s| s.is_entire_line);
12663 let first_selection_indent_column =
12664 clipboard_selections.first().map(|s| s.first_line_indent);
12665 if clipboard_selections.len() != old_selections.len() {
12666 clipboard_selections.drain(..);
12667 }
12668 let mut auto_indent_on_paste = true;
12669
12670 this.buffer.update(cx, |buffer, cx| {
12671 let snapshot = buffer.read(cx);
12672 auto_indent_on_paste = snapshot
12673 .language_settings_at(cursor_offset, cx)
12674 .auto_indent_on_paste;
12675
12676 let mut start_offset = 0;
12677 let mut edits = Vec::new();
12678 let mut original_indent_columns = Vec::new();
12679 for (ix, selection) in old_selections.iter().enumerate() {
12680 let to_insert;
12681 let entire_line;
12682 let original_indent_column;
12683 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12684 let end_offset = start_offset + clipboard_selection.len;
12685 to_insert = &clipboard_text[start_offset..end_offset];
12686 entire_line = clipboard_selection.is_entire_line;
12687 start_offset = end_offset + 1;
12688 original_indent_column = Some(clipboard_selection.first_line_indent);
12689 } else {
12690 to_insert = &*clipboard_text;
12691 entire_line = all_selections_were_entire_line;
12692 original_indent_column = first_selection_indent_column
12693 }
12694
12695 let (range, to_insert) =
12696 if selection.is_empty() && handle_entire_lines && entire_line {
12697 // If the corresponding selection was empty when this slice of the
12698 // clipboard text was written, then the entire line containing the
12699 // selection was copied. If this selection is also currently empty,
12700 // then paste the line before the current line of the buffer.
12701 let column = selection.start.to_point(&snapshot).column as usize;
12702 let line_start = selection.start - column;
12703 (line_start..line_start, Cow::Borrowed(to_insert))
12704 } else {
12705 let language = snapshot.language_at(selection.head());
12706 let range = selection.range();
12707 if let Some(language) = language
12708 && language.name() == "Markdown".into()
12709 {
12710 edit_for_markdown_paste(
12711 &snapshot,
12712 range,
12713 to_insert,
12714 url::Url::parse(to_insert).ok(),
12715 )
12716 } else {
12717 (range, Cow::Borrowed(to_insert))
12718 }
12719 };
12720
12721 edits.push((range, to_insert));
12722 original_indent_columns.push(original_indent_column);
12723 }
12724 drop(snapshot);
12725
12726 buffer.edit(
12727 edits,
12728 if auto_indent_on_paste {
12729 Some(AutoindentMode::Block {
12730 original_indent_columns,
12731 })
12732 } else {
12733 None
12734 },
12735 cx,
12736 );
12737 });
12738
12739 let selections = this.selections.all::<usize>(cx);
12740 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12741 } else {
12742 let url = url::Url::parse(&clipboard_text).ok();
12743
12744 let auto_indent_mode = if !clipboard_text.is_empty() {
12745 Some(AutoindentMode::Block {
12746 original_indent_columns: Vec::new(),
12747 })
12748 } else {
12749 None
12750 };
12751
12752 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12753 let snapshot = buffer.snapshot(cx);
12754
12755 let anchors = old_selections
12756 .iter()
12757 .map(|s| {
12758 let anchor = snapshot.anchor_after(s.head());
12759 s.map(|_| anchor)
12760 })
12761 .collect::<Vec<_>>();
12762
12763 let mut edits = Vec::new();
12764
12765 for selection in old_selections.iter() {
12766 let language = snapshot.language_at(selection.head());
12767 let range = selection.range();
12768
12769 let (edit_range, edit_text) = if let Some(language) = language
12770 && language.name() == "Markdown".into()
12771 {
12772 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12773 } else {
12774 (range, clipboard_text.clone())
12775 };
12776
12777 edits.push((edit_range, edit_text));
12778 }
12779
12780 drop(snapshot);
12781 buffer.edit(edits, auto_indent_mode, cx);
12782
12783 anchors
12784 });
12785
12786 this.change_selections(Default::default(), window, cx, |s| {
12787 s.select_anchors(selection_anchors);
12788 });
12789 }
12790
12791 let trigger_in_words =
12792 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12793
12794 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12795 });
12796 }
12797
12798 pub fn diff_clipboard_with_selection(
12799 &mut self,
12800 _: &DiffClipboardWithSelection,
12801 window: &mut Window,
12802 cx: &mut Context<Self>,
12803 ) {
12804 let selections = self.selections.all::<usize>(cx);
12805
12806 if selections.is_empty() {
12807 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12808 return;
12809 };
12810
12811 let clipboard_text = match cx.read_from_clipboard() {
12812 Some(item) => match item.entries().first() {
12813 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12814 _ => None,
12815 },
12816 None => None,
12817 };
12818
12819 let Some(clipboard_text) = clipboard_text else {
12820 log::warn!("Clipboard doesn't contain text.");
12821 return;
12822 };
12823
12824 window.dispatch_action(
12825 Box::new(DiffClipboardWithSelectionData {
12826 clipboard_text,
12827 editor: cx.entity(),
12828 }),
12829 cx,
12830 );
12831 }
12832
12833 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12834 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12835 if let Some(item) = cx.read_from_clipboard() {
12836 let entries = item.entries();
12837
12838 match entries.first() {
12839 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12840 // of all the pasted entries.
12841 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12842 .do_paste(
12843 clipboard_string.text(),
12844 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12845 true,
12846 window,
12847 cx,
12848 ),
12849 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12850 }
12851 }
12852 }
12853
12854 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12855 if self.read_only(cx) {
12856 return;
12857 }
12858
12859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12860
12861 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12862 if let Some((selections, _)) =
12863 self.selection_history.transaction(transaction_id).cloned()
12864 {
12865 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12866 s.select_anchors(selections.to_vec());
12867 });
12868 } else {
12869 log::error!(
12870 "No entry in selection_history found for undo. \
12871 This may correspond to a bug where undo does not update the selection. \
12872 If this is occurring, please add details to \
12873 https://github.com/zed-industries/zed/issues/22692"
12874 );
12875 }
12876 self.request_autoscroll(Autoscroll::fit(), cx);
12877 self.unmark_text(window, cx);
12878 self.refresh_edit_prediction(true, false, window, cx);
12879 cx.emit(EditorEvent::Edited { transaction_id });
12880 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12881 }
12882 }
12883
12884 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12885 if self.read_only(cx) {
12886 return;
12887 }
12888
12889 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12890
12891 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12892 if let Some((_, Some(selections))) =
12893 self.selection_history.transaction(transaction_id).cloned()
12894 {
12895 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12896 s.select_anchors(selections.to_vec());
12897 });
12898 } else {
12899 log::error!(
12900 "No entry in selection_history found for redo. \
12901 This may correspond to a bug where undo does not update the selection. \
12902 If this is occurring, please add details to \
12903 https://github.com/zed-industries/zed/issues/22692"
12904 );
12905 }
12906 self.request_autoscroll(Autoscroll::fit(), cx);
12907 self.unmark_text(window, cx);
12908 self.refresh_edit_prediction(true, false, window, cx);
12909 cx.emit(EditorEvent::Edited { transaction_id });
12910 }
12911 }
12912
12913 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12914 self.buffer
12915 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12916 }
12917
12918 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12919 self.buffer
12920 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12921 }
12922
12923 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12924 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12925 self.change_selections(Default::default(), window, cx, |s| {
12926 s.move_with(|map, selection| {
12927 let cursor = if selection.is_empty() {
12928 movement::left(map, selection.start)
12929 } else {
12930 selection.start
12931 };
12932 selection.collapse_to(cursor, SelectionGoal::None);
12933 });
12934 })
12935 }
12936
12937 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12938 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12939 self.change_selections(Default::default(), window, cx, |s| {
12940 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12941 })
12942 }
12943
12944 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12946 self.change_selections(Default::default(), window, cx, |s| {
12947 s.move_with(|map, selection| {
12948 let cursor = if selection.is_empty() {
12949 movement::right(map, selection.end)
12950 } else {
12951 selection.end
12952 };
12953 selection.collapse_to(cursor, SelectionGoal::None)
12954 });
12955 })
12956 }
12957
12958 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12959 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12960 self.change_selections(Default::default(), window, cx, |s| {
12961 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12962 });
12963 }
12964
12965 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12966 if self.take_rename(true, window, cx).is_some() {
12967 return;
12968 }
12969
12970 if self.mode.is_single_line() {
12971 cx.propagate();
12972 return;
12973 }
12974
12975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12976
12977 let text_layout_details = &self.text_layout_details(window);
12978 let selection_count = self.selections.count();
12979 let first_selection = self.selections.first_anchor();
12980
12981 self.change_selections(Default::default(), window, cx, |s| {
12982 s.move_with(|map, selection| {
12983 if !selection.is_empty() {
12984 selection.goal = SelectionGoal::None;
12985 }
12986 let (cursor, goal) = movement::up(
12987 map,
12988 selection.start,
12989 selection.goal,
12990 false,
12991 text_layout_details,
12992 );
12993 selection.collapse_to(cursor, goal);
12994 });
12995 });
12996
12997 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12998 {
12999 cx.propagate();
13000 }
13001 }
13002
13003 pub fn move_up_by_lines(
13004 &mut self,
13005 action: &MoveUpByLines,
13006 window: &mut Window,
13007 cx: &mut Context<Self>,
13008 ) {
13009 if self.take_rename(true, window, cx).is_some() {
13010 return;
13011 }
13012
13013 if self.mode.is_single_line() {
13014 cx.propagate();
13015 return;
13016 }
13017
13018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13019
13020 let text_layout_details = &self.text_layout_details(window);
13021
13022 self.change_selections(Default::default(), window, cx, |s| {
13023 s.move_with(|map, selection| {
13024 if !selection.is_empty() {
13025 selection.goal = SelectionGoal::None;
13026 }
13027 let (cursor, goal) = movement::up_by_rows(
13028 map,
13029 selection.start,
13030 action.lines,
13031 selection.goal,
13032 false,
13033 text_layout_details,
13034 );
13035 selection.collapse_to(cursor, goal);
13036 });
13037 })
13038 }
13039
13040 pub fn move_down_by_lines(
13041 &mut self,
13042 action: &MoveDownByLines,
13043 window: &mut Window,
13044 cx: &mut Context<Self>,
13045 ) {
13046 if self.take_rename(true, window, cx).is_some() {
13047 return;
13048 }
13049
13050 if self.mode.is_single_line() {
13051 cx.propagate();
13052 return;
13053 }
13054
13055 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13056
13057 let text_layout_details = &self.text_layout_details(window);
13058
13059 self.change_selections(Default::default(), window, cx, |s| {
13060 s.move_with(|map, selection| {
13061 if !selection.is_empty() {
13062 selection.goal = SelectionGoal::None;
13063 }
13064 let (cursor, goal) = movement::down_by_rows(
13065 map,
13066 selection.start,
13067 action.lines,
13068 selection.goal,
13069 false,
13070 text_layout_details,
13071 );
13072 selection.collapse_to(cursor, goal);
13073 });
13074 })
13075 }
13076
13077 pub fn select_down_by_lines(
13078 &mut self,
13079 action: &SelectDownByLines,
13080 window: &mut Window,
13081 cx: &mut Context<Self>,
13082 ) {
13083 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13084 let text_layout_details = &self.text_layout_details(window);
13085 self.change_selections(Default::default(), window, cx, |s| {
13086 s.move_heads_with(|map, head, goal| {
13087 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13088 })
13089 })
13090 }
13091
13092 pub fn select_up_by_lines(
13093 &mut self,
13094 action: &SelectUpByLines,
13095 window: &mut Window,
13096 cx: &mut Context<Self>,
13097 ) {
13098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13099 let text_layout_details = &self.text_layout_details(window);
13100 self.change_selections(Default::default(), window, cx, |s| {
13101 s.move_heads_with(|map, head, goal| {
13102 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13103 })
13104 })
13105 }
13106
13107 pub fn select_page_up(
13108 &mut self,
13109 _: &SelectPageUp,
13110 window: &mut Window,
13111 cx: &mut Context<Self>,
13112 ) {
13113 let Some(row_count) = self.visible_row_count() else {
13114 return;
13115 };
13116
13117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13118
13119 let text_layout_details = &self.text_layout_details(window);
13120
13121 self.change_selections(Default::default(), window, cx, |s| {
13122 s.move_heads_with(|map, head, goal| {
13123 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13124 })
13125 })
13126 }
13127
13128 pub fn move_page_up(
13129 &mut self,
13130 action: &MovePageUp,
13131 window: &mut Window,
13132 cx: &mut Context<Self>,
13133 ) {
13134 if self.take_rename(true, window, cx).is_some() {
13135 return;
13136 }
13137
13138 if self
13139 .context_menu
13140 .borrow_mut()
13141 .as_mut()
13142 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13143 .unwrap_or(false)
13144 {
13145 return;
13146 }
13147
13148 if matches!(self.mode, EditorMode::SingleLine) {
13149 cx.propagate();
13150 return;
13151 }
13152
13153 let Some(row_count) = self.visible_row_count() else {
13154 return;
13155 };
13156
13157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13158
13159 let effects = if action.center_cursor {
13160 SelectionEffects::scroll(Autoscroll::center())
13161 } else {
13162 SelectionEffects::default()
13163 };
13164
13165 let text_layout_details = &self.text_layout_details(window);
13166
13167 self.change_selections(effects, window, cx, |s| {
13168 s.move_with(|map, selection| {
13169 if !selection.is_empty() {
13170 selection.goal = SelectionGoal::None;
13171 }
13172 let (cursor, goal) = movement::up_by_rows(
13173 map,
13174 selection.end,
13175 row_count,
13176 selection.goal,
13177 false,
13178 text_layout_details,
13179 );
13180 selection.collapse_to(cursor, goal);
13181 });
13182 });
13183 }
13184
13185 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13187 let text_layout_details = &self.text_layout_details(window);
13188 self.change_selections(Default::default(), window, cx, |s| {
13189 s.move_heads_with(|map, head, goal| {
13190 movement::up(map, head, goal, false, text_layout_details)
13191 })
13192 })
13193 }
13194
13195 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13196 self.take_rename(true, window, cx);
13197
13198 if self.mode.is_single_line() {
13199 cx.propagate();
13200 return;
13201 }
13202
13203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13204
13205 let text_layout_details = &self.text_layout_details(window);
13206 let selection_count = self.selections.count();
13207 let first_selection = self.selections.first_anchor();
13208
13209 self.change_selections(Default::default(), window, cx, |s| {
13210 s.move_with(|map, selection| {
13211 if !selection.is_empty() {
13212 selection.goal = SelectionGoal::None;
13213 }
13214 let (cursor, goal) = movement::down(
13215 map,
13216 selection.end,
13217 selection.goal,
13218 false,
13219 text_layout_details,
13220 );
13221 selection.collapse_to(cursor, goal);
13222 });
13223 });
13224
13225 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13226 {
13227 cx.propagate();
13228 }
13229 }
13230
13231 pub fn select_page_down(
13232 &mut self,
13233 _: &SelectPageDown,
13234 window: &mut Window,
13235 cx: &mut Context<Self>,
13236 ) {
13237 let Some(row_count) = self.visible_row_count() else {
13238 return;
13239 };
13240
13241 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13242
13243 let text_layout_details = &self.text_layout_details(window);
13244
13245 self.change_selections(Default::default(), window, cx, |s| {
13246 s.move_heads_with(|map, head, goal| {
13247 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13248 })
13249 })
13250 }
13251
13252 pub fn move_page_down(
13253 &mut self,
13254 action: &MovePageDown,
13255 window: &mut Window,
13256 cx: &mut Context<Self>,
13257 ) {
13258 if self.take_rename(true, window, cx).is_some() {
13259 return;
13260 }
13261
13262 if self
13263 .context_menu
13264 .borrow_mut()
13265 .as_mut()
13266 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13267 .unwrap_or(false)
13268 {
13269 return;
13270 }
13271
13272 if matches!(self.mode, EditorMode::SingleLine) {
13273 cx.propagate();
13274 return;
13275 }
13276
13277 let Some(row_count) = self.visible_row_count() else {
13278 return;
13279 };
13280
13281 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13282
13283 let effects = if action.center_cursor {
13284 SelectionEffects::scroll(Autoscroll::center())
13285 } else {
13286 SelectionEffects::default()
13287 };
13288
13289 let text_layout_details = &self.text_layout_details(window);
13290 self.change_selections(effects, window, cx, |s| {
13291 s.move_with(|map, selection| {
13292 if !selection.is_empty() {
13293 selection.goal = SelectionGoal::None;
13294 }
13295 let (cursor, goal) = movement::down_by_rows(
13296 map,
13297 selection.end,
13298 row_count,
13299 selection.goal,
13300 false,
13301 text_layout_details,
13302 );
13303 selection.collapse_to(cursor, goal);
13304 });
13305 });
13306 }
13307
13308 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13309 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13310 let text_layout_details = &self.text_layout_details(window);
13311 self.change_selections(Default::default(), window, cx, |s| {
13312 s.move_heads_with(|map, head, goal| {
13313 movement::down(map, head, goal, false, text_layout_details)
13314 })
13315 });
13316 }
13317
13318 pub fn context_menu_first(
13319 &mut self,
13320 _: &ContextMenuFirst,
13321 window: &mut Window,
13322 cx: &mut Context<Self>,
13323 ) {
13324 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13325 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13326 }
13327 }
13328
13329 pub fn context_menu_prev(
13330 &mut self,
13331 _: &ContextMenuPrevious,
13332 window: &mut Window,
13333 cx: &mut Context<Self>,
13334 ) {
13335 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13336 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13337 }
13338 }
13339
13340 pub fn context_menu_next(
13341 &mut self,
13342 _: &ContextMenuNext,
13343 window: &mut Window,
13344 cx: &mut Context<Self>,
13345 ) {
13346 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13347 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13348 }
13349 }
13350
13351 pub fn context_menu_last(
13352 &mut self,
13353 _: &ContextMenuLast,
13354 window: &mut Window,
13355 cx: &mut Context<Self>,
13356 ) {
13357 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13358 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13359 }
13360 }
13361
13362 pub fn signature_help_prev(
13363 &mut self,
13364 _: &SignatureHelpPrevious,
13365 _: &mut Window,
13366 cx: &mut Context<Self>,
13367 ) {
13368 if let Some(popover) = self.signature_help_state.popover_mut() {
13369 if popover.current_signature == 0 {
13370 popover.current_signature = popover.signatures.len() - 1;
13371 } else {
13372 popover.current_signature -= 1;
13373 }
13374 cx.notify();
13375 }
13376 }
13377
13378 pub fn signature_help_next(
13379 &mut self,
13380 _: &SignatureHelpNext,
13381 _: &mut Window,
13382 cx: &mut Context<Self>,
13383 ) {
13384 if let Some(popover) = self.signature_help_state.popover_mut() {
13385 if popover.current_signature + 1 == popover.signatures.len() {
13386 popover.current_signature = 0;
13387 } else {
13388 popover.current_signature += 1;
13389 }
13390 cx.notify();
13391 }
13392 }
13393
13394 pub fn move_to_previous_word_start(
13395 &mut self,
13396 _: &MoveToPreviousWordStart,
13397 window: &mut Window,
13398 cx: &mut Context<Self>,
13399 ) {
13400 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13401 self.change_selections(Default::default(), window, cx, |s| {
13402 s.move_cursors_with(|map, head, _| {
13403 (
13404 movement::previous_word_start(map, head),
13405 SelectionGoal::None,
13406 )
13407 });
13408 })
13409 }
13410
13411 pub fn move_to_previous_subword_start(
13412 &mut self,
13413 _: &MoveToPreviousSubwordStart,
13414 window: &mut Window,
13415 cx: &mut Context<Self>,
13416 ) {
13417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13418 self.change_selections(Default::default(), window, cx, |s| {
13419 s.move_cursors_with(|map, head, _| {
13420 (
13421 movement::previous_subword_start(map, head),
13422 SelectionGoal::None,
13423 )
13424 });
13425 })
13426 }
13427
13428 pub fn select_to_previous_word_start(
13429 &mut self,
13430 _: &SelectToPreviousWordStart,
13431 window: &mut Window,
13432 cx: &mut Context<Self>,
13433 ) {
13434 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13435 self.change_selections(Default::default(), window, cx, |s| {
13436 s.move_heads_with(|map, head, _| {
13437 (
13438 movement::previous_word_start(map, head),
13439 SelectionGoal::None,
13440 )
13441 });
13442 })
13443 }
13444
13445 pub fn select_to_previous_subword_start(
13446 &mut self,
13447 _: &SelectToPreviousSubwordStart,
13448 window: &mut Window,
13449 cx: &mut Context<Self>,
13450 ) {
13451 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13452 self.change_selections(Default::default(), window, cx, |s| {
13453 s.move_heads_with(|map, head, _| {
13454 (
13455 movement::previous_subword_start(map, head),
13456 SelectionGoal::None,
13457 )
13458 });
13459 })
13460 }
13461
13462 pub fn delete_to_previous_word_start(
13463 &mut self,
13464 action: &DeleteToPreviousWordStart,
13465 window: &mut Window,
13466 cx: &mut Context<Self>,
13467 ) {
13468 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13469 self.transact(window, cx, |this, window, cx| {
13470 this.select_autoclose_pair(window, cx);
13471 this.change_selections(Default::default(), window, cx, |s| {
13472 s.move_with(|map, selection| {
13473 if selection.is_empty() {
13474 let mut cursor = if action.ignore_newlines {
13475 movement::previous_word_start(map, selection.head())
13476 } else {
13477 movement::previous_word_start_or_newline(map, selection.head())
13478 };
13479 cursor = movement::adjust_greedy_deletion(
13480 map,
13481 selection.head(),
13482 cursor,
13483 action.ignore_brackets,
13484 );
13485 selection.set_head(cursor, SelectionGoal::None);
13486 }
13487 });
13488 });
13489 this.insert("", window, cx);
13490 });
13491 }
13492
13493 pub fn delete_to_previous_subword_start(
13494 &mut self,
13495 _: &DeleteToPreviousSubwordStart,
13496 window: &mut Window,
13497 cx: &mut Context<Self>,
13498 ) {
13499 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13500 self.transact(window, cx, |this, window, cx| {
13501 this.select_autoclose_pair(window, cx);
13502 this.change_selections(Default::default(), window, cx, |s| {
13503 s.move_with(|map, selection| {
13504 if selection.is_empty() {
13505 let mut cursor = movement::previous_subword_start(map, selection.head());
13506 cursor =
13507 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13508 selection.set_head(cursor, SelectionGoal::None);
13509 }
13510 });
13511 });
13512 this.insert("", window, cx);
13513 });
13514 }
13515
13516 pub fn move_to_next_word_end(
13517 &mut self,
13518 _: &MoveToNextWordEnd,
13519 window: &mut Window,
13520 cx: &mut Context<Self>,
13521 ) {
13522 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13523 self.change_selections(Default::default(), window, cx, |s| {
13524 s.move_cursors_with(|map, head, _| {
13525 (movement::next_word_end(map, head), SelectionGoal::None)
13526 });
13527 })
13528 }
13529
13530 pub fn move_to_next_subword_end(
13531 &mut self,
13532 _: &MoveToNextSubwordEnd,
13533 window: &mut Window,
13534 cx: &mut Context<Self>,
13535 ) {
13536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13537 self.change_selections(Default::default(), window, cx, |s| {
13538 s.move_cursors_with(|map, head, _| {
13539 (movement::next_subword_end(map, head), SelectionGoal::None)
13540 });
13541 })
13542 }
13543
13544 pub fn select_to_next_word_end(
13545 &mut self,
13546 _: &SelectToNextWordEnd,
13547 window: &mut Window,
13548 cx: &mut Context<Self>,
13549 ) {
13550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13551 self.change_selections(Default::default(), window, cx, |s| {
13552 s.move_heads_with(|map, head, _| {
13553 (movement::next_word_end(map, head), SelectionGoal::None)
13554 });
13555 })
13556 }
13557
13558 pub fn select_to_next_subword_end(
13559 &mut self,
13560 _: &SelectToNextSubwordEnd,
13561 window: &mut Window,
13562 cx: &mut Context<Self>,
13563 ) {
13564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13565 self.change_selections(Default::default(), window, cx, |s| {
13566 s.move_heads_with(|map, head, _| {
13567 (movement::next_subword_end(map, head), SelectionGoal::None)
13568 });
13569 })
13570 }
13571
13572 pub fn delete_to_next_word_end(
13573 &mut self,
13574 action: &DeleteToNextWordEnd,
13575 window: &mut Window,
13576 cx: &mut Context<Self>,
13577 ) {
13578 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13579 self.transact(window, cx, |this, window, cx| {
13580 this.change_selections(Default::default(), window, cx, |s| {
13581 s.move_with(|map, selection| {
13582 if selection.is_empty() {
13583 let mut cursor = if action.ignore_newlines {
13584 movement::next_word_end(map, selection.head())
13585 } else {
13586 movement::next_word_end_or_newline(map, selection.head())
13587 };
13588 cursor = movement::adjust_greedy_deletion(
13589 map,
13590 selection.head(),
13591 cursor,
13592 action.ignore_brackets,
13593 );
13594 selection.set_head(cursor, SelectionGoal::None);
13595 }
13596 });
13597 });
13598 this.insert("", window, cx);
13599 });
13600 }
13601
13602 pub fn delete_to_next_subword_end(
13603 &mut self,
13604 _: &DeleteToNextSubwordEnd,
13605 window: &mut Window,
13606 cx: &mut Context<Self>,
13607 ) {
13608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13609 self.transact(window, cx, |this, window, cx| {
13610 this.change_selections(Default::default(), window, cx, |s| {
13611 s.move_with(|map, selection| {
13612 if selection.is_empty() {
13613 let mut cursor = movement::next_subword_end(map, selection.head());
13614 cursor =
13615 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13616 selection.set_head(cursor, SelectionGoal::None);
13617 }
13618 });
13619 });
13620 this.insert("", window, cx);
13621 });
13622 }
13623
13624 pub fn move_to_beginning_of_line(
13625 &mut self,
13626 action: &MoveToBeginningOfLine,
13627 window: &mut Window,
13628 cx: &mut Context<Self>,
13629 ) {
13630 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13631 self.change_selections(Default::default(), window, cx, |s| {
13632 s.move_cursors_with(|map, head, _| {
13633 (
13634 movement::indented_line_beginning(
13635 map,
13636 head,
13637 action.stop_at_soft_wraps,
13638 action.stop_at_indent,
13639 ),
13640 SelectionGoal::None,
13641 )
13642 });
13643 })
13644 }
13645
13646 pub fn select_to_beginning_of_line(
13647 &mut self,
13648 action: &SelectToBeginningOfLine,
13649 window: &mut Window,
13650 cx: &mut Context<Self>,
13651 ) {
13652 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13653 self.change_selections(Default::default(), window, cx, |s| {
13654 s.move_heads_with(|map, head, _| {
13655 (
13656 movement::indented_line_beginning(
13657 map,
13658 head,
13659 action.stop_at_soft_wraps,
13660 action.stop_at_indent,
13661 ),
13662 SelectionGoal::None,
13663 )
13664 });
13665 });
13666 }
13667
13668 pub fn delete_to_beginning_of_line(
13669 &mut self,
13670 action: &DeleteToBeginningOfLine,
13671 window: &mut Window,
13672 cx: &mut Context<Self>,
13673 ) {
13674 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13675 self.transact(window, cx, |this, window, cx| {
13676 this.change_selections(Default::default(), window, cx, |s| {
13677 s.move_with(|_, selection| {
13678 selection.reversed = true;
13679 });
13680 });
13681
13682 this.select_to_beginning_of_line(
13683 &SelectToBeginningOfLine {
13684 stop_at_soft_wraps: false,
13685 stop_at_indent: action.stop_at_indent,
13686 },
13687 window,
13688 cx,
13689 );
13690 this.backspace(&Backspace, window, cx);
13691 });
13692 }
13693
13694 pub fn move_to_end_of_line(
13695 &mut self,
13696 action: &MoveToEndOfLine,
13697 window: &mut Window,
13698 cx: &mut Context<Self>,
13699 ) {
13700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13701 self.change_selections(Default::default(), window, cx, |s| {
13702 s.move_cursors_with(|map, head, _| {
13703 (
13704 movement::line_end(map, head, action.stop_at_soft_wraps),
13705 SelectionGoal::None,
13706 )
13707 });
13708 })
13709 }
13710
13711 pub fn select_to_end_of_line(
13712 &mut self,
13713 action: &SelectToEndOfLine,
13714 window: &mut Window,
13715 cx: &mut Context<Self>,
13716 ) {
13717 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13718 self.change_selections(Default::default(), window, cx, |s| {
13719 s.move_heads_with(|map, head, _| {
13720 (
13721 movement::line_end(map, head, action.stop_at_soft_wraps),
13722 SelectionGoal::None,
13723 )
13724 });
13725 })
13726 }
13727
13728 pub fn delete_to_end_of_line(
13729 &mut self,
13730 _: &DeleteToEndOfLine,
13731 window: &mut Window,
13732 cx: &mut Context<Self>,
13733 ) {
13734 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13735 self.transact(window, cx, |this, window, cx| {
13736 this.select_to_end_of_line(
13737 &SelectToEndOfLine {
13738 stop_at_soft_wraps: false,
13739 },
13740 window,
13741 cx,
13742 );
13743 this.delete(&Delete, window, cx);
13744 });
13745 }
13746
13747 pub fn cut_to_end_of_line(
13748 &mut self,
13749 action: &CutToEndOfLine,
13750 window: &mut Window,
13751 cx: &mut Context<Self>,
13752 ) {
13753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13754 self.transact(window, cx, |this, window, cx| {
13755 this.select_to_end_of_line(
13756 &SelectToEndOfLine {
13757 stop_at_soft_wraps: false,
13758 },
13759 window,
13760 cx,
13761 );
13762 if !action.stop_at_newlines {
13763 this.change_selections(Default::default(), window, cx, |s| {
13764 s.move_with(|_, sel| {
13765 if sel.is_empty() {
13766 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13767 }
13768 });
13769 });
13770 }
13771 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13772 let item = this.cut_common(false, window, cx);
13773 cx.write_to_clipboard(item);
13774 });
13775 }
13776
13777 pub fn move_to_start_of_paragraph(
13778 &mut self,
13779 _: &MoveToStartOfParagraph,
13780 window: &mut Window,
13781 cx: &mut Context<Self>,
13782 ) {
13783 if matches!(self.mode, EditorMode::SingleLine) {
13784 cx.propagate();
13785 return;
13786 }
13787 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13788 self.change_selections(Default::default(), window, cx, |s| {
13789 s.move_with(|map, selection| {
13790 selection.collapse_to(
13791 movement::start_of_paragraph(map, selection.head(), 1),
13792 SelectionGoal::None,
13793 )
13794 });
13795 })
13796 }
13797
13798 pub fn move_to_end_of_paragraph(
13799 &mut self,
13800 _: &MoveToEndOfParagraph,
13801 window: &mut Window,
13802 cx: &mut Context<Self>,
13803 ) {
13804 if matches!(self.mode, EditorMode::SingleLine) {
13805 cx.propagate();
13806 return;
13807 }
13808 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13809 self.change_selections(Default::default(), window, cx, |s| {
13810 s.move_with(|map, selection| {
13811 selection.collapse_to(
13812 movement::end_of_paragraph(map, selection.head(), 1),
13813 SelectionGoal::None,
13814 )
13815 });
13816 })
13817 }
13818
13819 pub fn select_to_start_of_paragraph(
13820 &mut self,
13821 _: &SelectToStartOfParagraph,
13822 window: &mut Window,
13823 cx: &mut Context<Self>,
13824 ) {
13825 if matches!(self.mode, EditorMode::SingleLine) {
13826 cx.propagate();
13827 return;
13828 }
13829 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13830 self.change_selections(Default::default(), window, cx, |s| {
13831 s.move_heads_with(|map, head, _| {
13832 (
13833 movement::start_of_paragraph(map, head, 1),
13834 SelectionGoal::None,
13835 )
13836 });
13837 })
13838 }
13839
13840 pub fn select_to_end_of_paragraph(
13841 &mut self,
13842 _: &SelectToEndOfParagraph,
13843 window: &mut Window,
13844 cx: &mut Context<Self>,
13845 ) {
13846 if matches!(self.mode, EditorMode::SingleLine) {
13847 cx.propagate();
13848 return;
13849 }
13850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13851 self.change_selections(Default::default(), window, cx, |s| {
13852 s.move_heads_with(|map, head, _| {
13853 (
13854 movement::end_of_paragraph(map, head, 1),
13855 SelectionGoal::None,
13856 )
13857 });
13858 })
13859 }
13860
13861 pub fn move_to_start_of_excerpt(
13862 &mut self,
13863 _: &MoveToStartOfExcerpt,
13864 window: &mut Window,
13865 cx: &mut Context<Self>,
13866 ) {
13867 if matches!(self.mode, EditorMode::SingleLine) {
13868 cx.propagate();
13869 return;
13870 }
13871 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13872 self.change_selections(Default::default(), window, cx, |s| {
13873 s.move_with(|map, selection| {
13874 selection.collapse_to(
13875 movement::start_of_excerpt(
13876 map,
13877 selection.head(),
13878 workspace::searchable::Direction::Prev,
13879 ),
13880 SelectionGoal::None,
13881 )
13882 });
13883 })
13884 }
13885
13886 pub fn move_to_start_of_next_excerpt(
13887 &mut self,
13888 _: &MoveToStartOfNextExcerpt,
13889 window: &mut Window,
13890 cx: &mut Context<Self>,
13891 ) {
13892 if matches!(self.mode, EditorMode::SingleLine) {
13893 cx.propagate();
13894 return;
13895 }
13896
13897 self.change_selections(Default::default(), window, cx, |s| {
13898 s.move_with(|map, selection| {
13899 selection.collapse_to(
13900 movement::start_of_excerpt(
13901 map,
13902 selection.head(),
13903 workspace::searchable::Direction::Next,
13904 ),
13905 SelectionGoal::None,
13906 )
13907 });
13908 })
13909 }
13910
13911 pub fn move_to_end_of_excerpt(
13912 &mut self,
13913 _: &MoveToEndOfExcerpt,
13914 window: &mut Window,
13915 cx: &mut Context<Self>,
13916 ) {
13917 if matches!(self.mode, EditorMode::SingleLine) {
13918 cx.propagate();
13919 return;
13920 }
13921 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13922 self.change_selections(Default::default(), window, cx, |s| {
13923 s.move_with(|map, selection| {
13924 selection.collapse_to(
13925 movement::end_of_excerpt(
13926 map,
13927 selection.head(),
13928 workspace::searchable::Direction::Next,
13929 ),
13930 SelectionGoal::None,
13931 )
13932 });
13933 })
13934 }
13935
13936 pub fn move_to_end_of_previous_excerpt(
13937 &mut self,
13938 _: &MoveToEndOfPreviousExcerpt,
13939 window: &mut Window,
13940 cx: &mut Context<Self>,
13941 ) {
13942 if matches!(self.mode, EditorMode::SingleLine) {
13943 cx.propagate();
13944 return;
13945 }
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13947 self.change_selections(Default::default(), window, cx, |s| {
13948 s.move_with(|map, selection| {
13949 selection.collapse_to(
13950 movement::end_of_excerpt(
13951 map,
13952 selection.head(),
13953 workspace::searchable::Direction::Prev,
13954 ),
13955 SelectionGoal::None,
13956 )
13957 });
13958 })
13959 }
13960
13961 pub fn select_to_start_of_excerpt(
13962 &mut self,
13963 _: &SelectToStartOfExcerpt,
13964 window: &mut Window,
13965 cx: &mut Context<Self>,
13966 ) {
13967 if matches!(self.mode, EditorMode::SingleLine) {
13968 cx.propagate();
13969 return;
13970 }
13971 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13972 self.change_selections(Default::default(), window, cx, |s| {
13973 s.move_heads_with(|map, head, _| {
13974 (
13975 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13976 SelectionGoal::None,
13977 )
13978 });
13979 })
13980 }
13981
13982 pub fn select_to_start_of_next_excerpt(
13983 &mut self,
13984 _: &SelectToStartOfNextExcerpt,
13985 window: &mut Window,
13986 cx: &mut Context<Self>,
13987 ) {
13988 if matches!(self.mode, EditorMode::SingleLine) {
13989 cx.propagate();
13990 return;
13991 }
13992 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13993 self.change_selections(Default::default(), window, cx, |s| {
13994 s.move_heads_with(|map, head, _| {
13995 (
13996 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13997 SelectionGoal::None,
13998 )
13999 });
14000 })
14001 }
14002
14003 pub fn select_to_end_of_excerpt(
14004 &mut self,
14005 _: &SelectToEndOfExcerpt,
14006 window: &mut Window,
14007 cx: &mut Context<Self>,
14008 ) {
14009 if matches!(self.mode, EditorMode::SingleLine) {
14010 cx.propagate();
14011 return;
14012 }
14013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14014 self.change_selections(Default::default(), window, cx, |s| {
14015 s.move_heads_with(|map, head, _| {
14016 (
14017 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14018 SelectionGoal::None,
14019 )
14020 });
14021 })
14022 }
14023
14024 pub fn select_to_end_of_previous_excerpt(
14025 &mut self,
14026 _: &SelectToEndOfPreviousExcerpt,
14027 window: &mut Window,
14028 cx: &mut Context<Self>,
14029 ) {
14030 if matches!(self.mode, EditorMode::SingleLine) {
14031 cx.propagate();
14032 return;
14033 }
14034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14035 self.change_selections(Default::default(), window, cx, |s| {
14036 s.move_heads_with(|map, head, _| {
14037 (
14038 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14039 SelectionGoal::None,
14040 )
14041 });
14042 })
14043 }
14044
14045 pub fn move_to_beginning(
14046 &mut self,
14047 _: &MoveToBeginning,
14048 window: &mut Window,
14049 cx: &mut Context<Self>,
14050 ) {
14051 if matches!(self.mode, EditorMode::SingleLine) {
14052 cx.propagate();
14053 return;
14054 }
14055 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14056 self.change_selections(Default::default(), window, cx, |s| {
14057 s.select_ranges(vec![0..0]);
14058 });
14059 }
14060
14061 pub fn select_to_beginning(
14062 &mut self,
14063 _: &SelectToBeginning,
14064 window: &mut Window,
14065 cx: &mut Context<Self>,
14066 ) {
14067 let mut selection = self.selections.last::<Point>(cx);
14068 selection.set_head(Point::zero(), SelectionGoal::None);
14069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14070 self.change_selections(Default::default(), window, cx, |s| {
14071 s.select(vec![selection]);
14072 });
14073 }
14074
14075 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14076 if matches!(self.mode, EditorMode::SingleLine) {
14077 cx.propagate();
14078 return;
14079 }
14080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14081 let cursor = self.buffer.read(cx).read(cx).len();
14082 self.change_selections(Default::default(), window, cx, |s| {
14083 s.select_ranges(vec![cursor..cursor])
14084 });
14085 }
14086
14087 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14088 self.nav_history = nav_history;
14089 }
14090
14091 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14092 self.nav_history.as_ref()
14093 }
14094
14095 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14096 self.push_to_nav_history(
14097 self.selections.newest_anchor().head(),
14098 None,
14099 false,
14100 true,
14101 cx,
14102 );
14103 }
14104
14105 fn push_to_nav_history(
14106 &mut self,
14107 cursor_anchor: Anchor,
14108 new_position: Option<Point>,
14109 is_deactivate: bool,
14110 always: bool,
14111 cx: &mut Context<Self>,
14112 ) {
14113 if let Some(nav_history) = self.nav_history.as_mut() {
14114 let buffer = self.buffer.read(cx).read(cx);
14115 let cursor_position = cursor_anchor.to_point(&buffer);
14116 let scroll_state = self.scroll_manager.anchor();
14117 let scroll_top_row = scroll_state.top_row(&buffer);
14118 drop(buffer);
14119
14120 if let Some(new_position) = new_position {
14121 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14122 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14123 return;
14124 }
14125 }
14126
14127 nav_history.push(
14128 Some(NavigationData {
14129 cursor_anchor,
14130 cursor_position,
14131 scroll_anchor: scroll_state,
14132 scroll_top_row,
14133 }),
14134 cx,
14135 );
14136 cx.emit(EditorEvent::PushedToNavHistory {
14137 anchor: cursor_anchor,
14138 is_deactivate,
14139 })
14140 }
14141 }
14142
14143 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14144 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14145 let buffer = self.buffer.read(cx).snapshot(cx);
14146 let mut selection = self.selections.first::<usize>(cx);
14147 selection.set_head(buffer.len(), SelectionGoal::None);
14148 self.change_selections(Default::default(), window, cx, |s| {
14149 s.select(vec![selection]);
14150 });
14151 }
14152
14153 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14154 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14155 let end = self.buffer.read(cx).read(cx).len();
14156 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14157 s.select_ranges(vec![0..end]);
14158 });
14159 }
14160
14161 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14162 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14163 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14164 let mut selections = self.selections.all::<Point>(cx);
14165 let max_point = display_map.buffer_snapshot().max_point();
14166 for selection in &mut selections {
14167 let rows = selection.spanned_rows(true, &display_map);
14168 selection.start = Point::new(rows.start.0, 0);
14169 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14170 selection.reversed = false;
14171 }
14172 self.change_selections(Default::default(), window, cx, |s| {
14173 s.select(selections);
14174 });
14175 }
14176
14177 pub fn split_selection_into_lines(
14178 &mut self,
14179 action: &SplitSelectionIntoLines,
14180 window: &mut Window,
14181 cx: &mut Context<Self>,
14182 ) {
14183 let selections = self
14184 .selections
14185 .all::<Point>(cx)
14186 .into_iter()
14187 .map(|selection| selection.start..selection.end)
14188 .collect::<Vec<_>>();
14189 self.unfold_ranges(&selections, true, true, cx);
14190
14191 let mut new_selection_ranges = Vec::new();
14192 {
14193 let buffer = self.buffer.read(cx).read(cx);
14194 for selection in selections {
14195 for row in selection.start.row..selection.end.row {
14196 let line_start = Point::new(row, 0);
14197 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14198
14199 if action.keep_selections {
14200 // Keep the selection range for each line
14201 let selection_start = if row == selection.start.row {
14202 selection.start
14203 } else {
14204 line_start
14205 };
14206 new_selection_ranges.push(selection_start..line_end);
14207 } else {
14208 // Collapse to cursor at end of line
14209 new_selection_ranges.push(line_end..line_end);
14210 }
14211 }
14212
14213 let is_multiline_selection = selection.start.row != selection.end.row;
14214 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14215 // so this action feels more ergonomic when paired with other selection operations
14216 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14217 if !should_skip_last {
14218 if action.keep_selections {
14219 if is_multiline_selection {
14220 let line_start = Point::new(selection.end.row, 0);
14221 new_selection_ranges.push(line_start..selection.end);
14222 } else {
14223 new_selection_ranges.push(selection.start..selection.end);
14224 }
14225 } else {
14226 new_selection_ranges.push(selection.end..selection.end);
14227 }
14228 }
14229 }
14230 }
14231 self.change_selections(Default::default(), window, cx, |s| {
14232 s.select_ranges(new_selection_ranges);
14233 });
14234 }
14235
14236 pub fn add_selection_above(
14237 &mut self,
14238 _: &AddSelectionAbove,
14239 window: &mut Window,
14240 cx: &mut Context<Self>,
14241 ) {
14242 self.add_selection(true, window, cx);
14243 }
14244
14245 pub fn add_selection_below(
14246 &mut self,
14247 _: &AddSelectionBelow,
14248 window: &mut Window,
14249 cx: &mut Context<Self>,
14250 ) {
14251 self.add_selection(false, window, cx);
14252 }
14253
14254 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14255 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14256
14257 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14258 let all_selections = self.selections.all::<Point>(cx);
14259 let text_layout_details = self.text_layout_details(window);
14260
14261 let (mut columnar_selections, new_selections_to_columnarize) = {
14262 if let Some(state) = self.add_selections_state.as_ref() {
14263 let columnar_selection_ids: HashSet<_> = state
14264 .groups
14265 .iter()
14266 .flat_map(|group| group.stack.iter())
14267 .copied()
14268 .collect();
14269
14270 all_selections
14271 .into_iter()
14272 .partition(|s| columnar_selection_ids.contains(&s.id))
14273 } else {
14274 (Vec::new(), all_selections)
14275 }
14276 };
14277
14278 let mut state = self
14279 .add_selections_state
14280 .take()
14281 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14282
14283 for selection in new_selections_to_columnarize {
14284 let range = selection.display_range(&display_map).sorted();
14285 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14286 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14287 let positions = start_x.min(end_x)..start_x.max(end_x);
14288 let mut stack = Vec::new();
14289 for row in range.start.row().0..=range.end.row().0 {
14290 if let Some(selection) = self.selections.build_columnar_selection(
14291 &display_map,
14292 DisplayRow(row),
14293 &positions,
14294 selection.reversed,
14295 &text_layout_details,
14296 ) {
14297 stack.push(selection.id);
14298 columnar_selections.push(selection);
14299 }
14300 }
14301 if !stack.is_empty() {
14302 if above {
14303 stack.reverse();
14304 }
14305 state.groups.push(AddSelectionsGroup { above, stack });
14306 }
14307 }
14308
14309 let mut final_selections = Vec::new();
14310 let end_row = if above {
14311 DisplayRow(0)
14312 } else {
14313 display_map.max_point().row()
14314 };
14315
14316 let mut last_added_item_per_group = HashMap::default();
14317 for group in state.groups.iter_mut() {
14318 if let Some(last_id) = group.stack.last() {
14319 last_added_item_per_group.insert(*last_id, group);
14320 }
14321 }
14322
14323 for selection in columnar_selections {
14324 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14325 if above == group.above {
14326 let range = selection.display_range(&display_map).sorted();
14327 debug_assert_eq!(range.start.row(), range.end.row());
14328 let mut row = range.start.row();
14329 let positions =
14330 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14331 Pixels::from(start)..Pixels::from(end)
14332 } else {
14333 let start_x =
14334 display_map.x_for_display_point(range.start, &text_layout_details);
14335 let end_x =
14336 display_map.x_for_display_point(range.end, &text_layout_details);
14337 start_x.min(end_x)..start_x.max(end_x)
14338 };
14339
14340 let mut maybe_new_selection = None;
14341 while row != end_row {
14342 if above {
14343 row.0 -= 1;
14344 } else {
14345 row.0 += 1;
14346 }
14347 if let Some(new_selection) = self.selections.build_columnar_selection(
14348 &display_map,
14349 row,
14350 &positions,
14351 selection.reversed,
14352 &text_layout_details,
14353 ) {
14354 maybe_new_selection = Some(new_selection);
14355 break;
14356 }
14357 }
14358
14359 if let Some(new_selection) = maybe_new_selection {
14360 group.stack.push(new_selection.id);
14361 if above {
14362 final_selections.push(new_selection);
14363 final_selections.push(selection);
14364 } else {
14365 final_selections.push(selection);
14366 final_selections.push(new_selection);
14367 }
14368 } else {
14369 final_selections.push(selection);
14370 }
14371 } else {
14372 group.stack.pop();
14373 }
14374 } else {
14375 final_selections.push(selection);
14376 }
14377 }
14378
14379 self.change_selections(Default::default(), window, cx, |s| {
14380 s.select(final_selections);
14381 });
14382
14383 let final_selection_ids: HashSet<_> = self
14384 .selections
14385 .all::<Point>(cx)
14386 .iter()
14387 .map(|s| s.id)
14388 .collect();
14389 state.groups.retain_mut(|group| {
14390 // selections might get merged above so we remove invalid items from stacks
14391 group.stack.retain(|id| final_selection_ids.contains(id));
14392
14393 // single selection in stack can be treated as initial state
14394 group.stack.len() > 1
14395 });
14396
14397 if !state.groups.is_empty() {
14398 self.add_selections_state = Some(state);
14399 }
14400 }
14401
14402 fn select_match_ranges(
14403 &mut self,
14404 range: Range<usize>,
14405 reversed: bool,
14406 replace_newest: bool,
14407 auto_scroll: Option<Autoscroll>,
14408 window: &mut Window,
14409 cx: &mut Context<Editor>,
14410 ) {
14411 self.unfold_ranges(
14412 std::slice::from_ref(&range),
14413 false,
14414 auto_scroll.is_some(),
14415 cx,
14416 );
14417 let effects = if let Some(scroll) = auto_scroll {
14418 SelectionEffects::scroll(scroll)
14419 } else {
14420 SelectionEffects::no_scroll()
14421 };
14422 self.change_selections(effects, window, cx, |s| {
14423 if replace_newest {
14424 s.delete(s.newest_anchor().id);
14425 }
14426 if reversed {
14427 s.insert_range(range.end..range.start);
14428 } else {
14429 s.insert_range(range);
14430 }
14431 });
14432 }
14433
14434 pub fn select_next_match_internal(
14435 &mut self,
14436 display_map: &DisplaySnapshot,
14437 replace_newest: bool,
14438 autoscroll: Option<Autoscroll>,
14439 window: &mut Window,
14440 cx: &mut Context<Self>,
14441 ) -> Result<()> {
14442 let buffer = display_map.buffer_snapshot();
14443 let mut selections = self.selections.all::<usize>(cx);
14444 if let Some(mut select_next_state) = self.select_next_state.take() {
14445 let query = &select_next_state.query;
14446 if !select_next_state.done {
14447 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14448 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14449 let mut next_selected_range = None;
14450
14451 // Collect and sort selection ranges for efficient overlap checking
14452 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14453 selection_ranges.sort_by_key(|r| r.start);
14454
14455 let bytes_after_last_selection =
14456 buffer.bytes_in_range(last_selection.end..buffer.len());
14457 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14458 let query_matches = query
14459 .stream_find_iter(bytes_after_last_selection)
14460 .map(|result| (last_selection.end, result))
14461 .chain(
14462 query
14463 .stream_find_iter(bytes_before_first_selection)
14464 .map(|result| (0, result)),
14465 );
14466
14467 for (start_offset, query_match) in query_matches {
14468 let query_match = query_match.unwrap(); // can only fail due to I/O
14469 let offset_range =
14470 start_offset + query_match.start()..start_offset + query_match.end();
14471
14472 if !select_next_state.wordwise
14473 || (!buffer.is_inside_word(offset_range.start, None)
14474 && !buffer.is_inside_word(offset_range.end, None))
14475 {
14476 // Use binary search to check for overlap (O(log n))
14477 let overlaps = selection_ranges
14478 .binary_search_by(|range| {
14479 if range.end <= offset_range.start {
14480 std::cmp::Ordering::Less
14481 } else if range.start >= offset_range.end {
14482 std::cmp::Ordering::Greater
14483 } else {
14484 std::cmp::Ordering::Equal
14485 }
14486 })
14487 .is_ok();
14488
14489 if !overlaps {
14490 next_selected_range = Some(offset_range);
14491 break;
14492 }
14493 }
14494 }
14495
14496 if let Some(next_selected_range) = next_selected_range {
14497 self.select_match_ranges(
14498 next_selected_range,
14499 last_selection.reversed,
14500 replace_newest,
14501 autoscroll,
14502 window,
14503 cx,
14504 );
14505 } else {
14506 select_next_state.done = true;
14507 }
14508 }
14509
14510 self.select_next_state = Some(select_next_state);
14511 } else {
14512 let mut only_carets = true;
14513 let mut same_text_selected = true;
14514 let mut selected_text = None;
14515
14516 let mut selections_iter = selections.iter().peekable();
14517 while let Some(selection) = selections_iter.next() {
14518 if selection.start != selection.end {
14519 only_carets = false;
14520 }
14521
14522 if same_text_selected {
14523 if selected_text.is_none() {
14524 selected_text =
14525 Some(buffer.text_for_range(selection.range()).collect::<String>());
14526 }
14527
14528 if let Some(next_selection) = selections_iter.peek() {
14529 if next_selection.range().len() == selection.range().len() {
14530 let next_selected_text = buffer
14531 .text_for_range(next_selection.range())
14532 .collect::<String>();
14533 if Some(next_selected_text) != selected_text {
14534 same_text_selected = false;
14535 selected_text = None;
14536 }
14537 } else {
14538 same_text_selected = false;
14539 selected_text = None;
14540 }
14541 }
14542 }
14543 }
14544
14545 if only_carets {
14546 for selection in &mut selections {
14547 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14548 selection.start = word_range.start;
14549 selection.end = word_range.end;
14550 selection.goal = SelectionGoal::None;
14551 selection.reversed = false;
14552 self.select_match_ranges(
14553 selection.start..selection.end,
14554 selection.reversed,
14555 replace_newest,
14556 autoscroll,
14557 window,
14558 cx,
14559 );
14560 }
14561
14562 if selections.len() == 1 {
14563 let selection = selections
14564 .last()
14565 .expect("ensured that there's only one selection");
14566 let query = buffer
14567 .text_for_range(selection.start..selection.end)
14568 .collect::<String>();
14569 let is_empty = query.is_empty();
14570 let select_state = SelectNextState {
14571 query: AhoCorasick::new(&[query])?,
14572 wordwise: true,
14573 done: is_empty,
14574 };
14575 self.select_next_state = Some(select_state);
14576 } else {
14577 self.select_next_state = None;
14578 }
14579 } else if let Some(selected_text) = selected_text {
14580 self.select_next_state = Some(SelectNextState {
14581 query: AhoCorasick::new(&[selected_text])?,
14582 wordwise: false,
14583 done: false,
14584 });
14585 self.select_next_match_internal(
14586 display_map,
14587 replace_newest,
14588 autoscroll,
14589 window,
14590 cx,
14591 )?;
14592 }
14593 }
14594 Ok(())
14595 }
14596
14597 pub fn select_all_matches(
14598 &mut self,
14599 _action: &SelectAllMatches,
14600 window: &mut Window,
14601 cx: &mut Context<Self>,
14602 ) -> Result<()> {
14603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14604
14605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14606
14607 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14608 let Some(select_next_state) = self.select_next_state.as_mut() else {
14609 return Ok(());
14610 };
14611 if select_next_state.done {
14612 return Ok(());
14613 }
14614
14615 let mut new_selections = Vec::new();
14616
14617 let reversed = self.selections.oldest::<usize>(cx).reversed;
14618 let buffer = display_map.buffer_snapshot();
14619 let query_matches = select_next_state
14620 .query
14621 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14622
14623 for query_match in query_matches.into_iter() {
14624 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14625 let offset_range = if reversed {
14626 query_match.end()..query_match.start()
14627 } else {
14628 query_match.start()..query_match.end()
14629 };
14630
14631 if !select_next_state.wordwise
14632 || (!buffer.is_inside_word(offset_range.start, None)
14633 && !buffer.is_inside_word(offset_range.end, None))
14634 {
14635 new_selections.push(offset_range.start..offset_range.end);
14636 }
14637 }
14638
14639 select_next_state.done = true;
14640
14641 if new_selections.is_empty() {
14642 log::error!("bug: new_selections is empty in select_all_matches");
14643 return Ok(());
14644 }
14645
14646 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14647 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14648 selections.select_ranges(new_selections)
14649 });
14650
14651 Ok(())
14652 }
14653
14654 pub fn select_next(
14655 &mut self,
14656 action: &SelectNext,
14657 window: &mut Window,
14658 cx: &mut Context<Self>,
14659 ) -> Result<()> {
14660 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14661 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14662 self.select_next_match_internal(
14663 &display_map,
14664 action.replace_newest,
14665 Some(Autoscroll::newest()),
14666 window,
14667 cx,
14668 )?;
14669 Ok(())
14670 }
14671
14672 pub fn select_previous(
14673 &mut self,
14674 action: &SelectPrevious,
14675 window: &mut Window,
14676 cx: &mut Context<Self>,
14677 ) -> Result<()> {
14678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14679 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14680 let buffer = display_map.buffer_snapshot();
14681 let mut selections = self.selections.all::<usize>(cx);
14682 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14683 let query = &select_prev_state.query;
14684 if !select_prev_state.done {
14685 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14686 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14687 let mut next_selected_range = None;
14688 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14689 let bytes_before_last_selection =
14690 buffer.reversed_bytes_in_range(0..last_selection.start);
14691 let bytes_after_first_selection =
14692 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14693 let query_matches = query
14694 .stream_find_iter(bytes_before_last_selection)
14695 .map(|result| (last_selection.start, result))
14696 .chain(
14697 query
14698 .stream_find_iter(bytes_after_first_selection)
14699 .map(|result| (buffer.len(), result)),
14700 );
14701 for (end_offset, query_match) in query_matches {
14702 let query_match = query_match.unwrap(); // can only fail due to I/O
14703 let offset_range =
14704 end_offset - query_match.end()..end_offset - query_match.start();
14705
14706 if !select_prev_state.wordwise
14707 || (!buffer.is_inside_word(offset_range.start, None)
14708 && !buffer.is_inside_word(offset_range.end, None))
14709 {
14710 next_selected_range = Some(offset_range);
14711 break;
14712 }
14713 }
14714
14715 if let Some(next_selected_range) = next_selected_range {
14716 self.select_match_ranges(
14717 next_selected_range,
14718 last_selection.reversed,
14719 action.replace_newest,
14720 Some(Autoscroll::newest()),
14721 window,
14722 cx,
14723 );
14724 } else {
14725 select_prev_state.done = true;
14726 }
14727 }
14728
14729 self.select_prev_state = Some(select_prev_state);
14730 } else {
14731 let mut only_carets = true;
14732 let mut same_text_selected = true;
14733 let mut selected_text = None;
14734
14735 let mut selections_iter = selections.iter().peekable();
14736 while let Some(selection) = selections_iter.next() {
14737 if selection.start != selection.end {
14738 only_carets = false;
14739 }
14740
14741 if same_text_selected {
14742 if selected_text.is_none() {
14743 selected_text =
14744 Some(buffer.text_for_range(selection.range()).collect::<String>());
14745 }
14746
14747 if let Some(next_selection) = selections_iter.peek() {
14748 if next_selection.range().len() == selection.range().len() {
14749 let next_selected_text = buffer
14750 .text_for_range(next_selection.range())
14751 .collect::<String>();
14752 if Some(next_selected_text) != selected_text {
14753 same_text_selected = false;
14754 selected_text = None;
14755 }
14756 } else {
14757 same_text_selected = false;
14758 selected_text = None;
14759 }
14760 }
14761 }
14762 }
14763
14764 if only_carets {
14765 for selection in &mut selections {
14766 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14767 selection.start = word_range.start;
14768 selection.end = word_range.end;
14769 selection.goal = SelectionGoal::None;
14770 selection.reversed = false;
14771 self.select_match_ranges(
14772 selection.start..selection.end,
14773 selection.reversed,
14774 action.replace_newest,
14775 Some(Autoscroll::newest()),
14776 window,
14777 cx,
14778 );
14779 }
14780 if selections.len() == 1 {
14781 let selection = selections
14782 .last()
14783 .expect("ensured that there's only one selection");
14784 let query = buffer
14785 .text_for_range(selection.start..selection.end)
14786 .collect::<String>();
14787 let is_empty = query.is_empty();
14788 let select_state = SelectNextState {
14789 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14790 wordwise: true,
14791 done: is_empty,
14792 };
14793 self.select_prev_state = Some(select_state);
14794 } else {
14795 self.select_prev_state = None;
14796 }
14797 } else if let Some(selected_text) = selected_text {
14798 self.select_prev_state = Some(SelectNextState {
14799 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14800 wordwise: false,
14801 done: false,
14802 });
14803 self.select_previous(action, window, cx)?;
14804 }
14805 }
14806 Ok(())
14807 }
14808
14809 pub fn find_next_match(
14810 &mut self,
14811 _: &FindNextMatch,
14812 window: &mut Window,
14813 cx: &mut Context<Self>,
14814 ) -> Result<()> {
14815 let selections = self.selections.disjoint_anchors_arc();
14816 match selections.first() {
14817 Some(first) if selections.len() >= 2 => {
14818 self.change_selections(Default::default(), window, cx, |s| {
14819 s.select_ranges([first.range()]);
14820 });
14821 }
14822 _ => self.select_next(
14823 &SelectNext {
14824 replace_newest: true,
14825 },
14826 window,
14827 cx,
14828 )?,
14829 }
14830 Ok(())
14831 }
14832
14833 pub fn find_previous_match(
14834 &mut self,
14835 _: &FindPreviousMatch,
14836 window: &mut Window,
14837 cx: &mut Context<Self>,
14838 ) -> Result<()> {
14839 let selections = self.selections.disjoint_anchors_arc();
14840 match selections.last() {
14841 Some(last) if selections.len() >= 2 => {
14842 self.change_selections(Default::default(), window, cx, |s| {
14843 s.select_ranges([last.range()]);
14844 });
14845 }
14846 _ => self.select_previous(
14847 &SelectPrevious {
14848 replace_newest: true,
14849 },
14850 window,
14851 cx,
14852 )?,
14853 }
14854 Ok(())
14855 }
14856
14857 pub fn toggle_comments(
14858 &mut self,
14859 action: &ToggleComments,
14860 window: &mut Window,
14861 cx: &mut Context<Self>,
14862 ) {
14863 if self.read_only(cx) {
14864 return;
14865 }
14866 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14867 let text_layout_details = &self.text_layout_details(window);
14868 self.transact(window, cx, |this, window, cx| {
14869 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14870 let mut edits = Vec::new();
14871 let mut selection_edit_ranges = Vec::new();
14872 let mut last_toggled_row = None;
14873 let snapshot = this.buffer.read(cx).read(cx);
14874 let empty_str: Arc<str> = Arc::default();
14875 let mut suffixes_inserted = Vec::new();
14876 let ignore_indent = action.ignore_indent;
14877
14878 fn comment_prefix_range(
14879 snapshot: &MultiBufferSnapshot,
14880 row: MultiBufferRow,
14881 comment_prefix: &str,
14882 comment_prefix_whitespace: &str,
14883 ignore_indent: bool,
14884 ) -> Range<Point> {
14885 let indent_size = if ignore_indent {
14886 0
14887 } else {
14888 snapshot.indent_size_for_line(row).len
14889 };
14890
14891 let start = Point::new(row.0, indent_size);
14892
14893 let mut line_bytes = snapshot
14894 .bytes_in_range(start..snapshot.max_point())
14895 .flatten()
14896 .copied();
14897
14898 // If this line currently begins with the line comment prefix, then record
14899 // the range containing the prefix.
14900 if line_bytes
14901 .by_ref()
14902 .take(comment_prefix.len())
14903 .eq(comment_prefix.bytes())
14904 {
14905 // Include any whitespace that matches the comment prefix.
14906 let matching_whitespace_len = line_bytes
14907 .zip(comment_prefix_whitespace.bytes())
14908 .take_while(|(a, b)| a == b)
14909 .count() as u32;
14910 let end = Point::new(
14911 start.row,
14912 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14913 );
14914 start..end
14915 } else {
14916 start..start
14917 }
14918 }
14919
14920 fn comment_suffix_range(
14921 snapshot: &MultiBufferSnapshot,
14922 row: MultiBufferRow,
14923 comment_suffix: &str,
14924 comment_suffix_has_leading_space: bool,
14925 ) -> Range<Point> {
14926 let end = Point::new(row.0, snapshot.line_len(row));
14927 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14928
14929 let mut line_end_bytes = snapshot
14930 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14931 .flatten()
14932 .copied();
14933
14934 let leading_space_len = if suffix_start_column > 0
14935 && line_end_bytes.next() == Some(b' ')
14936 && comment_suffix_has_leading_space
14937 {
14938 1
14939 } else {
14940 0
14941 };
14942
14943 // If this line currently begins with the line comment prefix, then record
14944 // the range containing the prefix.
14945 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14946 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14947 start..end
14948 } else {
14949 end..end
14950 }
14951 }
14952
14953 // TODO: Handle selections that cross excerpts
14954 for selection in &mut selections {
14955 let start_column = snapshot
14956 .indent_size_for_line(MultiBufferRow(selection.start.row))
14957 .len;
14958 let language = if let Some(language) =
14959 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14960 {
14961 language
14962 } else {
14963 continue;
14964 };
14965
14966 selection_edit_ranges.clear();
14967
14968 // If multiple selections contain a given row, avoid processing that
14969 // row more than once.
14970 let mut start_row = MultiBufferRow(selection.start.row);
14971 if last_toggled_row == Some(start_row) {
14972 start_row = start_row.next_row();
14973 }
14974 let end_row =
14975 if selection.end.row > selection.start.row && selection.end.column == 0 {
14976 MultiBufferRow(selection.end.row - 1)
14977 } else {
14978 MultiBufferRow(selection.end.row)
14979 };
14980 last_toggled_row = Some(end_row);
14981
14982 if start_row > end_row {
14983 continue;
14984 }
14985
14986 // If the language has line comments, toggle those.
14987 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14988
14989 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14990 if ignore_indent {
14991 full_comment_prefixes = full_comment_prefixes
14992 .into_iter()
14993 .map(|s| Arc::from(s.trim_end()))
14994 .collect();
14995 }
14996
14997 if !full_comment_prefixes.is_empty() {
14998 let first_prefix = full_comment_prefixes
14999 .first()
15000 .expect("prefixes is non-empty");
15001 let prefix_trimmed_lengths = full_comment_prefixes
15002 .iter()
15003 .map(|p| p.trim_end_matches(' ').len())
15004 .collect::<SmallVec<[usize; 4]>>();
15005
15006 let mut all_selection_lines_are_comments = true;
15007
15008 for row in start_row.0..=end_row.0 {
15009 let row = MultiBufferRow(row);
15010 if start_row < end_row && snapshot.is_line_blank(row) {
15011 continue;
15012 }
15013
15014 let prefix_range = full_comment_prefixes
15015 .iter()
15016 .zip(prefix_trimmed_lengths.iter().copied())
15017 .map(|(prefix, trimmed_prefix_len)| {
15018 comment_prefix_range(
15019 snapshot.deref(),
15020 row,
15021 &prefix[..trimmed_prefix_len],
15022 &prefix[trimmed_prefix_len..],
15023 ignore_indent,
15024 )
15025 })
15026 .max_by_key(|range| range.end.column - range.start.column)
15027 .expect("prefixes is non-empty");
15028
15029 if prefix_range.is_empty() {
15030 all_selection_lines_are_comments = false;
15031 }
15032
15033 selection_edit_ranges.push(prefix_range);
15034 }
15035
15036 if all_selection_lines_are_comments {
15037 edits.extend(
15038 selection_edit_ranges
15039 .iter()
15040 .cloned()
15041 .map(|range| (range, empty_str.clone())),
15042 );
15043 } else {
15044 let min_column = selection_edit_ranges
15045 .iter()
15046 .map(|range| range.start.column)
15047 .min()
15048 .unwrap_or(0);
15049 edits.extend(selection_edit_ranges.iter().map(|range| {
15050 let position = Point::new(range.start.row, min_column);
15051 (position..position, first_prefix.clone())
15052 }));
15053 }
15054 } else if let Some(BlockCommentConfig {
15055 start: full_comment_prefix,
15056 end: comment_suffix,
15057 ..
15058 }) = language.block_comment()
15059 {
15060 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15061 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15062 let prefix_range = comment_prefix_range(
15063 snapshot.deref(),
15064 start_row,
15065 comment_prefix,
15066 comment_prefix_whitespace,
15067 ignore_indent,
15068 );
15069 let suffix_range = comment_suffix_range(
15070 snapshot.deref(),
15071 end_row,
15072 comment_suffix.trim_start_matches(' '),
15073 comment_suffix.starts_with(' '),
15074 );
15075
15076 if prefix_range.is_empty() || suffix_range.is_empty() {
15077 edits.push((
15078 prefix_range.start..prefix_range.start,
15079 full_comment_prefix.clone(),
15080 ));
15081 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15082 suffixes_inserted.push((end_row, comment_suffix.len()));
15083 } else {
15084 edits.push((prefix_range, empty_str.clone()));
15085 edits.push((suffix_range, empty_str.clone()));
15086 }
15087 } else {
15088 continue;
15089 }
15090 }
15091
15092 drop(snapshot);
15093 this.buffer.update(cx, |buffer, cx| {
15094 buffer.edit(edits, None, cx);
15095 });
15096
15097 // Adjust selections so that they end before any comment suffixes that
15098 // were inserted.
15099 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15100 let mut selections = this.selections.all::<Point>(cx);
15101 let snapshot = this.buffer.read(cx).read(cx);
15102 for selection in &mut selections {
15103 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15104 match row.cmp(&MultiBufferRow(selection.end.row)) {
15105 Ordering::Less => {
15106 suffixes_inserted.next();
15107 continue;
15108 }
15109 Ordering::Greater => break,
15110 Ordering::Equal => {
15111 if selection.end.column == snapshot.line_len(row) {
15112 if selection.is_empty() {
15113 selection.start.column -= suffix_len as u32;
15114 }
15115 selection.end.column -= suffix_len as u32;
15116 }
15117 break;
15118 }
15119 }
15120 }
15121 }
15122
15123 drop(snapshot);
15124 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15125
15126 let selections = this.selections.all::<Point>(cx);
15127 let selections_on_single_row = selections.windows(2).all(|selections| {
15128 selections[0].start.row == selections[1].start.row
15129 && selections[0].end.row == selections[1].end.row
15130 && selections[0].start.row == selections[0].end.row
15131 });
15132 let selections_selecting = selections
15133 .iter()
15134 .any(|selection| selection.start != selection.end);
15135 let advance_downwards = action.advance_downwards
15136 && selections_on_single_row
15137 && !selections_selecting
15138 && !matches!(this.mode, EditorMode::SingleLine);
15139
15140 if advance_downwards {
15141 let snapshot = this.buffer.read(cx).snapshot(cx);
15142
15143 this.change_selections(Default::default(), window, cx, |s| {
15144 s.move_cursors_with(|display_snapshot, display_point, _| {
15145 let mut point = display_point.to_point(display_snapshot);
15146 point.row += 1;
15147 point = snapshot.clip_point(point, Bias::Left);
15148 let display_point = point.to_display_point(display_snapshot);
15149 let goal = SelectionGoal::HorizontalPosition(
15150 display_snapshot
15151 .x_for_display_point(display_point, text_layout_details)
15152 .into(),
15153 );
15154 (display_point, goal)
15155 })
15156 });
15157 }
15158 });
15159 }
15160
15161 pub fn select_enclosing_symbol(
15162 &mut self,
15163 _: &SelectEnclosingSymbol,
15164 window: &mut Window,
15165 cx: &mut Context<Self>,
15166 ) {
15167 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15168
15169 let buffer = self.buffer.read(cx).snapshot(cx);
15170 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15171
15172 fn update_selection(
15173 selection: &Selection<usize>,
15174 buffer_snap: &MultiBufferSnapshot,
15175 ) -> Option<Selection<usize>> {
15176 let cursor = selection.head();
15177 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15178 for symbol in symbols.iter().rev() {
15179 let start = symbol.range.start.to_offset(buffer_snap);
15180 let end = symbol.range.end.to_offset(buffer_snap);
15181 let new_range = start..end;
15182 if start < selection.start || end > selection.end {
15183 return Some(Selection {
15184 id: selection.id,
15185 start: new_range.start,
15186 end: new_range.end,
15187 goal: SelectionGoal::None,
15188 reversed: selection.reversed,
15189 });
15190 }
15191 }
15192 None
15193 }
15194
15195 let mut selected_larger_symbol = false;
15196 let new_selections = old_selections
15197 .iter()
15198 .map(|selection| match update_selection(selection, &buffer) {
15199 Some(new_selection) => {
15200 if new_selection.range() != selection.range() {
15201 selected_larger_symbol = true;
15202 }
15203 new_selection
15204 }
15205 None => selection.clone(),
15206 })
15207 .collect::<Vec<_>>();
15208
15209 if selected_larger_symbol {
15210 self.change_selections(Default::default(), window, cx, |s| {
15211 s.select(new_selections);
15212 });
15213 }
15214 }
15215
15216 pub fn select_larger_syntax_node(
15217 &mut self,
15218 _: &SelectLargerSyntaxNode,
15219 window: &mut Window,
15220 cx: &mut Context<Self>,
15221 ) {
15222 let Some(visible_row_count) = self.visible_row_count() else {
15223 return;
15224 };
15225 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15226 if old_selections.is_empty() {
15227 return;
15228 }
15229
15230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15231
15232 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15233 let buffer = self.buffer.read(cx).snapshot(cx);
15234
15235 let mut selected_larger_node = false;
15236 let mut new_selections = old_selections
15237 .iter()
15238 .map(|selection| {
15239 let old_range = selection.start..selection.end;
15240
15241 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15242 // manually select word at selection
15243 if ["string_content", "inline"].contains(&node.kind()) {
15244 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15245 // ignore if word is already selected
15246 if !word_range.is_empty() && old_range != word_range {
15247 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15248 // only select word if start and end point belongs to same word
15249 if word_range == last_word_range {
15250 selected_larger_node = true;
15251 return Selection {
15252 id: selection.id,
15253 start: word_range.start,
15254 end: word_range.end,
15255 goal: SelectionGoal::None,
15256 reversed: selection.reversed,
15257 };
15258 }
15259 }
15260 }
15261 }
15262
15263 let mut new_range = old_range.clone();
15264 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15265 new_range = range;
15266 if !node.is_named() {
15267 continue;
15268 }
15269 if !display_map.intersects_fold(new_range.start)
15270 && !display_map.intersects_fold(new_range.end)
15271 {
15272 break;
15273 }
15274 }
15275
15276 selected_larger_node |= new_range != old_range;
15277 Selection {
15278 id: selection.id,
15279 start: new_range.start,
15280 end: new_range.end,
15281 goal: SelectionGoal::None,
15282 reversed: selection.reversed,
15283 }
15284 })
15285 .collect::<Vec<_>>();
15286
15287 if !selected_larger_node {
15288 return; // don't put this call in the history
15289 }
15290
15291 // scroll based on transformation done to the last selection created by the user
15292 let (last_old, last_new) = old_selections
15293 .last()
15294 .zip(new_selections.last().cloned())
15295 .expect("old_selections isn't empty");
15296
15297 // revert selection
15298 let is_selection_reversed = {
15299 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15300 new_selections.last_mut().expect("checked above").reversed =
15301 should_newest_selection_be_reversed;
15302 should_newest_selection_be_reversed
15303 };
15304
15305 if selected_larger_node {
15306 self.select_syntax_node_history.disable_clearing = true;
15307 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15308 s.select(new_selections.clone());
15309 });
15310 self.select_syntax_node_history.disable_clearing = false;
15311 }
15312
15313 let start_row = last_new.start.to_display_point(&display_map).row().0;
15314 let end_row = last_new.end.to_display_point(&display_map).row().0;
15315 let selection_height = end_row - start_row + 1;
15316 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15317
15318 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15319 let scroll_behavior = if fits_on_the_screen {
15320 self.request_autoscroll(Autoscroll::fit(), cx);
15321 SelectSyntaxNodeScrollBehavior::FitSelection
15322 } else if is_selection_reversed {
15323 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15324 SelectSyntaxNodeScrollBehavior::CursorTop
15325 } else {
15326 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15327 SelectSyntaxNodeScrollBehavior::CursorBottom
15328 };
15329
15330 self.select_syntax_node_history.push((
15331 old_selections,
15332 scroll_behavior,
15333 is_selection_reversed,
15334 ));
15335 }
15336
15337 pub fn select_smaller_syntax_node(
15338 &mut self,
15339 _: &SelectSmallerSyntaxNode,
15340 window: &mut Window,
15341 cx: &mut Context<Self>,
15342 ) {
15343 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15344
15345 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15346 self.select_syntax_node_history.pop()
15347 {
15348 if let Some(selection) = selections.last_mut() {
15349 selection.reversed = is_selection_reversed;
15350 }
15351
15352 self.select_syntax_node_history.disable_clearing = true;
15353 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15354 s.select(selections.to_vec());
15355 });
15356 self.select_syntax_node_history.disable_clearing = false;
15357
15358 match scroll_behavior {
15359 SelectSyntaxNodeScrollBehavior::CursorTop => {
15360 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15361 }
15362 SelectSyntaxNodeScrollBehavior::FitSelection => {
15363 self.request_autoscroll(Autoscroll::fit(), cx);
15364 }
15365 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15366 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15367 }
15368 }
15369 }
15370 }
15371
15372 pub fn unwrap_syntax_node(
15373 &mut self,
15374 _: &UnwrapSyntaxNode,
15375 window: &mut Window,
15376 cx: &mut Context<Self>,
15377 ) {
15378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15379
15380 let buffer = self.buffer.read(cx).snapshot(cx);
15381 let selections = self
15382 .selections
15383 .all::<usize>(cx)
15384 .into_iter()
15385 // subtracting the offset requires sorting
15386 .sorted_by_key(|i| i.start);
15387
15388 let full_edits = selections
15389 .into_iter()
15390 .filter_map(|selection| {
15391 let child = if selection.is_empty()
15392 && let Some((_, ancestor_range)) =
15393 buffer.syntax_ancestor(selection.start..selection.end)
15394 {
15395 ancestor_range
15396 } else {
15397 selection.range()
15398 };
15399
15400 let mut parent = child.clone();
15401 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15402 parent = ancestor_range;
15403 if parent.start < child.start || parent.end > child.end {
15404 break;
15405 }
15406 }
15407
15408 if parent == child {
15409 return None;
15410 }
15411 let text = buffer.text_for_range(child).collect::<String>();
15412 Some((selection.id, parent, text))
15413 })
15414 .collect::<Vec<_>>();
15415 if full_edits.is_empty() {
15416 return;
15417 }
15418
15419 self.transact(window, cx, |this, window, cx| {
15420 this.buffer.update(cx, |buffer, cx| {
15421 buffer.edit(
15422 full_edits
15423 .iter()
15424 .map(|(_, p, t)| (p.clone(), t.clone()))
15425 .collect::<Vec<_>>(),
15426 None,
15427 cx,
15428 );
15429 });
15430 this.change_selections(Default::default(), window, cx, |s| {
15431 let mut offset = 0;
15432 let mut selections = vec![];
15433 for (id, parent, text) in full_edits {
15434 let start = parent.start - offset;
15435 offset += parent.len() - text.len();
15436 selections.push(Selection {
15437 id,
15438 start,
15439 end: start + text.len(),
15440 reversed: false,
15441 goal: Default::default(),
15442 });
15443 }
15444 s.select(selections);
15445 });
15446 });
15447 }
15448
15449 pub fn select_next_syntax_node(
15450 &mut self,
15451 _: &SelectNextSyntaxNode,
15452 window: &mut Window,
15453 cx: &mut Context<Self>,
15454 ) {
15455 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15456 if old_selections.is_empty() {
15457 return;
15458 }
15459
15460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15461
15462 let buffer = self.buffer.read(cx).snapshot(cx);
15463 let mut selected_sibling = false;
15464
15465 let new_selections = old_selections
15466 .iter()
15467 .map(|selection| {
15468 let old_range = selection.start..selection.end;
15469
15470 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15471 let new_range = node.byte_range();
15472 selected_sibling = true;
15473 Selection {
15474 id: selection.id,
15475 start: new_range.start,
15476 end: new_range.end,
15477 goal: SelectionGoal::None,
15478 reversed: selection.reversed,
15479 }
15480 } else {
15481 selection.clone()
15482 }
15483 })
15484 .collect::<Vec<_>>();
15485
15486 if selected_sibling {
15487 self.change_selections(
15488 SelectionEffects::scroll(Autoscroll::fit()),
15489 window,
15490 cx,
15491 |s| {
15492 s.select(new_selections);
15493 },
15494 );
15495 }
15496 }
15497
15498 pub fn select_prev_syntax_node(
15499 &mut self,
15500 _: &SelectPreviousSyntaxNode,
15501 window: &mut Window,
15502 cx: &mut Context<Self>,
15503 ) {
15504 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15505 if old_selections.is_empty() {
15506 return;
15507 }
15508
15509 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15510
15511 let buffer = self.buffer.read(cx).snapshot(cx);
15512 let mut selected_sibling = false;
15513
15514 let new_selections = old_selections
15515 .iter()
15516 .map(|selection| {
15517 let old_range = selection.start..selection.end;
15518
15519 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15520 let new_range = node.byte_range();
15521 selected_sibling = true;
15522 Selection {
15523 id: selection.id,
15524 start: new_range.start,
15525 end: new_range.end,
15526 goal: SelectionGoal::None,
15527 reversed: selection.reversed,
15528 }
15529 } else {
15530 selection.clone()
15531 }
15532 })
15533 .collect::<Vec<_>>();
15534
15535 if selected_sibling {
15536 self.change_selections(
15537 SelectionEffects::scroll(Autoscroll::fit()),
15538 window,
15539 cx,
15540 |s| {
15541 s.select(new_selections);
15542 },
15543 );
15544 }
15545 }
15546
15547 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15548 if !EditorSettings::get_global(cx).gutter.runnables {
15549 self.clear_tasks();
15550 return Task::ready(());
15551 }
15552 let project = self.project().map(Entity::downgrade);
15553 let task_sources = self.lsp_task_sources(cx);
15554 let multi_buffer = self.buffer.downgrade();
15555 cx.spawn_in(window, async move |editor, cx| {
15556 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15557 let Some(project) = project.and_then(|p| p.upgrade()) else {
15558 return;
15559 };
15560 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15561 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15562 }) else {
15563 return;
15564 };
15565
15566 let hide_runnables = project
15567 .update(cx, |project, _| project.is_via_collab())
15568 .unwrap_or(true);
15569 if hide_runnables {
15570 return;
15571 }
15572 let new_rows =
15573 cx.background_spawn({
15574 let snapshot = display_snapshot.clone();
15575 async move {
15576 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15577 }
15578 })
15579 .await;
15580 let Ok(lsp_tasks) =
15581 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15582 else {
15583 return;
15584 };
15585 let lsp_tasks = lsp_tasks.await;
15586
15587 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15588 lsp_tasks
15589 .into_iter()
15590 .flat_map(|(kind, tasks)| {
15591 tasks.into_iter().filter_map(move |(location, task)| {
15592 Some((kind.clone(), location?, task))
15593 })
15594 })
15595 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15596 let buffer = location.target.buffer;
15597 let buffer_snapshot = buffer.read(cx).snapshot();
15598 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15599 |(excerpt_id, snapshot, _)| {
15600 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15601 display_snapshot
15602 .buffer_snapshot()
15603 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15604 } else {
15605 None
15606 }
15607 },
15608 );
15609 if let Some(offset) = offset {
15610 let task_buffer_range =
15611 location.target.range.to_point(&buffer_snapshot);
15612 let context_buffer_range =
15613 task_buffer_range.to_offset(&buffer_snapshot);
15614 let context_range = BufferOffset(context_buffer_range.start)
15615 ..BufferOffset(context_buffer_range.end);
15616
15617 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15618 .or_insert_with(|| RunnableTasks {
15619 templates: Vec::new(),
15620 offset,
15621 column: task_buffer_range.start.column,
15622 extra_variables: HashMap::default(),
15623 context_range,
15624 })
15625 .templates
15626 .push((kind, task.original_task().clone()));
15627 }
15628
15629 acc
15630 })
15631 }) else {
15632 return;
15633 };
15634
15635 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15636 buffer.language_settings(cx).tasks.prefer_lsp
15637 }) else {
15638 return;
15639 };
15640
15641 let rows = Self::runnable_rows(
15642 project,
15643 display_snapshot,
15644 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15645 new_rows,
15646 cx.clone(),
15647 )
15648 .await;
15649 editor
15650 .update(cx, |editor, _| {
15651 editor.clear_tasks();
15652 for (key, mut value) in rows {
15653 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15654 value.templates.extend(lsp_tasks.templates);
15655 }
15656
15657 editor.insert_tasks(key, value);
15658 }
15659 for (key, value) in lsp_tasks_by_rows {
15660 editor.insert_tasks(key, value);
15661 }
15662 })
15663 .ok();
15664 })
15665 }
15666 fn fetch_runnable_ranges(
15667 snapshot: &DisplaySnapshot,
15668 range: Range<Anchor>,
15669 ) -> Vec<language::RunnableRange> {
15670 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15671 }
15672
15673 fn runnable_rows(
15674 project: Entity<Project>,
15675 snapshot: DisplaySnapshot,
15676 prefer_lsp: bool,
15677 runnable_ranges: Vec<RunnableRange>,
15678 cx: AsyncWindowContext,
15679 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15680 cx.spawn(async move |cx| {
15681 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15682 for mut runnable in runnable_ranges {
15683 let Some(tasks) = cx
15684 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15685 .ok()
15686 else {
15687 continue;
15688 };
15689 let mut tasks = tasks.await;
15690
15691 if prefer_lsp {
15692 tasks.retain(|(task_kind, _)| {
15693 !matches!(task_kind, TaskSourceKind::Language { .. })
15694 });
15695 }
15696 if tasks.is_empty() {
15697 continue;
15698 }
15699
15700 let point = runnable
15701 .run_range
15702 .start
15703 .to_point(&snapshot.buffer_snapshot());
15704 let Some(row) = snapshot
15705 .buffer_snapshot()
15706 .buffer_line_for_row(MultiBufferRow(point.row))
15707 .map(|(_, range)| range.start.row)
15708 else {
15709 continue;
15710 };
15711
15712 let context_range =
15713 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15714 runnable_rows.push((
15715 (runnable.buffer_id, row),
15716 RunnableTasks {
15717 templates: tasks,
15718 offset: snapshot
15719 .buffer_snapshot()
15720 .anchor_before(runnable.run_range.start),
15721 context_range,
15722 column: point.column,
15723 extra_variables: runnable.extra_captures,
15724 },
15725 ));
15726 }
15727 runnable_rows
15728 })
15729 }
15730
15731 fn templates_with_tags(
15732 project: &Entity<Project>,
15733 runnable: &mut Runnable,
15734 cx: &mut App,
15735 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15736 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15737 let (worktree_id, file) = project
15738 .buffer_for_id(runnable.buffer, cx)
15739 .and_then(|buffer| buffer.read(cx).file())
15740 .map(|file| (file.worktree_id(cx), file.clone()))
15741 .unzip();
15742
15743 (
15744 project.task_store().read(cx).task_inventory().cloned(),
15745 worktree_id,
15746 file,
15747 )
15748 });
15749
15750 let tags = mem::take(&mut runnable.tags);
15751 let language = runnable.language.clone();
15752 cx.spawn(async move |cx| {
15753 let mut templates_with_tags = Vec::new();
15754 if let Some(inventory) = inventory {
15755 for RunnableTag(tag) in tags {
15756 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15757 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15758 }) else {
15759 return templates_with_tags;
15760 };
15761 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15762 move |(_, template)| {
15763 template.tags.iter().any(|source_tag| source_tag == &tag)
15764 },
15765 ));
15766 }
15767 }
15768 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15769
15770 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15771 // Strongest source wins; if we have worktree tag binding, prefer that to
15772 // global and language bindings;
15773 // if we have a global binding, prefer that to language binding.
15774 let first_mismatch = templates_with_tags
15775 .iter()
15776 .position(|(tag_source, _)| tag_source != leading_tag_source);
15777 if let Some(index) = first_mismatch {
15778 templates_with_tags.truncate(index);
15779 }
15780 }
15781
15782 templates_with_tags
15783 })
15784 }
15785
15786 pub fn move_to_enclosing_bracket(
15787 &mut self,
15788 _: &MoveToEnclosingBracket,
15789 window: &mut Window,
15790 cx: &mut Context<Self>,
15791 ) {
15792 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15793 self.change_selections(Default::default(), window, cx, |s| {
15794 s.move_offsets_with(|snapshot, selection| {
15795 let Some(enclosing_bracket_ranges) =
15796 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15797 else {
15798 return;
15799 };
15800
15801 let mut best_length = usize::MAX;
15802 let mut best_inside = false;
15803 let mut best_in_bracket_range = false;
15804 let mut best_destination = None;
15805 for (open, close) in enclosing_bracket_ranges {
15806 let close = close.to_inclusive();
15807 let length = close.end() - open.start;
15808 let inside = selection.start >= open.end && selection.end <= *close.start();
15809 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15810 || close.contains(&selection.head());
15811
15812 // If best is next to a bracket and current isn't, skip
15813 if !in_bracket_range && best_in_bracket_range {
15814 continue;
15815 }
15816
15817 // Prefer smaller lengths unless best is inside and current isn't
15818 if length > best_length && (best_inside || !inside) {
15819 continue;
15820 }
15821
15822 best_length = length;
15823 best_inside = inside;
15824 best_in_bracket_range = in_bracket_range;
15825 best_destination = Some(
15826 if close.contains(&selection.start) && close.contains(&selection.end) {
15827 if inside { open.end } else { open.start }
15828 } else if inside {
15829 *close.start()
15830 } else {
15831 *close.end()
15832 },
15833 );
15834 }
15835
15836 if let Some(destination) = best_destination {
15837 selection.collapse_to(destination, SelectionGoal::None);
15838 }
15839 })
15840 });
15841 }
15842
15843 pub fn undo_selection(
15844 &mut self,
15845 _: &UndoSelection,
15846 window: &mut Window,
15847 cx: &mut Context<Self>,
15848 ) {
15849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15850 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15851 self.selection_history.mode = SelectionHistoryMode::Undoing;
15852 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15853 this.end_selection(window, cx);
15854 this.change_selections(
15855 SelectionEffects::scroll(Autoscroll::newest()),
15856 window,
15857 cx,
15858 |s| s.select_anchors(entry.selections.to_vec()),
15859 );
15860 });
15861 self.selection_history.mode = SelectionHistoryMode::Normal;
15862
15863 self.select_next_state = entry.select_next_state;
15864 self.select_prev_state = entry.select_prev_state;
15865 self.add_selections_state = entry.add_selections_state;
15866 }
15867 }
15868
15869 pub fn redo_selection(
15870 &mut self,
15871 _: &RedoSelection,
15872 window: &mut Window,
15873 cx: &mut Context<Self>,
15874 ) {
15875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15876 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15877 self.selection_history.mode = SelectionHistoryMode::Redoing;
15878 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15879 this.end_selection(window, cx);
15880 this.change_selections(
15881 SelectionEffects::scroll(Autoscroll::newest()),
15882 window,
15883 cx,
15884 |s| s.select_anchors(entry.selections.to_vec()),
15885 );
15886 });
15887 self.selection_history.mode = SelectionHistoryMode::Normal;
15888
15889 self.select_next_state = entry.select_next_state;
15890 self.select_prev_state = entry.select_prev_state;
15891 self.add_selections_state = entry.add_selections_state;
15892 }
15893 }
15894
15895 pub fn expand_excerpts(
15896 &mut self,
15897 action: &ExpandExcerpts,
15898 _: &mut Window,
15899 cx: &mut Context<Self>,
15900 ) {
15901 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15902 }
15903
15904 pub fn expand_excerpts_down(
15905 &mut self,
15906 action: &ExpandExcerptsDown,
15907 _: &mut Window,
15908 cx: &mut Context<Self>,
15909 ) {
15910 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15911 }
15912
15913 pub fn expand_excerpts_up(
15914 &mut self,
15915 action: &ExpandExcerptsUp,
15916 _: &mut Window,
15917 cx: &mut Context<Self>,
15918 ) {
15919 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15920 }
15921
15922 pub fn expand_excerpts_for_direction(
15923 &mut self,
15924 lines: u32,
15925 direction: ExpandExcerptDirection,
15926
15927 cx: &mut Context<Self>,
15928 ) {
15929 let selections = self.selections.disjoint_anchors_arc();
15930
15931 let lines = if lines == 0 {
15932 EditorSettings::get_global(cx).expand_excerpt_lines
15933 } else {
15934 lines
15935 };
15936
15937 self.buffer.update(cx, |buffer, cx| {
15938 let snapshot = buffer.snapshot(cx);
15939 let mut excerpt_ids = selections
15940 .iter()
15941 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15942 .collect::<Vec<_>>();
15943 excerpt_ids.sort();
15944 excerpt_ids.dedup();
15945 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15946 })
15947 }
15948
15949 pub fn expand_excerpt(
15950 &mut self,
15951 excerpt: ExcerptId,
15952 direction: ExpandExcerptDirection,
15953 window: &mut Window,
15954 cx: &mut Context<Self>,
15955 ) {
15956 let current_scroll_position = self.scroll_position(cx);
15957 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15958 let mut should_scroll_up = false;
15959
15960 if direction == ExpandExcerptDirection::Down {
15961 let multi_buffer = self.buffer.read(cx);
15962 let snapshot = multi_buffer.snapshot(cx);
15963 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15964 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15965 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15966 {
15967 let buffer_snapshot = buffer.read(cx).snapshot();
15968 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15969 let last_row = buffer_snapshot.max_point().row;
15970 let lines_below = last_row.saturating_sub(excerpt_end_row);
15971 should_scroll_up = lines_below >= lines_to_expand;
15972 }
15973 }
15974
15975 self.buffer.update(cx, |buffer, cx| {
15976 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15977 });
15978
15979 if should_scroll_up {
15980 let new_scroll_position =
15981 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15982 self.set_scroll_position(new_scroll_position, window, cx);
15983 }
15984 }
15985
15986 pub fn go_to_singleton_buffer_point(
15987 &mut self,
15988 point: Point,
15989 window: &mut Window,
15990 cx: &mut Context<Self>,
15991 ) {
15992 self.go_to_singleton_buffer_range(point..point, window, cx);
15993 }
15994
15995 pub fn go_to_singleton_buffer_range(
15996 &mut self,
15997 range: Range<Point>,
15998 window: &mut Window,
15999 cx: &mut Context<Self>,
16000 ) {
16001 let multibuffer = self.buffer().read(cx);
16002 let Some(buffer) = multibuffer.as_singleton() else {
16003 return;
16004 };
16005 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16006 return;
16007 };
16008 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16009 return;
16010 };
16011 self.change_selections(
16012 SelectionEffects::default().nav_history(true),
16013 window,
16014 cx,
16015 |s| s.select_anchor_ranges([start..end]),
16016 );
16017 }
16018
16019 pub fn go_to_diagnostic(
16020 &mut self,
16021 action: &GoToDiagnostic,
16022 window: &mut Window,
16023 cx: &mut Context<Self>,
16024 ) {
16025 if !self.diagnostics_enabled() {
16026 return;
16027 }
16028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16029 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16030 }
16031
16032 pub fn go_to_prev_diagnostic(
16033 &mut self,
16034 action: &GoToPreviousDiagnostic,
16035 window: &mut Window,
16036 cx: &mut Context<Self>,
16037 ) {
16038 if !self.diagnostics_enabled() {
16039 return;
16040 }
16041 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16042 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16043 }
16044
16045 pub fn go_to_diagnostic_impl(
16046 &mut self,
16047 direction: Direction,
16048 severity: GoToDiagnosticSeverityFilter,
16049 window: &mut Window,
16050 cx: &mut Context<Self>,
16051 ) {
16052 let buffer = self.buffer.read(cx).snapshot(cx);
16053 let selection = self.selections.newest::<usize>(cx);
16054
16055 let mut active_group_id = None;
16056 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16057 && active_group.active_range.start.to_offset(&buffer) == selection.start
16058 {
16059 active_group_id = Some(active_group.group_id);
16060 }
16061
16062 fn filtered<'a>(
16063 snapshot: EditorSnapshot,
16064 severity: GoToDiagnosticSeverityFilter,
16065 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16066 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16067 diagnostics
16068 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16069 .filter(|entry| entry.range.start != entry.range.end)
16070 .filter(|entry| !entry.diagnostic.is_unnecessary)
16071 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16072 }
16073
16074 let snapshot = self.snapshot(window, cx);
16075 let before = filtered(
16076 snapshot.clone(),
16077 severity,
16078 buffer
16079 .diagnostics_in_range(0..selection.start)
16080 .filter(|entry| entry.range.start <= selection.start),
16081 );
16082 let after = filtered(
16083 snapshot,
16084 severity,
16085 buffer
16086 .diagnostics_in_range(selection.start..buffer.len())
16087 .filter(|entry| entry.range.start >= selection.start),
16088 );
16089
16090 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16091 if direction == Direction::Prev {
16092 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16093 {
16094 for diagnostic in prev_diagnostics.into_iter().rev() {
16095 if diagnostic.range.start != selection.start
16096 || active_group_id
16097 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16098 {
16099 found = Some(diagnostic);
16100 break 'outer;
16101 }
16102 }
16103 }
16104 } else {
16105 for diagnostic in after.chain(before) {
16106 if diagnostic.range.start != selection.start
16107 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16108 {
16109 found = Some(diagnostic);
16110 break;
16111 }
16112 }
16113 }
16114 let Some(next_diagnostic) = found else {
16115 return;
16116 };
16117
16118 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16119 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16120 return;
16121 };
16122 self.change_selections(Default::default(), window, cx, |s| {
16123 s.select_ranges(vec![
16124 next_diagnostic.range.start..next_diagnostic.range.start,
16125 ])
16126 });
16127 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16128 self.refresh_edit_prediction(false, true, window, cx);
16129 }
16130
16131 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16133 let snapshot = self.snapshot(window, cx);
16134 let selection = self.selections.newest::<Point>(cx);
16135 self.go_to_hunk_before_or_after_position(
16136 &snapshot,
16137 selection.head(),
16138 Direction::Next,
16139 window,
16140 cx,
16141 );
16142 }
16143
16144 pub fn go_to_hunk_before_or_after_position(
16145 &mut self,
16146 snapshot: &EditorSnapshot,
16147 position: Point,
16148 direction: Direction,
16149 window: &mut Window,
16150 cx: &mut Context<Editor>,
16151 ) {
16152 let row = if direction == Direction::Next {
16153 self.hunk_after_position(snapshot, position)
16154 .map(|hunk| hunk.row_range.start)
16155 } else {
16156 self.hunk_before_position(snapshot, position)
16157 };
16158
16159 if let Some(row) = row {
16160 let destination = Point::new(row.0, 0);
16161 let autoscroll = Autoscroll::center();
16162
16163 self.unfold_ranges(&[destination..destination], false, false, cx);
16164 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16165 s.select_ranges([destination..destination]);
16166 });
16167 }
16168 }
16169
16170 fn hunk_after_position(
16171 &mut self,
16172 snapshot: &EditorSnapshot,
16173 position: Point,
16174 ) -> Option<MultiBufferDiffHunk> {
16175 snapshot
16176 .buffer_snapshot()
16177 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16178 .find(|hunk| hunk.row_range.start.0 > position.row)
16179 .or_else(|| {
16180 snapshot
16181 .buffer_snapshot()
16182 .diff_hunks_in_range(Point::zero()..position)
16183 .find(|hunk| hunk.row_range.end.0 < position.row)
16184 })
16185 }
16186
16187 fn go_to_prev_hunk(
16188 &mut self,
16189 _: &GoToPreviousHunk,
16190 window: &mut Window,
16191 cx: &mut Context<Self>,
16192 ) {
16193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16194 let snapshot = self.snapshot(window, cx);
16195 let selection = self.selections.newest::<Point>(cx);
16196 self.go_to_hunk_before_or_after_position(
16197 &snapshot,
16198 selection.head(),
16199 Direction::Prev,
16200 window,
16201 cx,
16202 );
16203 }
16204
16205 fn hunk_before_position(
16206 &mut self,
16207 snapshot: &EditorSnapshot,
16208 position: Point,
16209 ) -> Option<MultiBufferRow> {
16210 snapshot
16211 .buffer_snapshot()
16212 .diff_hunk_before(position)
16213 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16214 }
16215
16216 fn go_to_next_change(
16217 &mut self,
16218 _: &GoToNextChange,
16219 window: &mut Window,
16220 cx: &mut Context<Self>,
16221 ) {
16222 if let Some(selections) = self
16223 .change_list
16224 .next_change(1, Direction::Next)
16225 .map(|s| s.to_vec())
16226 {
16227 self.change_selections(Default::default(), window, cx, |s| {
16228 let map = s.display_map();
16229 s.select_display_ranges(selections.iter().map(|a| {
16230 let point = a.to_display_point(&map);
16231 point..point
16232 }))
16233 })
16234 }
16235 }
16236
16237 fn go_to_previous_change(
16238 &mut self,
16239 _: &GoToPreviousChange,
16240 window: &mut Window,
16241 cx: &mut Context<Self>,
16242 ) {
16243 if let Some(selections) = self
16244 .change_list
16245 .next_change(1, Direction::Prev)
16246 .map(|s| s.to_vec())
16247 {
16248 self.change_selections(Default::default(), window, cx, |s| {
16249 let map = s.display_map();
16250 s.select_display_ranges(selections.iter().map(|a| {
16251 let point = a.to_display_point(&map);
16252 point..point
16253 }))
16254 })
16255 }
16256 }
16257
16258 pub fn go_to_next_document_highlight(
16259 &mut self,
16260 _: &GoToNextDocumentHighlight,
16261 window: &mut Window,
16262 cx: &mut Context<Self>,
16263 ) {
16264 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16265 }
16266
16267 pub fn go_to_prev_document_highlight(
16268 &mut self,
16269 _: &GoToPreviousDocumentHighlight,
16270 window: &mut Window,
16271 cx: &mut Context<Self>,
16272 ) {
16273 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16274 }
16275
16276 pub fn go_to_document_highlight_before_or_after_position(
16277 &mut self,
16278 direction: Direction,
16279 window: &mut Window,
16280 cx: &mut Context<Editor>,
16281 ) {
16282 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16283 let snapshot = self.snapshot(window, cx);
16284 let buffer = &snapshot.buffer_snapshot();
16285 let position = self.selections.newest::<Point>(cx).head();
16286 let anchor_position = buffer.anchor_after(position);
16287
16288 // Get all document highlights (both read and write)
16289 let mut all_highlights = Vec::new();
16290
16291 if let Some((_, read_highlights)) = self
16292 .background_highlights
16293 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16294 {
16295 all_highlights.extend(read_highlights.iter());
16296 }
16297
16298 if let Some((_, write_highlights)) = self
16299 .background_highlights
16300 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16301 {
16302 all_highlights.extend(write_highlights.iter());
16303 }
16304
16305 if all_highlights.is_empty() {
16306 return;
16307 }
16308
16309 // Sort highlights by position
16310 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16311
16312 let target_highlight = match direction {
16313 Direction::Next => {
16314 // Find the first highlight after the current position
16315 all_highlights
16316 .iter()
16317 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16318 }
16319 Direction::Prev => {
16320 // Find the last highlight before the current position
16321 all_highlights
16322 .iter()
16323 .rev()
16324 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16325 }
16326 };
16327
16328 if let Some(highlight) = target_highlight {
16329 let destination = highlight.start.to_point(buffer);
16330 let autoscroll = Autoscroll::center();
16331
16332 self.unfold_ranges(&[destination..destination], false, false, cx);
16333 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16334 s.select_ranges([destination..destination]);
16335 });
16336 }
16337 }
16338
16339 fn go_to_line<T: 'static>(
16340 &mut self,
16341 position: Anchor,
16342 highlight_color: Option<Hsla>,
16343 window: &mut Window,
16344 cx: &mut Context<Self>,
16345 ) {
16346 let snapshot = self.snapshot(window, cx).display_snapshot;
16347 let position = position.to_point(&snapshot.buffer_snapshot());
16348 let start = snapshot
16349 .buffer_snapshot()
16350 .clip_point(Point::new(position.row, 0), Bias::Left);
16351 let end = start + Point::new(1, 0);
16352 let start = snapshot.buffer_snapshot().anchor_before(start);
16353 let end = snapshot.buffer_snapshot().anchor_before(end);
16354
16355 self.highlight_rows::<T>(
16356 start..end,
16357 highlight_color
16358 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16359 Default::default(),
16360 cx,
16361 );
16362
16363 if self.buffer.read(cx).is_singleton() {
16364 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16365 }
16366 }
16367
16368 pub fn go_to_definition(
16369 &mut self,
16370 _: &GoToDefinition,
16371 window: &mut Window,
16372 cx: &mut Context<Self>,
16373 ) -> Task<Result<Navigated>> {
16374 let definition =
16375 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16376 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16377 cx.spawn_in(window, async move |editor, cx| {
16378 if definition.await? == Navigated::Yes {
16379 return Ok(Navigated::Yes);
16380 }
16381 match fallback_strategy {
16382 GoToDefinitionFallback::None => Ok(Navigated::No),
16383 GoToDefinitionFallback::FindAllReferences => {
16384 match editor.update_in(cx, |editor, window, cx| {
16385 editor.find_all_references(&FindAllReferences, window, cx)
16386 })? {
16387 Some(references) => references.await,
16388 None => Ok(Navigated::No),
16389 }
16390 }
16391 }
16392 })
16393 }
16394
16395 pub fn go_to_declaration(
16396 &mut self,
16397 _: &GoToDeclaration,
16398 window: &mut Window,
16399 cx: &mut Context<Self>,
16400 ) -> Task<Result<Navigated>> {
16401 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16402 }
16403
16404 pub fn go_to_declaration_split(
16405 &mut self,
16406 _: &GoToDeclaration,
16407 window: &mut Window,
16408 cx: &mut Context<Self>,
16409 ) -> Task<Result<Navigated>> {
16410 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16411 }
16412
16413 pub fn go_to_implementation(
16414 &mut self,
16415 _: &GoToImplementation,
16416 window: &mut Window,
16417 cx: &mut Context<Self>,
16418 ) -> Task<Result<Navigated>> {
16419 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16420 }
16421
16422 pub fn go_to_implementation_split(
16423 &mut self,
16424 _: &GoToImplementationSplit,
16425 window: &mut Window,
16426 cx: &mut Context<Self>,
16427 ) -> Task<Result<Navigated>> {
16428 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16429 }
16430
16431 pub fn go_to_type_definition(
16432 &mut self,
16433 _: &GoToTypeDefinition,
16434 window: &mut Window,
16435 cx: &mut Context<Self>,
16436 ) -> Task<Result<Navigated>> {
16437 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16438 }
16439
16440 pub fn go_to_definition_split(
16441 &mut self,
16442 _: &GoToDefinitionSplit,
16443 window: &mut Window,
16444 cx: &mut Context<Self>,
16445 ) -> Task<Result<Navigated>> {
16446 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16447 }
16448
16449 pub fn go_to_type_definition_split(
16450 &mut self,
16451 _: &GoToTypeDefinitionSplit,
16452 window: &mut Window,
16453 cx: &mut Context<Self>,
16454 ) -> Task<Result<Navigated>> {
16455 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16456 }
16457
16458 fn go_to_definition_of_kind(
16459 &mut self,
16460 kind: GotoDefinitionKind,
16461 split: bool,
16462 window: &mut Window,
16463 cx: &mut Context<Self>,
16464 ) -> Task<Result<Navigated>> {
16465 let Some(provider) = self.semantics_provider.clone() else {
16466 return Task::ready(Ok(Navigated::No));
16467 };
16468 let head = self.selections.newest::<usize>(cx).head();
16469 let buffer = self.buffer.read(cx);
16470 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16471 return Task::ready(Ok(Navigated::No));
16472 };
16473 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16474 return Task::ready(Ok(Navigated::No));
16475 };
16476
16477 cx.spawn_in(window, async move |editor, cx| {
16478 let Some(definitions) = definitions.await? else {
16479 return Ok(Navigated::No);
16480 };
16481 let navigated = editor
16482 .update_in(cx, |editor, window, cx| {
16483 editor.navigate_to_hover_links(
16484 Some(kind),
16485 definitions
16486 .into_iter()
16487 .filter(|location| {
16488 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16489 })
16490 .map(HoverLink::Text)
16491 .collect::<Vec<_>>(),
16492 split,
16493 window,
16494 cx,
16495 )
16496 })?
16497 .await?;
16498 anyhow::Ok(navigated)
16499 })
16500 }
16501
16502 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16503 let selection = self.selections.newest_anchor();
16504 let head = selection.head();
16505 let tail = selection.tail();
16506
16507 let Some((buffer, start_position)) =
16508 self.buffer.read(cx).text_anchor_for_position(head, cx)
16509 else {
16510 return;
16511 };
16512
16513 let end_position = if head != tail {
16514 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16515 return;
16516 };
16517 Some(pos)
16518 } else {
16519 None
16520 };
16521
16522 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16523 let url = if let Some(end_pos) = end_position {
16524 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16525 } else {
16526 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16527 };
16528
16529 if let Some(url) = url {
16530 cx.update(|window, cx| {
16531 if parse_zed_link(&url, cx).is_some() {
16532 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16533 } else {
16534 cx.open_url(&url);
16535 }
16536 })?;
16537 }
16538
16539 anyhow::Ok(())
16540 });
16541
16542 url_finder.detach();
16543 }
16544
16545 pub fn open_selected_filename(
16546 &mut self,
16547 _: &OpenSelectedFilename,
16548 window: &mut Window,
16549 cx: &mut Context<Self>,
16550 ) {
16551 let Some(workspace) = self.workspace() else {
16552 return;
16553 };
16554
16555 let position = self.selections.newest_anchor().head();
16556
16557 let Some((buffer, buffer_position)) =
16558 self.buffer.read(cx).text_anchor_for_position(position, cx)
16559 else {
16560 return;
16561 };
16562
16563 let project = self.project.clone();
16564
16565 cx.spawn_in(window, async move |_, cx| {
16566 let result = find_file(&buffer, project, buffer_position, cx).await;
16567
16568 if let Some((_, path)) = result {
16569 workspace
16570 .update_in(cx, |workspace, window, cx| {
16571 workspace.open_resolved_path(path, window, cx)
16572 })?
16573 .await?;
16574 }
16575 anyhow::Ok(())
16576 })
16577 .detach();
16578 }
16579
16580 pub(crate) fn navigate_to_hover_links(
16581 &mut self,
16582 kind: Option<GotoDefinitionKind>,
16583 definitions: Vec<HoverLink>,
16584 split: bool,
16585 window: &mut Window,
16586 cx: &mut Context<Editor>,
16587 ) -> Task<Result<Navigated>> {
16588 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16589 let mut first_url_or_file = None;
16590 let definitions: Vec<_> = definitions
16591 .into_iter()
16592 .filter_map(|def| match def {
16593 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16594 HoverLink::InlayHint(lsp_location, server_id) => {
16595 let computation =
16596 self.compute_target_location(lsp_location, server_id, window, cx);
16597 Some(cx.background_spawn(computation))
16598 }
16599 HoverLink::Url(url) => {
16600 first_url_or_file = Some(Either::Left(url));
16601 None
16602 }
16603 HoverLink::File(path) => {
16604 first_url_or_file = Some(Either::Right(path));
16605 None
16606 }
16607 })
16608 .collect();
16609
16610 let workspace = self.workspace();
16611
16612 cx.spawn_in(window, async move |editor, cx| {
16613 let locations: Vec<Location> = future::join_all(definitions)
16614 .await
16615 .into_iter()
16616 .filter_map(|location| location.transpose())
16617 .collect::<Result<_>>()
16618 .context("location tasks")?;
16619 let mut locations = cx.update(|_, cx| {
16620 locations
16621 .into_iter()
16622 .map(|location| {
16623 let buffer = location.buffer.read(cx);
16624 (location.buffer, location.range.to_point(buffer))
16625 })
16626 .into_group_map()
16627 })?;
16628 let mut num_locations = 0;
16629 for ranges in locations.values_mut() {
16630 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16631 ranges.dedup();
16632 num_locations += ranges.len();
16633 }
16634
16635 if num_locations > 1 {
16636 let Some(workspace) = workspace else {
16637 return Ok(Navigated::No);
16638 };
16639
16640 let tab_kind = match kind {
16641 Some(GotoDefinitionKind::Implementation) => "Implementations",
16642 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16643 Some(GotoDefinitionKind::Declaration) => "Declarations",
16644 Some(GotoDefinitionKind::Type) => "Types",
16645 };
16646 let title = editor
16647 .update_in(cx, |_, _, cx| {
16648 let target = locations
16649 .iter()
16650 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16651 .map(|(buffer, location)| {
16652 buffer
16653 .read(cx)
16654 .text_for_range(location.clone())
16655 .collect::<String>()
16656 })
16657 .filter(|text| !text.contains('\n'))
16658 .unique()
16659 .take(3)
16660 .join(", ");
16661 if target.is_empty() {
16662 tab_kind.to_owned()
16663 } else {
16664 format!("{tab_kind} for {target}")
16665 }
16666 })
16667 .context("buffer title")?;
16668
16669 let opened = workspace
16670 .update_in(cx, |workspace, window, cx| {
16671 Self::open_locations_in_multibuffer(
16672 workspace,
16673 locations,
16674 title,
16675 split,
16676 MultibufferSelectionMode::First,
16677 window,
16678 cx,
16679 )
16680 })
16681 .is_ok();
16682
16683 anyhow::Ok(Navigated::from_bool(opened))
16684 } else if num_locations == 0 {
16685 // If there is one url or file, open it directly
16686 match first_url_or_file {
16687 Some(Either::Left(url)) => {
16688 cx.update(|_, cx| cx.open_url(&url))?;
16689 Ok(Navigated::Yes)
16690 }
16691 Some(Either::Right(path)) => {
16692 let Some(workspace) = workspace else {
16693 return Ok(Navigated::No);
16694 };
16695
16696 workspace
16697 .update_in(cx, |workspace, window, cx| {
16698 workspace.open_resolved_path(path, window, cx)
16699 })?
16700 .await?;
16701 Ok(Navigated::Yes)
16702 }
16703 None => Ok(Navigated::No),
16704 }
16705 } else {
16706 let Some(workspace) = workspace else {
16707 return Ok(Navigated::No);
16708 };
16709
16710 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16711 let target_range = target_ranges.first().unwrap().clone();
16712
16713 editor.update_in(cx, |editor, window, cx| {
16714 let range = target_range.to_point(target_buffer.read(cx));
16715 let range = editor.range_for_match(&range);
16716 let range = collapse_multiline_range(range);
16717
16718 if !split
16719 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16720 {
16721 editor.go_to_singleton_buffer_range(range, window, cx);
16722 } else {
16723 let pane = workspace.read(cx).active_pane().clone();
16724 window.defer(cx, move |window, cx| {
16725 let target_editor: Entity<Self> =
16726 workspace.update(cx, |workspace, cx| {
16727 let pane = if split {
16728 workspace.adjacent_pane(window, cx)
16729 } else {
16730 workspace.active_pane().clone()
16731 };
16732
16733 workspace.open_project_item(
16734 pane,
16735 target_buffer.clone(),
16736 true,
16737 true,
16738 window,
16739 cx,
16740 )
16741 });
16742 target_editor.update(cx, |target_editor, cx| {
16743 // When selecting a definition in a different buffer, disable the nav history
16744 // to avoid creating a history entry at the previous cursor location.
16745 pane.update(cx, |pane, _| pane.disable_history());
16746 target_editor.go_to_singleton_buffer_range(range, window, cx);
16747 pane.update(cx, |pane, _| pane.enable_history());
16748 });
16749 });
16750 }
16751 Navigated::Yes
16752 })
16753 }
16754 })
16755 }
16756
16757 fn compute_target_location(
16758 &self,
16759 lsp_location: lsp::Location,
16760 server_id: LanguageServerId,
16761 window: &mut Window,
16762 cx: &mut Context<Self>,
16763 ) -> Task<anyhow::Result<Option<Location>>> {
16764 let Some(project) = self.project.clone() else {
16765 return Task::ready(Ok(None));
16766 };
16767
16768 cx.spawn_in(window, async move |editor, cx| {
16769 let location_task = editor.update(cx, |_, cx| {
16770 project.update(cx, |project, cx| {
16771 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16772 })
16773 })?;
16774 let location = Some({
16775 let target_buffer_handle = location_task.await.context("open local buffer")?;
16776 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16777 let target_start = target_buffer
16778 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16779 let target_end = target_buffer
16780 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16781 target_buffer.anchor_after(target_start)
16782 ..target_buffer.anchor_before(target_end)
16783 })?;
16784 Location {
16785 buffer: target_buffer_handle,
16786 range,
16787 }
16788 });
16789 Ok(location)
16790 })
16791 }
16792
16793 pub fn find_all_references(
16794 &mut self,
16795 _: &FindAllReferences,
16796 window: &mut Window,
16797 cx: &mut Context<Self>,
16798 ) -> Option<Task<Result<Navigated>>> {
16799 let selection = self.selections.newest::<usize>(cx);
16800 let multi_buffer = self.buffer.read(cx);
16801 let head = selection.head();
16802
16803 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16804 let head_anchor = multi_buffer_snapshot.anchor_at(
16805 head,
16806 if head < selection.tail() {
16807 Bias::Right
16808 } else {
16809 Bias::Left
16810 },
16811 );
16812
16813 match self
16814 .find_all_references_task_sources
16815 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16816 {
16817 Ok(_) => {
16818 log::info!(
16819 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16820 );
16821 return None;
16822 }
16823 Err(i) => {
16824 self.find_all_references_task_sources.insert(i, head_anchor);
16825 }
16826 }
16827
16828 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16829 let workspace = self.workspace()?;
16830 let project = workspace.read(cx).project().clone();
16831 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16832 Some(cx.spawn_in(window, async move |editor, cx| {
16833 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16834 if let Ok(i) = editor
16835 .find_all_references_task_sources
16836 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16837 {
16838 editor.find_all_references_task_sources.remove(i);
16839 }
16840 });
16841
16842 let Some(locations) = references.await? else {
16843 return anyhow::Ok(Navigated::No);
16844 };
16845 let mut locations = cx.update(|_, cx| {
16846 locations
16847 .into_iter()
16848 .map(|location| {
16849 let buffer = location.buffer.read(cx);
16850 (location.buffer, location.range.to_point(buffer))
16851 })
16852 .into_group_map()
16853 })?;
16854 if locations.is_empty() {
16855 return anyhow::Ok(Navigated::No);
16856 }
16857 for ranges in locations.values_mut() {
16858 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16859 ranges.dedup();
16860 }
16861
16862 workspace.update_in(cx, |workspace, window, cx| {
16863 let target = locations
16864 .iter()
16865 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16866 .map(|(buffer, location)| {
16867 buffer
16868 .read(cx)
16869 .text_for_range(location.clone())
16870 .collect::<String>()
16871 })
16872 .filter(|text| !text.contains('\n'))
16873 .unique()
16874 .take(3)
16875 .join(", ");
16876 let title = if target.is_empty() {
16877 "References".to_owned()
16878 } else {
16879 format!("References to {target}")
16880 };
16881 Self::open_locations_in_multibuffer(
16882 workspace,
16883 locations,
16884 title,
16885 false,
16886 MultibufferSelectionMode::First,
16887 window,
16888 cx,
16889 );
16890 Navigated::Yes
16891 })
16892 }))
16893 }
16894
16895 /// Opens a multibuffer with the given project locations in it
16896 pub fn open_locations_in_multibuffer(
16897 workspace: &mut Workspace,
16898 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16899 title: String,
16900 split: bool,
16901 multibuffer_selection_mode: MultibufferSelectionMode,
16902 window: &mut Window,
16903 cx: &mut Context<Workspace>,
16904 ) {
16905 if locations.is_empty() {
16906 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16907 return;
16908 }
16909
16910 let capability = workspace.project().read(cx).capability();
16911 let mut ranges = <Vec<Range<Anchor>>>::new();
16912
16913 // a key to find existing multibuffer editors with the same set of locations
16914 // to prevent us from opening more and more multibuffer tabs for searches and the like
16915 let mut key = (title.clone(), vec![]);
16916 let excerpt_buffer = cx.new(|cx| {
16917 let key = &mut key.1;
16918 let mut multibuffer = MultiBuffer::new(capability);
16919 for (buffer, mut ranges_for_buffer) in locations {
16920 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16921 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16922 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16923 PathKey::for_buffer(&buffer, cx),
16924 buffer.clone(),
16925 ranges_for_buffer,
16926 multibuffer_context_lines(cx),
16927 cx,
16928 );
16929 ranges.extend(new_ranges)
16930 }
16931
16932 multibuffer.with_title(title)
16933 });
16934 let existing = workspace.active_pane().update(cx, |pane, cx| {
16935 pane.items()
16936 .filter_map(|item| item.downcast::<Editor>())
16937 .find(|editor| {
16938 editor
16939 .read(cx)
16940 .lookup_key
16941 .as_ref()
16942 .and_then(|it| {
16943 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16944 })
16945 .is_some_and(|it| *it == key)
16946 })
16947 });
16948 let editor = existing.unwrap_or_else(|| {
16949 cx.new(|cx| {
16950 let mut editor = Editor::for_multibuffer(
16951 excerpt_buffer,
16952 Some(workspace.project().clone()),
16953 window,
16954 cx,
16955 );
16956 editor.lookup_key = Some(Box::new(key));
16957 editor
16958 })
16959 });
16960 editor.update(cx, |editor, cx| {
16961 match multibuffer_selection_mode {
16962 MultibufferSelectionMode::First => {
16963 if let Some(first_range) = ranges.first() {
16964 editor.change_selections(
16965 SelectionEffects::no_scroll(),
16966 window,
16967 cx,
16968 |selections| {
16969 selections.clear_disjoint();
16970 selections
16971 .select_anchor_ranges(std::iter::once(first_range.clone()));
16972 },
16973 );
16974 }
16975 editor.highlight_background::<Self>(
16976 &ranges,
16977 |theme| theme.colors().editor_highlighted_line_background,
16978 cx,
16979 );
16980 }
16981 MultibufferSelectionMode::All => {
16982 editor.change_selections(
16983 SelectionEffects::no_scroll(),
16984 window,
16985 cx,
16986 |selections| {
16987 selections.clear_disjoint();
16988 selections.select_anchor_ranges(ranges);
16989 },
16990 );
16991 }
16992 }
16993 editor.register_buffers_with_language_servers(cx);
16994 });
16995
16996 let item = Box::new(editor);
16997 let item_id = item.item_id();
16998
16999 if split {
17000 let pane = workspace.adjacent_pane(window, cx);
17001 workspace.add_item(pane, item, None, true, true, window, cx);
17002 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17003 let (preview_item_id, preview_item_idx) =
17004 workspace.active_pane().read_with(cx, |pane, _| {
17005 (pane.preview_item_id(), pane.preview_item_idx())
17006 });
17007
17008 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17009
17010 if let Some(preview_item_id) = preview_item_id {
17011 workspace.active_pane().update(cx, |pane, cx| {
17012 pane.remove_item(preview_item_id, false, false, window, cx);
17013 });
17014 }
17015 } else {
17016 workspace.add_item_to_active_pane(item, None, true, window, cx);
17017 }
17018 workspace.active_pane().update(cx, |pane, cx| {
17019 pane.set_preview_item_id(Some(item_id), cx);
17020 });
17021 }
17022
17023 pub fn rename(
17024 &mut self,
17025 _: &Rename,
17026 window: &mut Window,
17027 cx: &mut Context<Self>,
17028 ) -> Option<Task<Result<()>>> {
17029 use language::ToOffset as _;
17030
17031 let provider = self.semantics_provider.clone()?;
17032 let selection = self.selections.newest_anchor().clone();
17033 let (cursor_buffer, cursor_buffer_position) = self
17034 .buffer
17035 .read(cx)
17036 .text_anchor_for_position(selection.head(), cx)?;
17037 let (tail_buffer, cursor_buffer_position_end) = self
17038 .buffer
17039 .read(cx)
17040 .text_anchor_for_position(selection.tail(), cx)?;
17041 if tail_buffer != cursor_buffer {
17042 return None;
17043 }
17044
17045 let snapshot = cursor_buffer.read(cx).snapshot();
17046 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17047 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17048 let prepare_rename = provider
17049 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17050 .unwrap_or_else(|| Task::ready(Ok(None)));
17051 drop(snapshot);
17052
17053 Some(cx.spawn_in(window, async move |this, cx| {
17054 let rename_range = if let Some(range) = prepare_rename.await? {
17055 Some(range)
17056 } else {
17057 this.update(cx, |this, cx| {
17058 let buffer = this.buffer.read(cx).snapshot(cx);
17059 let mut buffer_highlights = this
17060 .document_highlights_for_position(selection.head(), &buffer)
17061 .filter(|highlight| {
17062 highlight.start.excerpt_id == selection.head().excerpt_id
17063 && highlight.end.excerpt_id == selection.head().excerpt_id
17064 });
17065 buffer_highlights
17066 .next()
17067 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17068 })?
17069 };
17070 if let Some(rename_range) = rename_range {
17071 this.update_in(cx, |this, window, cx| {
17072 let snapshot = cursor_buffer.read(cx).snapshot();
17073 let rename_buffer_range = rename_range.to_offset(&snapshot);
17074 let cursor_offset_in_rename_range =
17075 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17076 let cursor_offset_in_rename_range_end =
17077 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17078
17079 this.take_rename(false, window, cx);
17080 let buffer = this.buffer.read(cx).read(cx);
17081 let cursor_offset = selection.head().to_offset(&buffer);
17082 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17083 let rename_end = rename_start + rename_buffer_range.len();
17084 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17085 let mut old_highlight_id = None;
17086 let old_name: Arc<str> = buffer
17087 .chunks(rename_start..rename_end, true)
17088 .map(|chunk| {
17089 if old_highlight_id.is_none() {
17090 old_highlight_id = chunk.syntax_highlight_id;
17091 }
17092 chunk.text
17093 })
17094 .collect::<String>()
17095 .into();
17096
17097 drop(buffer);
17098
17099 // Position the selection in the rename editor so that it matches the current selection.
17100 this.show_local_selections = false;
17101 let rename_editor = cx.new(|cx| {
17102 let mut editor = Editor::single_line(window, cx);
17103 editor.buffer.update(cx, |buffer, cx| {
17104 buffer.edit([(0..0, old_name.clone())], None, cx)
17105 });
17106 let rename_selection_range = match cursor_offset_in_rename_range
17107 .cmp(&cursor_offset_in_rename_range_end)
17108 {
17109 Ordering::Equal => {
17110 editor.select_all(&SelectAll, window, cx);
17111 return editor;
17112 }
17113 Ordering::Less => {
17114 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17115 }
17116 Ordering::Greater => {
17117 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17118 }
17119 };
17120 if rename_selection_range.end > old_name.len() {
17121 editor.select_all(&SelectAll, window, cx);
17122 } else {
17123 editor.change_selections(Default::default(), window, cx, |s| {
17124 s.select_ranges([rename_selection_range]);
17125 });
17126 }
17127 editor
17128 });
17129 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17130 if e == &EditorEvent::Focused {
17131 cx.emit(EditorEvent::FocusedIn)
17132 }
17133 })
17134 .detach();
17135
17136 let write_highlights =
17137 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17138 let read_highlights =
17139 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17140 let ranges = write_highlights
17141 .iter()
17142 .flat_map(|(_, ranges)| ranges.iter())
17143 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17144 .cloned()
17145 .collect();
17146
17147 this.highlight_text::<Rename>(
17148 ranges,
17149 HighlightStyle {
17150 fade_out: Some(0.6),
17151 ..Default::default()
17152 },
17153 cx,
17154 );
17155 let rename_focus_handle = rename_editor.focus_handle(cx);
17156 window.focus(&rename_focus_handle);
17157 let block_id = this.insert_blocks(
17158 [BlockProperties {
17159 style: BlockStyle::Flex,
17160 placement: BlockPlacement::Below(range.start),
17161 height: Some(1),
17162 render: Arc::new({
17163 let rename_editor = rename_editor.clone();
17164 move |cx: &mut BlockContext| {
17165 let mut text_style = cx.editor_style.text.clone();
17166 if let Some(highlight_style) = old_highlight_id
17167 .and_then(|h| h.style(&cx.editor_style.syntax))
17168 {
17169 text_style = text_style.highlight(highlight_style);
17170 }
17171 div()
17172 .block_mouse_except_scroll()
17173 .pl(cx.anchor_x)
17174 .child(EditorElement::new(
17175 &rename_editor,
17176 EditorStyle {
17177 background: cx.theme().system().transparent,
17178 local_player: cx.editor_style.local_player,
17179 text: text_style,
17180 scrollbar_width: cx.editor_style.scrollbar_width,
17181 syntax: cx.editor_style.syntax.clone(),
17182 status: cx.editor_style.status.clone(),
17183 inlay_hints_style: HighlightStyle {
17184 font_weight: Some(FontWeight::BOLD),
17185 ..make_inlay_hints_style(cx.app)
17186 },
17187 edit_prediction_styles: make_suggestion_styles(
17188 cx.app,
17189 ),
17190 ..EditorStyle::default()
17191 },
17192 ))
17193 .into_any_element()
17194 }
17195 }),
17196 priority: 0,
17197 }],
17198 Some(Autoscroll::fit()),
17199 cx,
17200 )[0];
17201 this.pending_rename = Some(RenameState {
17202 range,
17203 old_name,
17204 editor: rename_editor,
17205 block_id,
17206 });
17207 })?;
17208 }
17209
17210 Ok(())
17211 }))
17212 }
17213
17214 pub fn confirm_rename(
17215 &mut self,
17216 _: &ConfirmRename,
17217 window: &mut Window,
17218 cx: &mut Context<Self>,
17219 ) -> Option<Task<Result<()>>> {
17220 let rename = self.take_rename(false, window, cx)?;
17221 let workspace = self.workspace()?.downgrade();
17222 let (buffer, start) = self
17223 .buffer
17224 .read(cx)
17225 .text_anchor_for_position(rename.range.start, cx)?;
17226 let (end_buffer, _) = self
17227 .buffer
17228 .read(cx)
17229 .text_anchor_for_position(rename.range.end, cx)?;
17230 if buffer != end_buffer {
17231 return None;
17232 }
17233
17234 let old_name = rename.old_name;
17235 let new_name = rename.editor.read(cx).text(cx);
17236
17237 let rename = self.semantics_provider.as_ref()?.perform_rename(
17238 &buffer,
17239 start,
17240 new_name.clone(),
17241 cx,
17242 )?;
17243
17244 Some(cx.spawn_in(window, async move |editor, cx| {
17245 let project_transaction = rename.await?;
17246 Self::open_project_transaction(
17247 &editor,
17248 workspace,
17249 project_transaction,
17250 format!("Rename: {} → {}", old_name, new_name),
17251 cx,
17252 )
17253 .await?;
17254
17255 editor.update(cx, |editor, cx| {
17256 editor.refresh_document_highlights(cx);
17257 })?;
17258 Ok(())
17259 }))
17260 }
17261
17262 fn take_rename(
17263 &mut self,
17264 moving_cursor: bool,
17265 window: &mut Window,
17266 cx: &mut Context<Self>,
17267 ) -> Option<RenameState> {
17268 let rename = self.pending_rename.take()?;
17269 if rename.editor.focus_handle(cx).is_focused(window) {
17270 window.focus(&self.focus_handle);
17271 }
17272
17273 self.remove_blocks(
17274 [rename.block_id].into_iter().collect(),
17275 Some(Autoscroll::fit()),
17276 cx,
17277 );
17278 self.clear_highlights::<Rename>(cx);
17279 self.show_local_selections = true;
17280
17281 if moving_cursor {
17282 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17283 editor.selections.newest::<usize>(cx).head()
17284 });
17285
17286 // Update the selection to match the position of the selection inside
17287 // the rename editor.
17288 let snapshot = self.buffer.read(cx).read(cx);
17289 let rename_range = rename.range.to_offset(&snapshot);
17290 let cursor_in_editor = snapshot
17291 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17292 .min(rename_range.end);
17293 drop(snapshot);
17294
17295 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17296 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17297 });
17298 } else {
17299 self.refresh_document_highlights(cx);
17300 }
17301
17302 Some(rename)
17303 }
17304
17305 pub fn pending_rename(&self) -> Option<&RenameState> {
17306 self.pending_rename.as_ref()
17307 }
17308
17309 fn format(
17310 &mut self,
17311 _: &Format,
17312 window: &mut Window,
17313 cx: &mut Context<Self>,
17314 ) -> Option<Task<Result<()>>> {
17315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17316
17317 let project = match &self.project {
17318 Some(project) => project.clone(),
17319 None => return None,
17320 };
17321
17322 Some(self.perform_format(
17323 project,
17324 FormatTrigger::Manual,
17325 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17326 window,
17327 cx,
17328 ))
17329 }
17330
17331 fn format_selections(
17332 &mut self,
17333 _: &FormatSelections,
17334 window: &mut Window,
17335 cx: &mut Context<Self>,
17336 ) -> Option<Task<Result<()>>> {
17337 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17338
17339 let project = match &self.project {
17340 Some(project) => project.clone(),
17341 None => return None,
17342 };
17343
17344 let ranges = self
17345 .selections
17346 .all_adjusted(cx)
17347 .into_iter()
17348 .map(|selection| selection.range())
17349 .collect_vec();
17350
17351 Some(self.perform_format(
17352 project,
17353 FormatTrigger::Manual,
17354 FormatTarget::Ranges(ranges),
17355 window,
17356 cx,
17357 ))
17358 }
17359
17360 fn perform_format(
17361 &mut self,
17362 project: Entity<Project>,
17363 trigger: FormatTrigger,
17364 target: FormatTarget,
17365 window: &mut Window,
17366 cx: &mut Context<Self>,
17367 ) -> Task<Result<()>> {
17368 let buffer = self.buffer.clone();
17369 let (buffers, target) = match target {
17370 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17371 FormatTarget::Ranges(selection_ranges) => {
17372 let multi_buffer = buffer.read(cx);
17373 let snapshot = multi_buffer.read(cx);
17374 let mut buffers = HashSet::default();
17375 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17376 BTreeMap::new();
17377 for selection_range in selection_ranges {
17378 for (buffer, buffer_range, _) in
17379 snapshot.range_to_buffer_ranges(selection_range)
17380 {
17381 let buffer_id = buffer.remote_id();
17382 let start = buffer.anchor_before(buffer_range.start);
17383 let end = buffer.anchor_after(buffer_range.end);
17384 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17385 buffer_id_to_ranges
17386 .entry(buffer_id)
17387 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17388 .or_insert_with(|| vec![start..end]);
17389 }
17390 }
17391 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17392 }
17393 };
17394
17395 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17396 let selections_prev = transaction_id_prev
17397 .and_then(|transaction_id_prev| {
17398 // default to selections as they were after the last edit, if we have them,
17399 // instead of how they are now.
17400 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17401 // will take you back to where you made the last edit, instead of staying where you scrolled
17402 self.selection_history
17403 .transaction(transaction_id_prev)
17404 .map(|t| t.0.clone())
17405 })
17406 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17407
17408 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17409 let format = project.update(cx, |project, cx| {
17410 project.format(buffers, target, true, trigger, cx)
17411 });
17412
17413 cx.spawn_in(window, async move |editor, cx| {
17414 let transaction = futures::select_biased! {
17415 transaction = format.log_err().fuse() => transaction,
17416 () = timeout => {
17417 log::warn!("timed out waiting for formatting");
17418 None
17419 }
17420 };
17421
17422 buffer
17423 .update(cx, |buffer, cx| {
17424 if let Some(transaction) = transaction
17425 && !buffer.is_singleton()
17426 {
17427 buffer.push_transaction(&transaction.0, cx);
17428 }
17429 cx.notify();
17430 })
17431 .ok();
17432
17433 if let Some(transaction_id_now) =
17434 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17435 {
17436 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17437 if has_new_transaction {
17438 _ = editor.update(cx, |editor, _| {
17439 editor
17440 .selection_history
17441 .insert_transaction(transaction_id_now, selections_prev);
17442 });
17443 }
17444 }
17445
17446 Ok(())
17447 })
17448 }
17449
17450 fn organize_imports(
17451 &mut self,
17452 _: &OrganizeImports,
17453 window: &mut Window,
17454 cx: &mut Context<Self>,
17455 ) -> Option<Task<Result<()>>> {
17456 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17457 let project = match &self.project {
17458 Some(project) => project.clone(),
17459 None => return None,
17460 };
17461 Some(self.perform_code_action_kind(
17462 project,
17463 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17464 window,
17465 cx,
17466 ))
17467 }
17468
17469 fn perform_code_action_kind(
17470 &mut self,
17471 project: Entity<Project>,
17472 kind: CodeActionKind,
17473 window: &mut Window,
17474 cx: &mut Context<Self>,
17475 ) -> Task<Result<()>> {
17476 let buffer = self.buffer.clone();
17477 let buffers = buffer.read(cx).all_buffers();
17478 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17479 let apply_action = project.update(cx, |project, cx| {
17480 project.apply_code_action_kind(buffers, kind, true, cx)
17481 });
17482 cx.spawn_in(window, async move |_, cx| {
17483 let transaction = futures::select_biased! {
17484 () = timeout => {
17485 log::warn!("timed out waiting for executing code action");
17486 None
17487 }
17488 transaction = apply_action.log_err().fuse() => transaction,
17489 };
17490 buffer
17491 .update(cx, |buffer, cx| {
17492 // check if we need this
17493 if let Some(transaction) = transaction
17494 && !buffer.is_singleton()
17495 {
17496 buffer.push_transaction(&transaction.0, cx);
17497 }
17498 cx.notify();
17499 })
17500 .ok();
17501 Ok(())
17502 })
17503 }
17504
17505 pub fn restart_language_server(
17506 &mut self,
17507 _: &RestartLanguageServer,
17508 _: &mut Window,
17509 cx: &mut Context<Self>,
17510 ) {
17511 if let Some(project) = self.project.clone() {
17512 self.buffer.update(cx, |multi_buffer, cx| {
17513 project.update(cx, |project, cx| {
17514 project.restart_language_servers_for_buffers(
17515 multi_buffer.all_buffers().into_iter().collect(),
17516 HashSet::default(),
17517 cx,
17518 );
17519 });
17520 })
17521 }
17522 }
17523
17524 pub fn stop_language_server(
17525 &mut self,
17526 _: &StopLanguageServer,
17527 _: &mut Window,
17528 cx: &mut Context<Self>,
17529 ) {
17530 if let Some(project) = self.project.clone() {
17531 self.buffer.update(cx, |multi_buffer, cx| {
17532 project.update(cx, |project, cx| {
17533 project.stop_language_servers_for_buffers(
17534 multi_buffer.all_buffers().into_iter().collect(),
17535 HashSet::default(),
17536 cx,
17537 );
17538 cx.emit(project::Event::RefreshInlayHints);
17539 });
17540 });
17541 }
17542 }
17543
17544 fn cancel_language_server_work(
17545 workspace: &mut Workspace,
17546 _: &actions::CancelLanguageServerWork,
17547 _: &mut Window,
17548 cx: &mut Context<Workspace>,
17549 ) {
17550 let project = workspace.project();
17551 let buffers = workspace
17552 .active_item(cx)
17553 .and_then(|item| item.act_as::<Editor>(cx))
17554 .map_or(HashSet::default(), |editor| {
17555 editor.read(cx).buffer.read(cx).all_buffers()
17556 });
17557 project.update(cx, |project, cx| {
17558 project.cancel_language_server_work_for_buffers(buffers, cx);
17559 });
17560 }
17561
17562 fn show_character_palette(
17563 &mut self,
17564 _: &ShowCharacterPalette,
17565 window: &mut Window,
17566 _: &mut Context<Self>,
17567 ) {
17568 window.show_character_palette();
17569 }
17570
17571 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17572 if !self.diagnostics_enabled() {
17573 return;
17574 }
17575
17576 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17577 let buffer = self.buffer.read(cx).snapshot(cx);
17578 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17579 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17580 let is_valid = buffer
17581 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17582 .any(|entry| {
17583 entry.diagnostic.is_primary
17584 && !entry.range.is_empty()
17585 && entry.range.start == primary_range_start
17586 && entry.diagnostic.message == active_diagnostics.active_message
17587 });
17588
17589 if !is_valid {
17590 self.dismiss_diagnostics(cx);
17591 }
17592 }
17593 }
17594
17595 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17596 match &self.active_diagnostics {
17597 ActiveDiagnostic::Group(group) => Some(group),
17598 _ => None,
17599 }
17600 }
17601
17602 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17603 if !self.diagnostics_enabled() {
17604 return;
17605 }
17606 self.dismiss_diagnostics(cx);
17607 self.active_diagnostics = ActiveDiagnostic::All;
17608 }
17609
17610 fn activate_diagnostics(
17611 &mut self,
17612 buffer_id: BufferId,
17613 diagnostic: DiagnosticEntryRef<'_, usize>,
17614 window: &mut Window,
17615 cx: &mut Context<Self>,
17616 ) {
17617 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17618 return;
17619 }
17620 self.dismiss_diagnostics(cx);
17621 let snapshot = self.snapshot(window, cx);
17622 let buffer = self.buffer.read(cx).snapshot(cx);
17623 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17624 return;
17625 };
17626
17627 let diagnostic_group = buffer
17628 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17629 .collect::<Vec<_>>();
17630
17631 let blocks =
17632 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17633
17634 let blocks = self.display_map.update(cx, |display_map, cx| {
17635 display_map.insert_blocks(blocks, cx).into_iter().collect()
17636 });
17637 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17638 active_range: buffer.anchor_before(diagnostic.range.start)
17639 ..buffer.anchor_after(diagnostic.range.end),
17640 active_message: diagnostic.diagnostic.message.clone(),
17641 group_id: diagnostic.diagnostic.group_id,
17642 blocks,
17643 });
17644 cx.notify();
17645 }
17646
17647 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17648 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17649 return;
17650 };
17651
17652 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17653 if let ActiveDiagnostic::Group(group) = prev {
17654 self.display_map.update(cx, |display_map, cx| {
17655 display_map.remove_blocks(group.blocks, cx);
17656 });
17657 cx.notify();
17658 }
17659 }
17660
17661 /// Disable inline diagnostics rendering for this editor.
17662 pub fn disable_inline_diagnostics(&mut self) {
17663 self.inline_diagnostics_enabled = false;
17664 self.inline_diagnostics_update = Task::ready(());
17665 self.inline_diagnostics.clear();
17666 }
17667
17668 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17669 self.diagnostics_enabled = false;
17670 self.dismiss_diagnostics(cx);
17671 self.inline_diagnostics_update = Task::ready(());
17672 self.inline_diagnostics.clear();
17673 }
17674
17675 pub fn disable_word_completions(&mut self) {
17676 self.word_completions_enabled = false;
17677 }
17678
17679 pub fn diagnostics_enabled(&self) -> bool {
17680 self.diagnostics_enabled && self.mode.is_full()
17681 }
17682
17683 pub fn inline_diagnostics_enabled(&self) -> bool {
17684 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17685 }
17686
17687 pub fn show_inline_diagnostics(&self) -> bool {
17688 self.show_inline_diagnostics
17689 }
17690
17691 pub fn toggle_inline_diagnostics(
17692 &mut self,
17693 _: &ToggleInlineDiagnostics,
17694 window: &mut Window,
17695 cx: &mut Context<Editor>,
17696 ) {
17697 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17698 self.refresh_inline_diagnostics(false, window, cx);
17699 }
17700
17701 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17702 self.diagnostics_max_severity = severity;
17703 self.display_map.update(cx, |display_map, _| {
17704 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17705 });
17706 }
17707
17708 pub fn toggle_diagnostics(
17709 &mut self,
17710 _: &ToggleDiagnostics,
17711 window: &mut Window,
17712 cx: &mut Context<Editor>,
17713 ) {
17714 if !self.diagnostics_enabled() {
17715 return;
17716 }
17717
17718 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17719 EditorSettings::get_global(cx)
17720 .diagnostics_max_severity
17721 .filter(|severity| severity != &DiagnosticSeverity::Off)
17722 .unwrap_or(DiagnosticSeverity::Hint)
17723 } else {
17724 DiagnosticSeverity::Off
17725 };
17726 self.set_max_diagnostics_severity(new_severity, cx);
17727 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17728 self.active_diagnostics = ActiveDiagnostic::None;
17729 self.inline_diagnostics_update = Task::ready(());
17730 self.inline_diagnostics.clear();
17731 } else {
17732 self.refresh_inline_diagnostics(false, window, cx);
17733 }
17734
17735 cx.notify();
17736 }
17737
17738 pub fn toggle_minimap(
17739 &mut self,
17740 _: &ToggleMinimap,
17741 window: &mut Window,
17742 cx: &mut Context<Editor>,
17743 ) {
17744 if self.supports_minimap(cx) {
17745 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17746 }
17747 }
17748
17749 fn refresh_inline_diagnostics(
17750 &mut self,
17751 debounce: bool,
17752 window: &mut Window,
17753 cx: &mut Context<Self>,
17754 ) {
17755 let max_severity = ProjectSettings::get_global(cx)
17756 .diagnostics
17757 .inline
17758 .max_severity
17759 .unwrap_or(self.diagnostics_max_severity);
17760
17761 if !self.inline_diagnostics_enabled()
17762 || !self.show_inline_diagnostics
17763 || max_severity == DiagnosticSeverity::Off
17764 {
17765 self.inline_diagnostics_update = Task::ready(());
17766 self.inline_diagnostics.clear();
17767 return;
17768 }
17769
17770 let debounce_ms = ProjectSettings::get_global(cx)
17771 .diagnostics
17772 .inline
17773 .update_debounce_ms;
17774 let debounce = if debounce && debounce_ms > 0 {
17775 Some(Duration::from_millis(debounce_ms))
17776 } else {
17777 None
17778 };
17779 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17780 if let Some(debounce) = debounce {
17781 cx.background_executor().timer(debounce).await;
17782 }
17783 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17784 editor
17785 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17786 .ok()
17787 }) else {
17788 return;
17789 };
17790
17791 let new_inline_diagnostics = cx
17792 .background_spawn(async move {
17793 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17794 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17795 let message = diagnostic_entry
17796 .diagnostic
17797 .message
17798 .split_once('\n')
17799 .map(|(line, _)| line)
17800 .map(SharedString::new)
17801 .unwrap_or_else(|| {
17802 SharedString::new(&*diagnostic_entry.diagnostic.message)
17803 });
17804 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17805 let (Ok(i) | Err(i)) = inline_diagnostics
17806 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17807 inline_diagnostics.insert(
17808 i,
17809 (
17810 start_anchor,
17811 InlineDiagnostic {
17812 message,
17813 group_id: diagnostic_entry.diagnostic.group_id,
17814 start: diagnostic_entry.range.start.to_point(&snapshot),
17815 is_primary: diagnostic_entry.diagnostic.is_primary,
17816 severity: diagnostic_entry.diagnostic.severity,
17817 },
17818 ),
17819 );
17820 }
17821 inline_diagnostics
17822 })
17823 .await;
17824
17825 editor
17826 .update(cx, |editor, cx| {
17827 editor.inline_diagnostics = new_inline_diagnostics;
17828 cx.notify();
17829 })
17830 .ok();
17831 });
17832 }
17833
17834 fn pull_diagnostics(
17835 &mut self,
17836 buffer_id: Option<BufferId>,
17837 window: &Window,
17838 cx: &mut Context<Self>,
17839 ) -> Option<()> {
17840 if !self.mode().is_full() {
17841 return None;
17842 }
17843 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17844 .diagnostics
17845 .lsp_pull_diagnostics;
17846 if !pull_diagnostics_settings.enabled {
17847 return None;
17848 }
17849 let project = self.project()?.downgrade();
17850 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17851 let mut buffers = self.buffer.read(cx).all_buffers();
17852 if let Some(buffer_id) = buffer_id {
17853 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17854 }
17855
17856 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17857 cx.background_executor().timer(debounce).await;
17858
17859 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17860 buffers
17861 .into_iter()
17862 .filter_map(|buffer| {
17863 project
17864 .update(cx, |project, cx| {
17865 project.lsp_store().update(cx, |lsp_store, cx| {
17866 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17867 })
17868 })
17869 .ok()
17870 })
17871 .collect::<FuturesUnordered<_>>()
17872 }) else {
17873 return;
17874 };
17875
17876 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17877 match pull_task {
17878 Ok(()) => {
17879 if editor
17880 .update_in(cx, |editor, window, cx| {
17881 editor.update_diagnostics_state(window, cx);
17882 })
17883 .is_err()
17884 {
17885 return;
17886 }
17887 }
17888 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17889 }
17890 }
17891 });
17892
17893 Some(())
17894 }
17895
17896 pub fn set_selections_from_remote(
17897 &mut self,
17898 selections: Vec<Selection<Anchor>>,
17899 pending_selection: Option<Selection<Anchor>>,
17900 window: &mut Window,
17901 cx: &mut Context<Self>,
17902 ) {
17903 let old_cursor_position = self.selections.newest_anchor().head();
17904 self.selections.change_with(cx, |s| {
17905 s.select_anchors(selections);
17906 if let Some(pending_selection) = pending_selection {
17907 s.set_pending(pending_selection, SelectMode::Character);
17908 } else {
17909 s.clear_pending();
17910 }
17911 });
17912 self.selections_did_change(
17913 false,
17914 &old_cursor_position,
17915 SelectionEffects::default(),
17916 window,
17917 cx,
17918 );
17919 }
17920
17921 pub fn transact(
17922 &mut self,
17923 window: &mut Window,
17924 cx: &mut Context<Self>,
17925 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17926 ) -> Option<TransactionId> {
17927 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17928 this.start_transaction_at(Instant::now(), window, cx);
17929 update(this, window, cx);
17930 this.end_transaction_at(Instant::now(), cx)
17931 })
17932 }
17933
17934 pub fn start_transaction_at(
17935 &mut self,
17936 now: Instant,
17937 window: &mut Window,
17938 cx: &mut Context<Self>,
17939 ) -> Option<TransactionId> {
17940 self.end_selection(window, cx);
17941 if let Some(tx_id) = self
17942 .buffer
17943 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17944 {
17945 self.selection_history
17946 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17947 cx.emit(EditorEvent::TransactionBegun {
17948 transaction_id: tx_id,
17949 });
17950 Some(tx_id)
17951 } else {
17952 None
17953 }
17954 }
17955
17956 pub fn end_transaction_at(
17957 &mut self,
17958 now: Instant,
17959 cx: &mut Context<Self>,
17960 ) -> Option<TransactionId> {
17961 if let Some(transaction_id) = self
17962 .buffer
17963 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17964 {
17965 if let Some((_, end_selections)) =
17966 self.selection_history.transaction_mut(transaction_id)
17967 {
17968 *end_selections = Some(self.selections.disjoint_anchors_arc());
17969 } else {
17970 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17971 }
17972
17973 cx.emit(EditorEvent::Edited { transaction_id });
17974 Some(transaction_id)
17975 } else {
17976 None
17977 }
17978 }
17979
17980 pub fn modify_transaction_selection_history(
17981 &mut self,
17982 transaction_id: TransactionId,
17983 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17984 ) -> bool {
17985 self.selection_history
17986 .transaction_mut(transaction_id)
17987 .map(modify)
17988 .is_some()
17989 }
17990
17991 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17992 if self.selection_mark_mode {
17993 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17994 s.move_with(|_, sel| {
17995 sel.collapse_to(sel.head(), SelectionGoal::None);
17996 });
17997 })
17998 }
17999 self.selection_mark_mode = true;
18000 cx.notify();
18001 }
18002
18003 pub fn swap_selection_ends(
18004 &mut self,
18005 _: &actions::SwapSelectionEnds,
18006 window: &mut Window,
18007 cx: &mut Context<Self>,
18008 ) {
18009 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18010 s.move_with(|_, sel| {
18011 if sel.start != sel.end {
18012 sel.reversed = !sel.reversed
18013 }
18014 });
18015 });
18016 self.request_autoscroll(Autoscroll::newest(), cx);
18017 cx.notify();
18018 }
18019
18020 pub fn toggle_focus(
18021 workspace: &mut Workspace,
18022 _: &actions::ToggleFocus,
18023 window: &mut Window,
18024 cx: &mut Context<Workspace>,
18025 ) {
18026 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18027 return;
18028 };
18029 workspace.activate_item(&item, true, true, window, cx);
18030 }
18031
18032 pub fn toggle_fold(
18033 &mut self,
18034 _: &actions::ToggleFold,
18035 window: &mut Window,
18036 cx: &mut Context<Self>,
18037 ) {
18038 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18039 let selection = self.selections.newest::<Point>(cx);
18040
18041 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18042 let range = if selection.is_empty() {
18043 let point = selection.head().to_display_point(&display_map);
18044 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18045 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18046 .to_point(&display_map);
18047 start..end
18048 } else {
18049 selection.range()
18050 };
18051 if display_map.folds_in_range(range).next().is_some() {
18052 self.unfold_lines(&Default::default(), window, cx)
18053 } else {
18054 self.fold(&Default::default(), window, cx)
18055 }
18056 } else {
18057 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18058 let buffer_ids: HashSet<_> = self
18059 .selections
18060 .disjoint_anchor_ranges()
18061 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18062 .collect();
18063
18064 let should_unfold = buffer_ids
18065 .iter()
18066 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18067
18068 for buffer_id in buffer_ids {
18069 if should_unfold {
18070 self.unfold_buffer(buffer_id, cx);
18071 } else {
18072 self.fold_buffer(buffer_id, cx);
18073 }
18074 }
18075 }
18076 }
18077
18078 pub fn toggle_fold_recursive(
18079 &mut self,
18080 _: &actions::ToggleFoldRecursive,
18081 window: &mut Window,
18082 cx: &mut Context<Self>,
18083 ) {
18084 let selection = self.selections.newest::<Point>(cx);
18085
18086 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18087 let range = if selection.is_empty() {
18088 let point = selection.head().to_display_point(&display_map);
18089 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18090 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18091 .to_point(&display_map);
18092 start..end
18093 } else {
18094 selection.range()
18095 };
18096 if display_map.folds_in_range(range).next().is_some() {
18097 self.unfold_recursive(&Default::default(), window, cx)
18098 } else {
18099 self.fold_recursive(&Default::default(), window, cx)
18100 }
18101 }
18102
18103 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18104 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18105 let mut to_fold = Vec::new();
18106 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18107 let selections = self.selections.all_adjusted(cx);
18108
18109 for selection in selections {
18110 let range = selection.range().sorted();
18111 let buffer_start_row = range.start.row;
18112
18113 if range.start.row != range.end.row {
18114 let mut found = false;
18115 let mut row = range.start.row;
18116 while row <= range.end.row {
18117 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18118 {
18119 found = true;
18120 row = crease.range().end.row + 1;
18121 to_fold.push(crease);
18122 } else {
18123 row += 1
18124 }
18125 }
18126 if found {
18127 continue;
18128 }
18129 }
18130
18131 for row in (0..=range.start.row).rev() {
18132 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18133 && crease.range().end.row >= buffer_start_row
18134 {
18135 to_fold.push(crease);
18136 if row <= range.start.row {
18137 break;
18138 }
18139 }
18140 }
18141 }
18142
18143 self.fold_creases(to_fold, true, window, cx);
18144 } else {
18145 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18146 let buffer_ids = self
18147 .selections
18148 .disjoint_anchor_ranges()
18149 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18150 .collect::<HashSet<_>>();
18151 for buffer_id in buffer_ids {
18152 self.fold_buffer(buffer_id, cx);
18153 }
18154 }
18155 }
18156
18157 pub fn toggle_fold_all(
18158 &mut self,
18159 _: &actions::ToggleFoldAll,
18160 window: &mut Window,
18161 cx: &mut Context<Self>,
18162 ) {
18163 if self.buffer.read(cx).is_singleton() {
18164 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18165 let has_folds = display_map
18166 .folds_in_range(0..display_map.buffer_snapshot().len())
18167 .next()
18168 .is_some();
18169
18170 if has_folds {
18171 self.unfold_all(&actions::UnfoldAll, window, cx);
18172 } else {
18173 self.fold_all(&actions::FoldAll, window, cx);
18174 }
18175 } else {
18176 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18177 let should_unfold = buffer_ids
18178 .iter()
18179 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18180
18181 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18182 editor
18183 .update_in(cx, |editor, _, cx| {
18184 for buffer_id in buffer_ids {
18185 if should_unfold {
18186 editor.unfold_buffer(buffer_id, cx);
18187 } else {
18188 editor.fold_buffer(buffer_id, cx);
18189 }
18190 }
18191 })
18192 .ok();
18193 });
18194 }
18195 }
18196
18197 fn fold_at_level(
18198 &mut self,
18199 fold_at: &FoldAtLevel,
18200 window: &mut Window,
18201 cx: &mut Context<Self>,
18202 ) {
18203 if !self.buffer.read(cx).is_singleton() {
18204 return;
18205 }
18206
18207 let fold_at_level = fold_at.0;
18208 let snapshot = self.buffer.read(cx).snapshot(cx);
18209 let mut to_fold = Vec::new();
18210 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18211
18212 let row_ranges_to_keep: Vec<Range<u32>> = self
18213 .selections
18214 .all::<Point>(cx)
18215 .into_iter()
18216 .map(|sel| sel.start.row..sel.end.row)
18217 .collect();
18218
18219 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18220 while start_row < end_row {
18221 match self
18222 .snapshot(window, cx)
18223 .crease_for_buffer_row(MultiBufferRow(start_row))
18224 {
18225 Some(crease) => {
18226 let nested_start_row = crease.range().start.row + 1;
18227 let nested_end_row = crease.range().end.row;
18228
18229 if current_level < fold_at_level {
18230 stack.push((nested_start_row, nested_end_row, current_level + 1));
18231 } else if current_level == fold_at_level {
18232 // Fold iff there is no selection completely contained within the fold region
18233 if !row_ranges_to_keep.iter().any(|selection| {
18234 selection.end >= nested_start_row
18235 && selection.start <= nested_end_row
18236 }) {
18237 to_fold.push(crease);
18238 }
18239 }
18240
18241 start_row = nested_end_row + 1;
18242 }
18243 None => start_row += 1,
18244 }
18245 }
18246 }
18247
18248 self.fold_creases(to_fold, true, window, cx);
18249 }
18250
18251 pub fn fold_at_level_1(
18252 &mut self,
18253 _: &actions::FoldAtLevel1,
18254 window: &mut Window,
18255 cx: &mut Context<Self>,
18256 ) {
18257 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18258 }
18259
18260 pub fn fold_at_level_2(
18261 &mut self,
18262 _: &actions::FoldAtLevel2,
18263 window: &mut Window,
18264 cx: &mut Context<Self>,
18265 ) {
18266 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18267 }
18268
18269 pub fn fold_at_level_3(
18270 &mut self,
18271 _: &actions::FoldAtLevel3,
18272 window: &mut Window,
18273 cx: &mut Context<Self>,
18274 ) {
18275 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18276 }
18277
18278 pub fn fold_at_level_4(
18279 &mut self,
18280 _: &actions::FoldAtLevel4,
18281 window: &mut Window,
18282 cx: &mut Context<Self>,
18283 ) {
18284 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18285 }
18286
18287 pub fn fold_at_level_5(
18288 &mut self,
18289 _: &actions::FoldAtLevel5,
18290 window: &mut Window,
18291 cx: &mut Context<Self>,
18292 ) {
18293 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18294 }
18295
18296 pub fn fold_at_level_6(
18297 &mut self,
18298 _: &actions::FoldAtLevel6,
18299 window: &mut Window,
18300 cx: &mut Context<Self>,
18301 ) {
18302 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18303 }
18304
18305 pub fn fold_at_level_7(
18306 &mut self,
18307 _: &actions::FoldAtLevel7,
18308 window: &mut Window,
18309 cx: &mut Context<Self>,
18310 ) {
18311 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18312 }
18313
18314 pub fn fold_at_level_8(
18315 &mut self,
18316 _: &actions::FoldAtLevel8,
18317 window: &mut Window,
18318 cx: &mut Context<Self>,
18319 ) {
18320 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18321 }
18322
18323 pub fn fold_at_level_9(
18324 &mut self,
18325 _: &actions::FoldAtLevel9,
18326 window: &mut Window,
18327 cx: &mut Context<Self>,
18328 ) {
18329 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18330 }
18331
18332 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18333 if self.buffer.read(cx).is_singleton() {
18334 let mut fold_ranges = Vec::new();
18335 let snapshot = self.buffer.read(cx).snapshot(cx);
18336
18337 for row in 0..snapshot.max_row().0 {
18338 if let Some(foldable_range) = self
18339 .snapshot(window, cx)
18340 .crease_for_buffer_row(MultiBufferRow(row))
18341 {
18342 fold_ranges.push(foldable_range);
18343 }
18344 }
18345
18346 self.fold_creases(fold_ranges, true, window, cx);
18347 } else {
18348 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18349 editor
18350 .update_in(cx, |editor, _, cx| {
18351 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18352 editor.fold_buffer(buffer_id, cx);
18353 }
18354 })
18355 .ok();
18356 });
18357 }
18358 }
18359
18360 pub fn fold_function_bodies(
18361 &mut self,
18362 _: &actions::FoldFunctionBodies,
18363 window: &mut Window,
18364 cx: &mut Context<Self>,
18365 ) {
18366 let snapshot = self.buffer.read(cx).snapshot(cx);
18367
18368 let ranges = snapshot
18369 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18370 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18371 .collect::<Vec<_>>();
18372
18373 let creases = ranges
18374 .into_iter()
18375 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18376 .collect();
18377
18378 self.fold_creases(creases, true, window, cx);
18379 }
18380
18381 pub fn fold_recursive(
18382 &mut self,
18383 _: &actions::FoldRecursive,
18384 window: &mut Window,
18385 cx: &mut Context<Self>,
18386 ) {
18387 let mut to_fold = Vec::new();
18388 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18389 let selections = self.selections.all_adjusted(cx);
18390
18391 for selection in selections {
18392 let range = selection.range().sorted();
18393 let buffer_start_row = range.start.row;
18394
18395 if range.start.row != range.end.row {
18396 let mut found = false;
18397 for row in range.start.row..=range.end.row {
18398 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18399 found = true;
18400 to_fold.push(crease);
18401 }
18402 }
18403 if found {
18404 continue;
18405 }
18406 }
18407
18408 for row in (0..=range.start.row).rev() {
18409 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18410 if crease.range().end.row >= buffer_start_row {
18411 to_fold.push(crease);
18412 } else {
18413 break;
18414 }
18415 }
18416 }
18417 }
18418
18419 self.fold_creases(to_fold, true, window, cx);
18420 }
18421
18422 pub fn fold_at(
18423 &mut self,
18424 buffer_row: MultiBufferRow,
18425 window: &mut Window,
18426 cx: &mut Context<Self>,
18427 ) {
18428 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18429
18430 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18431 let autoscroll = self
18432 .selections
18433 .all::<Point>(cx)
18434 .iter()
18435 .any(|selection| crease.range().overlaps(&selection.range()));
18436
18437 self.fold_creases(vec![crease], autoscroll, window, cx);
18438 }
18439 }
18440
18441 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18442 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18443 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18444 let buffer = display_map.buffer_snapshot();
18445 let selections = self.selections.all::<Point>(cx);
18446 let ranges = selections
18447 .iter()
18448 .map(|s| {
18449 let range = s.display_range(&display_map).sorted();
18450 let mut start = range.start.to_point(&display_map);
18451 let mut end = range.end.to_point(&display_map);
18452 start.column = 0;
18453 end.column = buffer.line_len(MultiBufferRow(end.row));
18454 start..end
18455 })
18456 .collect::<Vec<_>>();
18457
18458 self.unfold_ranges(&ranges, true, true, cx);
18459 } else {
18460 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18461 let buffer_ids = self
18462 .selections
18463 .disjoint_anchor_ranges()
18464 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18465 .collect::<HashSet<_>>();
18466 for buffer_id in buffer_ids {
18467 self.unfold_buffer(buffer_id, cx);
18468 }
18469 }
18470 }
18471
18472 pub fn unfold_recursive(
18473 &mut self,
18474 _: &UnfoldRecursive,
18475 _window: &mut Window,
18476 cx: &mut Context<Self>,
18477 ) {
18478 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18479 let selections = self.selections.all::<Point>(cx);
18480 let ranges = selections
18481 .iter()
18482 .map(|s| {
18483 let mut range = s.display_range(&display_map).sorted();
18484 *range.start.column_mut() = 0;
18485 *range.end.column_mut() = display_map.line_len(range.end.row());
18486 let start = range.start.to_point(&display_map);
18487 let end = range.end.to_point(&display_map);
18488 start..end
18489 })
18490 .collect::<Vec<_>>();
18491
18492 self.unfold_ranges(&ranges, true, true, cx);
18493 }
18494
18495 pub fn unfold_at(
18496 &mut self,
18497 buffer_row: MultiBufferRow,
18498 _window: &mut Window,
18499 cx: &mut Context<Self>,
18500 ) {
18501 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18502
18503 let intersection_range = Point::new(buffer_row.0, 0)
18504 ..Point::new(
18505 buffer_row.0,
18506 display_map.buffer_snapshot().line_len(buffer_row),
18507 );
18508
18509 let autoscroll = self
18510 .selections
18511 .all::<Point>(cx)
18512 .iter()
18513 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18514
18515 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18516 }
18517
18518 pub fn unfold_all(
18519 &mut self,
18520 _: &actions::UnfoldAll,
18521 _window: &mut Window,
18522 cx: &mut Context<Self>,
18523 ) {
18524 if self.buffer.read(cx).is_singleton() {
18525 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18526 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18527 } else {
18528 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18529 editor
18530 .update(cx, |editor, cx| {
18531 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18532 editor.unfold_buffer(buffer_id, cx);
18533 }
18534 })
18535 .ok();
18536 });
18537 }
18538 }
18539
18540 pub fn fold_selected_ranges(
18541 &mut self,
18542 _: &FoldSelectedRanges,
18543 window: &mut Window,
18544 cx: &mut Context<Self>,
18545 ) {
18546 let selections = self.selections.all_adjusted(cx);
18547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18548 let ranges = selections
18549 .into_iter()
18550 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18551 .collect::<Vec<_>>();
18552 self.fold_creases(ranges, true, window, cx);
18553 }
18554
18555 pub fn fold_ranges<T: ToOffset + Clone>(
18556 &mut self,
18557 ranges: Vec<Range<T>>,
18558 auto_scroll: bool,
18559 window: &mut Window,
18560 cx: &mut Context<Self>,
18561 ) {
18562 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18563 let ranges = ranges
18564 .into_iter()
18565 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18566 .collect::<Vec<_>>();
18567 self.fold_creases(ranges, auto_scroll, window, cx);
18568 }
18569
18570 pub fn fold_creases<T: ToOffset + Clone>(
18571 &mut self,
18572 creases: Vec<Crease<T>>,
18573 auto_scroll: bool,
18574 _window: &mut Window,
18575 cx: &mut Context<Self>,
18576 ) {
18577 if creases.is_empty() {
18578 return;
18579 }
18580
18581 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18582
18583 if auto_scroll {
18584 self.request_autoscroll(Autoscroll::fit(), cx);
18585 }
18586
18587 cx.notify();
18588
18589 self.scrollbar_marker_state.dirty = true;
18590 self.folds_did_change(cx);
18591 }
18592
18593 /// Removes any folds whose ranges intersect any of the given ranges.
18594 pub fn unfold_ranges<T: ToOffset + Clone>(
18595 &mut self,
18596 ranges: &[Range<T>],
18597 inclusive: bool,
18598 auto_scroll: bool,
18599 cx: &mut Context<Self>,
18600 ) {
18601 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18602 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18603 });
18604 self.folds_did_change(cx);
18605 }
18606
18607 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18608 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18609 return;
18610 }
18611 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18612 self.display_map.update(cx, |display_map, cx| {
18613 display_map.fold_buffers([buffer_id], cx)
18614 });
18615 cx.emit(EditorEvent::BufferFoldToggled {
18616 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18617 folded: true,
18618 });
18619 cx.notify();
18620 }
18621
18622 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18623 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18624 return;
18625 }
18626 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18627 self.display_map.update(cx, |display_map, cx| {
18628 display_map.unfold_buffers([buffer_id], cx);
18629 });
18630 cx.emit(EditorEvent::BufferFoldToggled {
18631 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18632 folded: false,
18633 });
18634 cx.notify();
18635 }
18636
18637 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18638 self.display_map.read(cx).is_buffer_folded(buffer)
18639 }
18640
18641 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18642 self.display_map.read(cx).folded_buffers()
18643 }
18644
18645 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18646 self.display_map.update(cx, |display_map, cx| {
18647 display_map.disable_header_for_buffer(buffer_id, cx);
18648 });
18649 cx.notify();
18650 }
18651
18652 /// Removes any folds with the given ranges.
18653 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18654 &mut self,
18655 ranges: &[Range<T>],
18656 type_id: TypeId,
18657 auto_scroll: bool,
18658 cx: &mut Context<Self>,
18659 ) {
18660 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18661 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18662 });
18663 self.folds_did_change(cx);
18664 }
18665
18666 fn remove_folds_with<T: ToOffset + Clone>(
18667 &mut self,
18668 ranges: &[Range<T>],
18669 auto_scroll: bool,
18670 cx: &mut Context<Self>,
18671 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18672 ) {
18673 if ranges.is_empty() {
18674 return;
18675 }
18676
18677 let mut buffers_affected = HashSet::default();
18678 let multi_buffer = self.buffer().read(cx);
18679 for range in ranges {
18680 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18681 buffers_affected.insert(buffer.read(cx).remote_id());
18682 };
18683 }
18684
18685 self.display_map.update(cx, update);
18686
18687 if auto_scroll {
18688 self.request_autoscroll(Autoscroll::fit(), cx);
18689 }
18690
18691 cx.notify();
18692 self.scrollbar_marker_state.dirty = true;
18693 self.active_indent_guides_state.dirty = true;
18694 }
18695
18696 pub fn update_renderer_widths(
18697 &mut self,
18698 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18699 cx: &mut Context<Self>,
18700 ) -> bool {
18701 self.display_map
18702 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18703 }
18704
18705 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18706 self.display_map.read(cx).fold_placeholder.clone()
18707 }
18708
18709 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18710 self.buffer.update(cx, |buffer, cx| {
18711 buffer.set_all_diff_hunks_expanded(cx);
18712 });
18713 }
18714
18715 pub fn expand_all_diff_hunks(
18716 &mut self,
18717 _: &ExpandAllDiffHunks,
18718 _window: &mut Window,
18719 cx: &mut Context<Self>,
18720 ) {
18721 self.buffer.update(cx, |buffer, cx| {
18722 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18723 });
18724 }
18725
18726 pub fn toggle_selected_diff_hunks(
18727 &mut self,
18728 _: &ToggleSelectedDiffHunks,
18729 _window: &mut Window,
18730 cx: &mut Context<Self>,
18731 ) {
18732 let ranges: Vec<_> = self
18733 .selections
18734 .disjoint_anchors()
18735 .iter()
18736 .map(|s| s.range())
18737 .collect();
18738 self.toggle_diff_hunks_in_ranges(ranges, cx);
18739 }
18740
18741 pub fn diff_hunks_in_ranges<'a>(
18742 &'a self,
18743 ranges: &'a [Range<Anchor>],
18744 buffer: &'a MultiBufferSnapshot,
18745 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18746 ranges.iter().flat_map(move |range| {
18747 let end_excerpt_id = range.end.excerpt_id;
18748 let range = range.to_point(buffer);
18749 let mut peek_end = range.end;
18750 if range.end.row < buffer.max_row().0 {
18751 peek_end = Point::new(range.end.row + 1, 0);
18752 }
18753 buffer
18754 .diff_hunks_in_range(range.start..peek_end)
18755 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18756 })
18757 }
18758
18759 pub fn has_stageable_diff_hunks_in_ranges(
18760 &self,
18761 ranges: &[Range<Anchor>],
18762 snapshot: &MultiBufferSnapshot,
18763 ) -> bool {
18764 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18765 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18766 }
18767
18768 pub fn toggle_staged_selected_diff_hunks(
18769 &mut self,
18770 _: &::git::ToggleStaged,
18771 _: &mut Window,
18772 cx: &mut Context<Self>,
18773 ) {
18774 let snapshot = self.buffer.read(cx).snapshot(cx);
18775 let ranges: Vec<_> = self
18776 .selections
18777 .disjoint_anchors()
18778 .iter()
18779 .map(|s| s.range())
18780 .collect();
18781 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18782 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18783 }
18784
18785 pub fn set_render_diff_hunk_controls(
18786 &mut self,
18787 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18788 cx: &mut Context<Self>,
18789 ) {
18790 self.render_diff_hunk_controls = render_diff_hunk_controls;
18791 cx.notify();
18792 }
18793
18794 pub fn stage_and_next(
18795 &mut self,
18796 _: &::git::StageAndNext,
18797 window: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) {
18800 self.do_stage_or_unstage_and_next(true, window, cx);
18801 }
18802
18803 pub fn unstage_and_next(
18804 &mut self,
18805 _: &::git::UnstageAndNext,
18806 window: &mut Window,
18807 cx: &mut Context<Self>,
18808 ) {
18809 self.do_stage_or_unstage_and_next(false, window, cx);
18810 }
18811
18812 pub fn stage_or_unstage_diff_hunks(
18813 &mut self,
18814 stage: bool,
18815 ranges: Vec<Range<Anchor>>,
18816 cx: &mut Context<Self>,
18817 ) {
18818 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18819 cx.spawn(async move |this, cx| {
18820 task.await?;
18821 this.update(cx, |this, cx| {
18822 let snapshot = this.buffer.read(cx).snapshot(cx);
18823 let chunk_by = this
18824 .diff_hunks_in_ranges(&ranges, &snapshot)
18825 .chunk_by(|hunk| hunk.buffer_id);
18826 for (buffer_id, hunks) in &chunk_by {
18827 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18828 }
18829 })
18830 })
18831 .detach_and_log_err(cx);
18832 }
18833
18834 fn save_buffers_for_ranges_if_needed(
18835 &mut self,
18836 ranges: &[Range<Anchor>],
18837 cx: &mut Context<Editor>,
18838 ) -> Task<Result<()>> {
18839 let multibuffer = self.buffer.read(cx);
18840 let snapshot = multibuffer.read(cx);
18841 let buffer_ids: HashSet<_> = ranges
18842 .iter()
18843 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18844 .collect();
18845 drop(snapshot);
18846
18847 let mut buffers = HashSet::default();
18848 for buffer_id in buffer_ids {
18849 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18850 let buffer = buffer_entity.read(cx);
18851 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18852 {
18853 buffers.insert(buffer_entity);
18854 }
18855 }
18856 }
18857
18858 if let Some(project) = &self.project {
18859 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18860 } else {
18861 Task::ready(Ok(()))
18862 }
18863 }
18864
18865 fn do_stage_or_unstage_and_next(
18866 &mut self,
18867 stage: bool,
18868 window: &mut Window,
18869 cx: &mut Context<Self>,
18870 ) {
18871 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18872
18873 if ranges.iter().any(|range| range.start != range.end) {
18874 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18875 return;
18876 }
18877
18878 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18879 let snapshot = self.snapshot(window, cx);
18880 let position = self.selections.newest::<Point>(cx).head();
18881 let mut row = snapshot
18882 .buffer_snapshot()
18883 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18884 .find(|hunk| hunk.row_range.start.0 > position.row)
18885 .map(|hunk| hunk.row_range.start);
18886
18887 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18888 // Outside of the project diff editor, wrap around to the beginning.
18889 if !all_diff_hunks_expanded {
18890 row = row.or_else(|| {
18891 snapshot
18892 .buffer_snapshot()
18893 .diff_hunks_in_range(Point::zero()..position)
18894 .find(|hunk| hunk.row_range.end.0 < position.row)
18895 .map(|hunk| hunk.row_range.start)
18896 });
18897 }
18898
18899 if let Some(row) = row {
18900 let destination = Point::new(row.0, 0);
18901 let autoscroll = Autoscroll::center();
18902
18903 self.unfold_ranges(&[destination..destination], false, false, cx);
18904 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18905 s.select_ranges([destination..destination]);
18906 });
18907 }
18908 }
18909
18910 fn do_stage_or_unstage(
18911 &self,
18912 stage: bool,
18913 buffer_id: BufferId,
18914 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18915 cx: &mut App,
18916 ) -> Option<()> {
18917 let project = self.project()?;
18918 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18919 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18920 let buffer_snapshot = buffer.read(cx).snapshot();
18921 let file_exists = buffer_snapshot
18922 .file()
18923 .is_some_and(|file| file.disk_state().exists());
18924 diff.update(cx, |diff, cx| {
18925 diff.stage_or_unstage_hunks(
18926 stage,
18927 &hunks
18928 .map(|hunk| buffer_diff::DiffHunk {
18929 buffer_range: hunk.buffer_range,
18930 diff_base_byte_range: hunk.diff_base_byte_range,
18931 secondary_status: hunk.secondary_status,
18932 range: Point::zero()..Point::zero(), // unused
18933 })
18934 .collect::<Vec<_>>(),
18935 &buffer_snapshot,
18936 file_exists,
18937 cx,
18938 )
18939 });
18940 None
18941 }
18942
18943 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18944 let ranges: Vec<_> = self
18945 .selections
18946 .disjoint_anchors()
18947 .iter()
18948 .map(|s| s.range())
18949 .collect();
18950 self.buffer
18951 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18952 }
18953
18954 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18955 self.buffer.update(cx, |buffer, cx| {
18956 let ranges = vec![Anchor::min()..Anchor::max()];
18957 if !buffer.all_diff_hunks_expanded()
18958 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18959 {
18960 buffer.collapse_diff_hunks(ranges, cx);
18961 true
18962 } else {
18963 false
18964 }
18965 })
18966 }
18967
18968 fn toggle_diff_hunks_in_ranges(
18969 &mut self,
18970 ranges: Vec<Range<Anchor>>,
18971 cx: &mut Context<Editor>,
18972 ) {
18973 self.buffer.update(cx, |buffer, cx| {
18974 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18975 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18976 })
18977 }
18978
18979 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18980 self.buffer.update(cx, |buffer, cx| {
18981 let snapshot = buffer.snapshot(cx);
18982 let excerpt_id = range.end.excerpt_id;
18983 let point_range = range.to_point(&snapshot);
18984 let expand = !buffer.single_hunk_is_expanded(range, cx);
18985 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18986 })
18987 }
18988
18989 pub(crate) fn apply_all_diff_hunks(
18990 &mut self,
18991 _: &ApplyAllDiffHunks,
18992 window: &mut Window,
18993 cx: &mut Context<Self>,
18994 ) {
18995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18996
18997 let buffers = self.buffer.read(cx).all_buffers();
18998 for branch_buffer in buffers {
18999 branch_buffer.update(cx, |branch_buffer, cx| {
19000 branch_buffer.merge_into_base(Vec::new(), cx);
19001 });
19002 }
19003
19004 if let Some(project) = self.project.clone() {
19005 self.save(
19006 SaveOptions {
19007 format: true,
19008 autosave: false,
19009 },
19010 project,
19011 window,
19012 cx,
19013 )
19014 .detach_and_log_err(cx);
19015 }
19016 }
19017
19018 pub(crate) fn apply_selected_diff_hunks(
19019 &mut self,
19020 _: &ApplyDiffHunk,
19021 window: &mut Window,
19022 cx: &mut Context<Self>,
19023 ) {
19024 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19025 let snapshot = self.snapshot(window, cx);
19026 let hunks = snapshot.hunks_for_ranges(
19027 self.selections
19028 .all(cx)
19029 .into_iter()
19030 .map(|selection| selection.range()),
19031 );
19032 let mut ranges_by_buffer = HashMap::default();
19033 self.transact(window, cx, |editor, _window, cx| {
19034 for hunk in hunks {
19035 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19036 ranges_by_buffer
19037 .entry(buffer.clone())
19038 .or_insert_with(Vec::new)
19039 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19040 }
19041 }
19042
19043 for (buffer, ranges) in ranges_by_buffer {
19044 buffer.update(cx, |buffer, cx| {
19045 buffer.merge_into_base(ranges, cx);
19046 });
19047 }
19048 });
19049
19050 if let Some(project) = self.project.clone() {
19051 self.save(
19052 SaveOptions {
19053 format: true,
19054 autosave: false,
19055 },
19056 project,
19057 window,
19058 cx,
19059 )
19060 .detach_and_log_err(cx);
19061 }
19062 }
19063
19064 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19065 if hovered != self.gutter_hovered {
19066 self.gutter_hovered = hovered;
19067 cx.notify();
19068 }
19069 }
19070
19071 pub fn insert_blocks(
19072 &mut self,
19073 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19074 autoscroll: Option<Autoscroll>,
19075 cx: &mut Context<Self>,
19076 ) -> Vec<CustomBlockId> {
19077 let blocks = self
19078 .display_map
19079 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19080 if let Some(autoscroll) = autoscroll {
19081 self.request_autoscroll(autoscroll, cx);
19082 }
19083 cx.notify();
19084 blocks
19085 }
19086
19087 pub fn resize_blocks(
19088 &mut self,
19089 heights: HashMap<CustomBlockId, u32>,
19090 autoscroll: Option<Autoscroll>,
19091 cx: &mut Context<Self>,
19092 ) {
19093 self.display_map
19094 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19095 if let Some(autoscroll) = autoscroll {
19096 self.request_autoscroll(autoscroll, cx);
19097 }
19098 cx.notify();
19099 }
19100
19101 pub fn replace_blocks(
19102 &mut self,
19103 renderers: HashMap<CustomBlockId, RenderBlock>,
19104 autoscroll: Option<Autoscroll>,
19105 cx: &mut Context<Self>,
19106 ) {
19107 self.display_map
19108 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19109 if let Some(autoscroll) = autoscroll {
19110 self.request_autoscroll(autoscroll, cx);
19111 }
19112 cx.notify();
19113 }
19114
19115 pub fn remove_blocks(
19116 &mut self,
19117 block_ids: HashSet<CustomBlockId>,
19118 autoscroll: Option<Autoscroll>,
19119 cx: &mut Context<Self>,
19120 ) {
19121 self.display_map.update(cx, |display_map, cx| {
19122 display_map.remove_blocks(block_ids, cx)
19123 });
19124 if let Some(autoscroll) = autoscroll {
19125 self.request_autoscroll(autoscroll, cx);
19126 }
19127 cx.notify();
19128 }
19129
19130 pub fn row_for_block(
19131 &self,
19132 block_id: CustomBlockId,
19133 cx: &mut Context<Self>,
19134 ) -> Option<DisplayRow> {
19135 self.display_map
19136 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19137 }
19138
19139 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19140 self.focused_block = Some(focused_block);
19141 }
19142
19143 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19144 self.focused_block.take()
19145 }
19146
19147 pub fn insert_creases(
19148 &mut self,
19149 creases: impl IntoIterator<Item = Crease<Anchor>>,
19150 cx: &mut Context<Self>,
19151 ) -> Vec<CreaseId> {
19152 self.display_map
19153 .update(cx, |map, cx| map.insert_creases(creases, cx))
19154 }
19155
19156 pub fn remove_creases(
19157 &mut self,
19158 ids: impl IntoIterator<Item = CreaseId>,
19159 cx: &mut Context<Self>,
19160 ) -> Vec<(CreaseId, Range<Anchor>)> {
19161 self.display_map
19162 .update(cx, |map, cx| map.remove_creases(ids, cx))
19163 }
19164
19165 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19166 self.display_map
19167 .update(cx, |map, cx| map.snapshot(cx))
19168 .longest_row()
19169 }
19170
19171 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19172 self.display_map
19173 .update(cx, |map, cx| map.snapshot(cx))
19174 .max_point()
19175 }
19176
19177 pub fn text(&self, cx: &App) -> String {
19178 self.buffer.read(cx).read(cx).text()
19179 }
19180
19181 pub fn is_empty(&self, cx: &App) -> bool {
19182 self.buffer.read(cx).read(cx).is_empty()
19183 }
19184
19185 pub fn text_option(&self, cx: &App) -> Option<String> {
19186 let text = self.text(cx);
19187 let text = text.trim();
19188
19189 if text.is_empty() {
19190 return None;
19191 }
19192
19193 Some(text.to_string())
19194 }
19195
19196 pub fn set_text(
19197 &mut self,
19198 text: impl Into<Arc<str>>,
19199 window: &mut Window,
19200 cx: &mut Context<Self>,
19201 ) {
19202 self.transact(window, cx, |this, _, cx| {
19203 this.buffer
19204 .read(cx)
19205 .as_singleton()
19206 .expect("you can only call set_text on editors for singleton buffers")
19207 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19208 });
19209 }
19210
19211 pub fn display_text(&self, cx: &mut App) -> String {
19212 self.display_map
19213 .update(cx, |map, cx| map.snapshot(cx))
19214 .text()
19215 }
19216
19217 fn create_minimap(
19218 &self,
19219 minimap_settings: MinimapSettings,
19220 window: &mut Window,
19221 cx: &mut Context<Self>,
19222 ) -> Option<Entity<Self>> {
19223 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19224 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19225 }
19226
19227 fn initialize_new_minimap(
19228 &self,
19229 minimap_settings: MinimapSettings,
19230 window: &mut Window,
19231 cx: &mut Context<Self>,
19232 ) -> Entity<Self> {
19233 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19234
19235 let mut minimap = Editor::new_internal(
19236 EditorMode::Minimap {
19237 parent: cx.weak_entity(),
19238 },
19239 self.buffer.clone(),
19240 None,
19241 Some(self.display_map.clone()),
19242 window,
19243 cx,
19244 );
19245 minimap.scroll_manager.clone_state(&self.scroll_manager);
19246 minimap.set_text_style_refinement(TextStyleRefinement {
19247 font_size: Some(MINIMAP_FONT_SIZE),
19248 font_weight: Some(MINIMAP_FONT_WEIGHT),
19249 ..Default::default()
19250 });
19251 minimap.update_minimap_configuration(minimap_settings, cx);
19252 cx.new(|_| minimap)
19253 }
19254
19255 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19256 let current_line_highlight = minimap_settings
19257 .current_line_highlight
19258 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19259 self.set_current_line_highlight(Some(current_line_highlight));
19260 }
19261
19262 pub fn minimap(&self) -> Option<&Entity<Self>> {
19263 self.minimap
19264 .as_ref()
19265 .filter(|_| self.minimap_visibility.visible())
19266 }
19267
19268 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19269 let mut wrap_guides = smallvec![];
19270
19271 if self.show_wrap_guides == Some(false) {
19272 return wrap_guides;
19273 }
19274
19275 let settings = self.buffer.read(cx).language_settings(cx);
19276 if settings.show_wrap_guides {
19277 match self.soft_wrap_mode(cx) {
19278 SoftWrap::Column(soft_wrap) => {
19279 wrap_guides.push((soft_wrap as usize, true));
19280 }
19281 SoftWrap::Bounded(soft_wrap) => {
19282 wrap_guides.push((soft_wrap as usize, true));
19283 }
19284 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19285 }
19286 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19287 }
19288
19289 wrap_guides
19290 }
19291
19292 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19293 let settings = self.buffer.read(cx).language_settings(cx);
19294 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19295 match mode {
19296 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19297 SoftWrap::None
19298 }
19299 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19300 language_settings::SoftWrap::PreferredLineLength => {
19301 SoftWrap::Column(settings.preferred_line_length)
19302 }
19303 language_settings::SoftWrap::Bounded => {
19304 SoftWrap::Bounded(settings.preferred_line_length)
19305 }
19306 }
19307 }
19308
19309 pub fn set_soft_wrap_mode(
19310 &mut self,
19311 mode: language_settings::SoftWrap,
19312
19313 cx: &mut Context<Self>,
19314 ) {
19315 self.soft_wrap_mode_override = Some(mode);
19316 cx.notify();
19317 }
19318
19319 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19320 self.hard_wrap = hard_wrap;
19321 cx.notify();
19322 }
19323
19324 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19325 self.text_style_refinement = Some(style);
19326 }
19327
19328 /// called by the Element so we know what style we were most recently rendered with.
19329 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19330 // We intentionally do not inform the display map about the minimap style
19331 // so that wrapping is not recalculated and stays consistent for the editor
19332 // and its linked minimap.
19333 if !self.mode.is_minimap() {
19334 let font = style.text.font();
19335 let font_size = style.text.font_size.to_pixels(window.rem_size());
19336 let display_map = self
19337 .placeholder_display_map
19338 .as_ref()
19339 .filter(|_| self.is_empty(cx))
19340 .unwrap_or(&self.display_map);
19341
19342 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19343 }
19344 self.style = Some(style);
19345 }
19346
19347 pub fn style(&self) -> Option<&EditorStyle> {
19348 self.style.as_ref()
19349 }
19350
19351 // Called by the element. This method is not designed to be called outside of the editor
19352 // element's layout code because it does not notify when rewrapping is computed synchronously.
19353 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19354 if self.is_empty(cx) {
19355 self.placeholder_display_map
19356 .as_ref()
19357 .map_or(false, |display_map| {
19358 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19359 })
19360 } else {
19361 self.display_map
19362 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19363 }
19364 }
19365
19366 pub fn set_soft_wrap(&mut self) {
19367 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19368 }
19369
19370 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19371 if self.soft_wrap_mode_override.is_some() {
19372 self.soft_wrap_mode_override.take();
19373 } else {
19374 let soft_wrap = match self.soft_wrap_mode(cx) {
19375 SoftWrap::GitDiff => return,
19376 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19377 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19378 language_settings::SoftWrap::None
19379 }
19380 };
19381 self.soft_wrap_mode_override = Some(soft_wrap);
19382 }
19383 cx.notify();
19384 }
19385
19386 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19387 let Some(workspace) = self.workspace() else {
19388 return;
19389 };
19390 let fs = workspace.read(cx).app_state().fs.clone();
19391 let current_show = TabBarSettings::get_global(cx).show;
19392 update_settings_file(fs, cx, move |setting, _| {
19393 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19394 });
19395 }
19396
19397 pub fn toggle_indent_guides(
19398 &mut self,
19399 _: &ToggleIndentGuides,
19400 _: &mut Window,
19401 cx: &mut Context<Self>,
19402 ) {
19403 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19404 self.buffer
19405 .read(cx)
19406 .language_settings(cx)
19407 .indent_guides
19408 .enabled
19409 });
19410 self.show_indent_guides = Some(!currently_enabled);
19411 cx.notify();
19412 }
19413
19414 fn should_show_indent_guides(&self) -> Option<bool> {
19415 self.show_indent_guides
19416 }
19417
19418 pub fn toggle_line_numbers(
19419 &mut self,
19420 _: &ToggleLineNumbers,
19421 _: &mut Window,
19422 cx: &mut Context<Self>,
19423 ) {
19424 let mut editor_settings = EditorSettings::get_global(cx).clone();
19425 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19426 EditorSettings::override_global(editor_settings, cx);
19427 }
19428
19429 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19430 if let Some(show_line_numbers) = self.show_line_numbers {
19431 return show_line_numbers;
19432 }
19433 EditorSettings::get_global(cx).gutter.line_numbers
19434 }
19435
19436 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19437 self.use_relative_line_numbers
19438 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19439 }
19440
19441 pub fn toggle_relative_line_numbers(
19442 &mut self,
19443 _: &ToggleRelativeLineNumbers,
19444 _: &mut Window,
19445 cx: &mut Context<Self>,
19446 ) {
19447 let is_relative = self.should_use_relative_line_numbers(cx);
19448 self.set_relative_line_number(Some(!is_relative), cx)
19449 }
19450
19451 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19452 self.use_relative_line_numbers = is_relative;
19453 cx.notify();
19454 }
19455
19456 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19457 self.show_gutter = show_gutter;
19458 cx.notify();
19459 }
19460
19461 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19462 self.show_scrollbars = ScrollbarAxes {
19463 horizontal: show,
19464 vertical: show,
19465 };
19466 cx.notify();
19467 }
19468
19469 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19470 self.show_scrollbars.vertical = show;
19471 cx.notify();
19472 }
19473
19474 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19475 self.show_scrollbars.horizontal = show;
19476 cx.notify();
19477 }
19478
19479 pub fn set_minimap_visibility(
19480 &mut self,
19481 minimap_visibility: MinimapVisibility,
19482 window: &mut Window,
19483 cx: &mut Context<Self>,
19484 ) {
19485 if self.minimap_visibility != minimap_visibility {
19486 if minimap_visibility.visible() && self.minimap.is_none() {
19487 let minimap_settings = EditorSettings::get_global(cx).minimap;
19488 self.minimap =
19489 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19490 }
19491 self.minimap_visibility = minimap_visibility;
19492 cx.notify();
19493 }
19494 }
19495
19496 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19497 self.set_show_scrollbars(false, cx);
19498 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19499 }
19500
19501 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19502 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19503 }
19504
19505 /// Normally the text in full mode and auto height editors is padded on the
19506 /// left side by roughly half a character width for improved hit testing.
19507 ///
19508 /// Use this method to disable this for cases where this is not wanted (e.g.
19509 /// if you want to align the editor text with some other text above or below)
19510 /// or if you want to add this padding to single-line editors.
19511 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19512 self.offset_content = offset_content;
19513 cx.notify();
19514 }
19515
19516 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19517 self.show_line_numbers = Some(show_line_numbers);
19518 cx.notify();
19519 }
19520
19521 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19522 self.disable_expand_excerpt_buttons = true;
19523 cx.notify();
19524 }
19525
19526 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19527 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19528 cx.notify();
19529 }
19530
19531 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19532 self.show_code_actions = Some(show_code_actions);
19533 cx.notify();
19534 }
19535
19536 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19537 self.show_runnables = Some(show_runnables);
19538 cx.notify();
19539 }
19540
19541 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19542 self.show_breakpoints = Some(show_breakpoints);
19543 cx.notify();
19544 }
19545
19546 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19547 if self.display_map.read(cx).masked != masked {
19548 self.display_map.update(cx, |map, _| map.masked = masked);
19549 }
19550 cx.notify()
19551 }
19552
19553 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19554 self.show_wrap_guides = Some(show_wrap_guides);
19555 cx.notify();
19556 }
19557
19558 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19559 self.show_indent_guides = Some(show_indent_guides);
19560 cx.notify();
19561 }
19562
19563 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19564 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19565 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19566 && let Some(dir) = file.abs_path(cx).parent()
19567 {
19568 return Some(dir.to_owned());
19569 }
19570 }
19571
19572 None
19573 }
19574
19575 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19576 self.active_excerpt(cx)?
19577 .1
19578 .read(cx)
19579 .file()
19580 .and_then(|f| f.as_local())
19581 }
19582
19583 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19584 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19585 let buffer = buffer.read(cx);
19586 if let Some(project_path) = buffer.project_path(cx) {
19587 let project = self.project()?.read(cx);
19588 project.absolute_path(&project_path, cx)
19589 } else {
19590 buffer
19591 .file()
19592 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19593 }
19594 })
19595 }
19596
19597 pub fn reveal_in_finder(
19598 &mut self,
19599 _: &RevealInFileManager,
19600 _window: &mut Window,
19601 cx: &mut Context<Self>,
19602 ) {
19603 if let Some(target) = self.target_file(cx) {
19604 cx.reveal_path(&target.abs_path(cx));
19605 }
19606 }
19607
19608 pub fn copy_path(
19609 &mut self,
19610 _: &zed_actions::workspace::CopyPath,
19611 _window: &mut Window,
19612 cx: &mut Context<Self>,
19613 ) {
19614 if let Some(path) = self.target_file_abs_path(cx)
19615 && let Some(path) = path.to_str()
19616 {
19617 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19618 } else {
19619 cx.propagate();
19620 }
19621 }
19622
19623 pub fn copy_relative_path(
19624 &mut self,
19625 _: &zed_actions::workspace::CopyRelativePath,
19626 _window: &mut Window,
19627 cx: &mut Context<Self>,
19628 ) {
19629 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19630 let project = self.project()?.read(cx);
19631 let path = buffer.read(cx).file()?.path();
19632 let path = path.display(project.path_style(cx));
19633 Some(path)
19634 }) {
19635 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19636 } else {
19637 cx.propagate();
19638 }
19639 }
19640
19641 /// Returns the project path for the editor's buffer, if any buffer is
19642 /// opened in the editor.
19643 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19644 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19645 buffer.read(cx).project_path(cx)
19646 } else {
19647 None
19648 }
19649 }
19650
19651 // Returns true if the editor handled a go-to-line request
19652 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19653 maybe!({
19654 let breakpoint_store = self.breakpoint_store.as_ref()?;
19655
19656 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19657 else {
19658 self.clear_row_highlights::<ActiveDebugLine>();
19659 return None;
19660 };
19661
19662 let position = active_stack_frame.position;
19663 let buffer_id = position.buffer_id?;
19664 let snapshot = self
19665 .project
19666 .as_ref()?
19667 .read(cx)
19668 .buffer_for_id(buffer_id, cx)?
19669 .read(cx)
19670 .snapshot();
19671
19672 let mut handled = false;
19673 for (id, ExcerptRange { context, .. }) in
19674 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19675 {
19676 if context.start.cmp(&position, &snapshot).is_ge()
19677 || context.end.cmp(&position, &snapshot).is_lt()
19678 {
19679 continue;
19680 }
19681 let snapshot = self.buffer.read(cx).snapshot(cx);
19682 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19683
19684 handled = true;
19685 self.clear_row_highlights::<ActiveDebugLine>();
19686
19687 self.go_to_line::<ActiveDebugLine>(
19688 multibuffer_anchor,
19689 Some(cx.theme().colors().editor_debugger_active_line_background),
19690 window,
19691 cx,
19692 );
19693
19694 cx.notify();
19695 }
19696
19697 handled.then_some(())
19698 })
19699 .is_some()
19700 }
19701
19702 pub fn copy_file_name_without_extension(
19703 &mut self,
19704 _: &CopyFileNameWithoutExtension,
19705 _: &mut Window,
19706 cx: &mut Context<Self>,
19707 ) {
19708 if let Some(file) = self.target_file(cx)
19709 && let Some(file_stem) = file.path().file_stem()
19710 {
19711 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19712 }
19713 }
19714
19715 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19716 if let Some(file) = self.target_file(cx)
19717 && let Some(name) = file.path().file_name()
19718 {
19719 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19720 }
19721 }
19722
19723 pub fn toggle_git_blame(
19724 &mut self,
19725 _: &::git::Blame,
19726 window: &mut Window,
19727 cx: &mut Context<Self>,
19728 ) {
19729 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19730
19731 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19732 self.start_git_blame(true, window, cx);
19733 }
19734
19735 cx.notify();
19736 }
19737
19738 pub fn toggle_git_blame_inline(
19739 &mut self,
19740 _: &ToggleGitBlameInline,
19741 window: &mut Window,
19742 cx: &mut Context<Self>,
19743 ) {
19744 self.toggle_git_blame_inline_internal(true, window, cx);
19745 cx.notify();
19746 }
19747
19748 pub fn open_git_blame_commit(
19749 &mut self,
19750 _: &OpenGitBlameCommit,
19751 window: &mut Window,
19752 cx: &mut Context<Self>,
19753 ) {
19754 self.open_git_blame_commit_internal(window, cx);
19755 }
19756
19757 fn open_git_blame_commit_internal(
19758 &mut self,
19759 window: &mut Window,
19760 cx: &mut Context<Self>,
19761 ) -> Option<()> {
19762 let blame = self.blame.as_ref()?;
19763 let snapshot = self.snapshot(window, cx);
19764 let cursor = self.selections.newest::<Point>(cx).head();
19765 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19766 let (_, blame_entry) = blame
19767 .update(cx, |blame, cx| {
19768 blame
19769 .blame_for_rows(
19770 &[RowInfo {
19771 buffer_id: Some(buffer.remote_id()),
19772 buffer_row: Some(point.row),
19773 ..Default::default()
19774 }],
19775 cx,
19776 )
19777 .next()
19778 })
19779 .flatten()?;
19780 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19781 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19782 let workspace = self.workspace()?.downgrade();
19783 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19784 None
19785 }
19786
19787 pub fn git_blame_inline_enabled(&self) -> bool {
19788 self.git_blame_inline_enabled
19789 }
19790
19791 pub fn toggle_selection_menu(
19792 &mut self,
19793 _: &ToggleSelectionMenu,
19794 _: &mut Window,
19795 cx: &mut Context<Self>,
19796 ) {
19797 self.show_selection_menu = self
19798 .show_selection_menu
19799 .map(|show_selections_menu| !show_selections_menu)
19800 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19801
19802 cx.notify();
19803 }
19804
19805 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19806 self.show_selection_menu
19807 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19808 }
19809
19810 fn start_git_blame(
19811 &mut self,
19812 user_triggered: bool,
19813 window: &mut Window,
19814 cx: &mut Context<Self>,
19815 ) {
19816 if let Some(project) = self.project() {
19817 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19818 && buffer.read(cx).file().is_none()
19819 {
19820 return;
19821 }
19822
19823 let focused = self.focus_handle(cx).contains_focused(window, cx);
19824
19825 let project = project.clone();
19826 let blame = cx
19827 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19828 self.blame_subscription =
19829 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19830 self.blame = Some(blame);
19831 }
19832 }
19833
19834 fn toggle_git_blame_inline_internal(
19835 &mut self,
19836 user_triggered: bool,
19837 window: &mut Window,
19838 cx: &mut Context<Self>,
19839 ) {
19840 if self.git_blame_inline_enabled {
19841 self.git_blame_inline_enabled = false;
19842 self.show_git_blame_inline = false;
19843 self.show_git_blame_inline_delay_task.take();
19844 } else {
19845 self.git_blame_inline_enabled = true;
19846 self.start_git_blame_inline(user_triggered, window, cx);
19847 }
19848
19849 cx.notify();
19850 }
19851
19852 fn start_git_blame_inline(
19853 &mut self,
19854 user_triggered: bool,
19855 window: &mut Window,
19856 cx: &mut Context<Self>,
19857 ) {
19858 self.start_git_blame(user_triggered, window, cx);
19859
19860 if ProjectSettings::get_global(cx)
19861 .git
19862 .inline_blame_delay()
19863 .is_some()
19864 {
19865 self.start_inline_blame_timer(window, cx);
19866 } else {
19867 self.show_git_blame_inline = true
19868 }
19869 }
19870
19871 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19872 self.blame.as_ref()
19873 }
19874
19875 pub fn show_git_blame_gutter(&self) -> bool {
19876 self.show_git_blame_gutter
19877 }
19878
19879 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19880 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19881 }
19882
19883 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19884 self.show_git_blame_inline
19885 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19886 && !self.newest_selection_head_on_empty_line(cx)
19887 && self.has_blame_entries(cx)
19888 }
19889
19890 fn has_blame_entries(&self, cx: &App) -> bool {
19891 self.blame()
19892 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19893 }
19894
19895 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19896 let cursor_anchor = self.selections.newest_anchor().head();
19897
19898 let snapshot = self.buffer.read(cx).snapshot(cx);
19899 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19900
19901 snapshot.line_len(buffer_row) == 0
19902 }
19903
19904 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19905 let buffer_and_selection = maybe!({
19906 let selection = self.selections.newest::<Point>(cx);
19907 let selection_range = selection.range();
19908
19909 let multi_buffer = self.buffer().read(cx);
19910 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19911 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19912
19913 let (buffer, range, _) = if selection.reversed {
19914 buffer_ranges.first()
19915 } else {
19916 buffer_ranges.last()
19917 }?;
19918
19919 let selection = text::ToPoint::to_point(&range.start, buffer).row
19920 ..text::ToPoint::to_point(&range.end, buffer).row;
19921 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19922 });
19923
19924 let Some((buffer, selection)) = buffer_and_selection else {
19925 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19926 };
19927
19928 let Some(project) = self.project() else {
19929 return Task::ready(Err(anyhow!("editor does not have project")));
19930 };
19931
19932 project.update(cx, |project, cx| {
19933 project.get_permalink_to_line(&buffer, selection, cx)
19934 })
19935 }
19936
19937 pub fn copy_permalink_to_line(
19938 &mut self,
19939 _: &CopyPermalinkToLine,
19940 window: &mut Window,
19941 cx: &mut Context<Self>,
19942 ) {
19943 let permalink_task = self.get_permalink_to_line(cx);
19944 let workspace = self.workspace();
19945
19946 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19947 Ok(permalink) => {
19948 cx.update(|_, cx| {
19949 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19950 })
19951 .ok();
19952 }
19953 Err(err) => {
19954 let message = format!("Failed to copy permalink: {err}");
19955
19956 anyhow::Result::<()>::Err(err).log_err();
19957
19958 if let Some(workspace) = workspace {
19959 workspace
19960 .update_in(cx, |workspace, _, cx| {
19961 struct CopyPermalinkToLine;
19962
19963 workspace.show_toast(
19964 Toast::new(
19965 NotificationId::unique::<CopyPermalinkToLine>(),
19966 message,
19967 ),
19968 cx,
19969 )
19970 })
19971 .ok();
19972 }
19973 }
19974 })
19975 .detach();
19976 }
19977
19978 pub fn copy_file_location(
19979 &mut self,
19980 _: &CopyFileLocation,
19981 _: &mut Window,
19982 cx: &mut Context<Self>,
19983 ) {
19984 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19985 if let Some(file) = self.target_file(cx) {
19986 let path = file.path().display(file.path_style(cx));
19987 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19988 }
19989 }
19990
19991 pub fn open_permalink_to_line(
19992 &mut self,
19993 _: &OpenPermalinkToLine,
19994 window: &mut Window,
19995 cx: &mut Context<Self>,
19996 ) {
19997 let permalink_task = self.get_permalink_to_line(cx);
19998 let workspace = self.workspace();
19999
20000 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20001 Ok(permalink) => {
20002 cx.update(|_, cx| {
20003 cx.open_url(permalink.as_ref());
20004 })
20005 .ok();
20006 }
20007 Err(err) => {
20008 let message = format!("Failed to open permalink: {err}");
20009
20010 anyhow::Result::<()>::Err(err).log_err();
20011
20012 if let Some(workspace) = workspace {
20013 workspace
20014 .update(cx, |workspace, cx| {
20015 struct OpenPermalinkToLine;
20016
20017 workspace.show_toast(
20018 Toast::new(
20019 NotificationId::unique::<OpenPermalinkToLine>(),
20020 message,
20021 ),
20022 cx,
20023 )
20024 })
20025 .ok();
20026 }
20027 }
20028 })
20029 .detach();
20030 }
20031
20032 pub fn insert_uuid_v4(
20033 &mut self,
20034 _: &InsertUuidV4,
20035 window: &mut Window,
20036 cx: &mut Context<Self>,
20037 ) {
20038 self.insert_uuid(UuidVersion::V4, window, cx);
20039 }
20040
20041 pub fn insert_uuid_v7(
20042 &mut self,
20043 _: &InsertUuidV7,
20044 window: &mut Window,
20045 cx: &mut Context<Self>,
20046 ) {
20047 self.insert_uuid(UuidVersion::V7, window, cx);
20048 }
20049
20050 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20052 self.transact(window, cx, |this, window, cx| {
20053 let edits = this
20054 .selections
20055 .all::<Point>(cx)
20056 .into_iter()
20057 .map(|selection| {
20058 let uuid = match version {
20059 UuidVersion::V4 => uuid::Uuid::new_v4(),
20060 UuidVersion::V7 => uuid::Uuid::now_v7(),
20061 };
20062
20063 (selection.range(), uuid.to_string())
20064 });
20065 this.edit(edits, cx);
20066 this.refresh_edit_prediction(true, false, window, cx);
20067 });
20068 }
20069
20070 pub fn open_selections_in_multibuffer(
20071 &mut self,
20072 _: &OpenSelectionsInMultibuffer,
20073 window: &mut Window,
20074 cx: &mut Context<Self>,
20075 ) {
20076 let multibuffer = self.buffer.read(cx);
20077
20078 let Some(buffer) = multibuffer.as_singleton() else {
20079 return;
20080 };
20081
20082 let Some(workspace) = self.workspace() else {
20083 return;
20084 };
20085
20086 let title = multibuffer.title(cx).to_string();
20087
20088 let locations = self
20089 .selections
20090 .all_anchors(cx)
20091 .iter()
20092 .map(|selection| {
20093 (
20094 buffer.clone(),
20095 (selection.start.text_anchor..selection.end.text_anchor)
20096 .to_point(buffer.read(cx)),
20097 )
20098 })
20099 .into_group_map();
20100
20101 cx.spawn_in(window, async move |_, cx| {
20102 workspace.update_in(cx, |workspace, window, cx| {
20103 Self::open_locations_in_multibuffer(
20104 workspace,
20105 locations,
20106 format!("Selections for '{title}'"),
20107 false,
20108 MultibufferSelectionMode::All,
20109 window,
20110 cx,
20111 );
20112 })
20113 })
20114 .detach();
20115 }
20116
20117 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20118 /// last highlight added will be used.
20119 ///
20120 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20121 pub fn highlight_rows<T: 'static>(
20122 &mut self,
20123 range: Range<Anchor>,
20124 color: Hsla,
20125 options: RowHighlightOptions,
20126 cx: &mut Context<Self>,
20127 ) {
20128 let snapshot = self.buffer().read(cx).snapshot(cx);
20129 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20130 let ix = row_highlights.binary_search_by(|highlight| {
20131 Ordering::Equal
20132 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20133 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20134 });
20135
20136 if let Err(mut ix) = ix {
20137 let index = post_inc(&mut self.highlight_order);
20138
20139 // If this range intersects with the preceding highlight, then merge it with
20140 // the preceding highlight. Otherwise insert a new highlight.
20141 let mut merged = false;
20142 if ix > 0 {
20143 let prev_highlight = &mut row_highlights[ix - 1];
20144 if prev_highlight
20145 .range
20146 .end
20147 .cmp(&range.start, &snapshot)
20148 .is_ge()
20149 {
20150 ix -= 1;
20151 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20152 prev_highlight.range.end = range.end;
20153 }
20154 merged = true;
20155 prev_highlight.index = index;
20156 prev_highlight.color = color;
20157 prev_highlight.options = options;
20158 }
20159 }
20160
20161 if !merged {
20162 row_highlights.insert(
20163 ix,
20164 RowHighlight {
20165 range,
20166 index,
20167 color,
20168 options,
20169 type_id: TypeId::of::<T>(),
20170 },
20171 );
20172 }
20173
20174 // If any of the following highlights intersect with this one, merge them.
20175 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20176 let highlight = &row_highlights[ix];
20177 if next_highlight
20178 .range
20179 .start
20180 .cmp(&highlight.range.end, &snapshot)
20181 .is_le()
20182 {
20183 if next_highlight
20184 .range
20185 .end
20186 .cmp(&highlight.range.end, &snapshot)
20187 .is_gt()
20188 {
20189 row_highlights[ix].range.end = next_highlight.range.end;
20190 }
20191 row_highlights.remove(ix + 1);
20192 } else {
20193 break;
20194 }
20195 }
20196 }
20197 }
20198
20199 /// Remove any highlighted row ranges of the given type that intersect the
20200 /// given ranges.
20201 pub fn remove_highlighted_rows<T: 'static>(
20202 &mut self,
20203 ranges_to_remove: Vec<Range<Anchor>>,
20204 cx: &mut Context<Self>,
20205 ) {
20206 let snapshot = self.buffer().read(cx).snapshot(cx);
20207 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20208 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20209 row_highlights.retain(|highlight| {
20210 while let Some(range_to_remove) = ranges_to_remove.peek() {
20211 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20212 Ordering::Less | Ordering::Equal => {
20213 ranges_to_remove.next();
20214 }
20215 Ordering::Greater => {
20216 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20217 Ordering::Less | Ordering::Equal => {
20218 return false;
20219 }
20220 Ordering::Greater => break,
20221 }
20222 }
20223 }
20224 }
20225
20226 true
20227 })
20228 }
20229
20230 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20231 pub fn clear_row_highlights<T: 'static>(&mut self) {
20232 self.highlighted_rows.remove(&TypeId::of::<T>());
20233 }
20234
20235 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20236 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20237 self.highlighted_rows
20238 .get(&TypeId::of::<T>())
20239 .map_or(&[] as &[_], |vec| vec.as_slice())
20240 .iter()
20241 .map(|highlight| (highlight.range.clone(), highlight.color))
20242 }
20243
20244 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20245 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20246 /// Allows to ignore certain kinds of highlights.
20247 pub fn highlighted_display_rows(
20248 &self,
20249 window: &mut Window,
20250 cx: &mut App,
20251 ) -> BTreeMap<DisplayRow, LineHighlight> {
20252 let snapshot = self.snapshot(window, cx);
20253 let mut used_highlight_orders = HashMap::default();
20254 self.highlighted_rows
20255 .iter()
20256 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20257 .fold(
20258 BTreeMap::<DisplayRow, LineHighlight>::new(),
20259 |mut unique_rows, highlight| {
20260 let start = highlight.range.start.to_display_point(&snapshot);
20261 let end = highlight.range.end.to_display_point(&snapshot);
20262 let start_row = start.row().0;
20263 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20264 && end.column() == 0
20265 {
20266 end.row().0.saturating_sub(1)
20267 } else {
20268 end.row().0
20269 };
20270 for row in start_row..=end_row {
20271 let used_index =
20272 used_highlight_orders.entry(row).or_insert(highlight.index);
20273 if highlight.index >= *used_index {
20274 *used_index = highlight.index;
20275 unique_rows.insert(
20276 DisplayRow(row),
20277 LineHighlight {
20278 include_gutter: highlight.options.include_gutter,
20279 border: None,
20280 background: highlight.color.into(),
20281 type_id: Some(highlight.type_id),
20282 },
20283 );
20284 }
20285 }
20286 unique_rows
20287 },
20288 )
20289 }
20290
20291 pub fn highlighted_display_row_for_autoscroll(
20292 &self,
20293 snapshot: &DisplaySnapshot,
20294 ) -> Option<DisplayRow> {
20295 self.highlighted_rows
20296 .values()
20297 .flat_map(|highlighted_rows| highlighted_rows.iter())
20298 .filter_map(|highlight| {
20299 if highlight.options.autoscroll {
20300 Some(highlight.range.start.to_display_point(snapshot).row())
20301 } else {
20302 None
20303 }
20304 })
20305 .min()
20306 }
20307
20308 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20309 self.highlight_background::<SearchWithinRange>(
20310 ranges,
20311 |colors| colors.colors().editor_document_highlight_read_background,
20312 cx,
20313 )
20314 }
20315
20316 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20317 self.breadcrumb_header = Some(new_header);
20318 }
20319
20320 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20321 self.clear_background_highlights::<SearchWithinRange>(cx);
20322 }
20323
20324 pub fn highlight_background<T: 'static>(
20325 &mut self,
20326 ranges: &[Range<Anchor>],
20327 color_fetcher: fn(&Theme) -> Hsla,
20328 cx: &mut Context<Self>,
20329 ) {
20330 self.background_highlights.insert(
20331 HighlightKey::Type(TypeId::of::<T>()),
20332 (color_fetcher, Arc::from(ranges)),
20333 );
20334 self.scrollbar_marker_state.dirty = true;
20335 cx.notify();
20336 }
20337
20338 pub fn highlight_background_key<T: 'static>(
20339 &mut self,
20340 key: usize,
20341 ranges: &[Range<Anchor>],
20342 color_fetcher: fn(&Theme) -> Hsla,
20343 cx: &mut Context<Self>,
20344 ) {
20345 self.background_highlights.insert(
20346 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20347 (color_fetcher, Arc::from(ranges)),
20348 );
20349 self.scrollbar_marker_state.dirty = true;
20350 cx.notify();
20351 }
20352
20353 pub fn clear_background_highlights<T: 'static>(
20354 &mut self,
20355 cx: &mut Context<Self>,
20356 ) -> Option<BackgroundHighlight> {
20357 let text_highlights = self
20358 .background_highlights
20359 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20360 if !text_highlights.1.is_empty() {
20361 self.scrollbar_marker_state.dirty = true;
20362 cx.notify();
20363 }
20364 Some(text_highlights)
20365 }
20366
20367 pub fn highlight_gutter<T: 'static>(
20368 &mut self,
20369 ranges: impl Into<Vec<Range<Anchor>>>,
20370 color_fetcher: fn(&App) -> Hsla,
20371 cx: &mut Context<Self>,
20372 ) {
20373 self.gutter_highlights
20374 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20375 cx.notify();
20376 }
20377
20378 pub fn clear_gutter_highlights<T: 'static>(
20379 &mut self,
20380 cx: &mut Context<Self>,
20381 ) -> Option<GutterHighlight> {
20382 cx.notify();
20383 self.gutter_highlights.remove(&TypeId::of::<T>())
20384 }
20385
20386 pub fn insert_gutter_highlight<T: 'static>(
20387 &mut self,
20388 range: Range<Anchor>,
20389 color_fetcher: fn(&App) -> Hsla,
20390 cx: &mut Context<Self>,
20391 ) {
20392 let snapshot = self.buffer().read(cx).snapshot(cx);
20393 let mut highlights = self
20394 .gutter_highlights
20395 .remove(&TypeId::of::<T>())
20396 .map(|(_, highlights)| highlights)
20397 .unwrap_or_default();
20398 let ix = highlights.binary_search_by(|highlight| {
20399 Ordering::Equal
20400 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20401 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20402 });
20403 if let Err(ix) = ix {
20404 highlights.insert(ix, range);
20405 }
20406 self.gutter_highlights
20407 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20408 }
20409
20410 pub fn remove_gutter_highlights<T: 'static>(
20411 &mut self,
20412 ranges_to_remove: Vec<Range<Anchor>>,
20413 cx: &mut Context<Self>,
20414 ) {
20415 let snapshot = self.buffer().read(cx).snapshot(cx);
20416 let Some((color_fetcher, mut gutter_highlights)) =
20417 self.gutter_highlights.remove(&TypeId::of::<T>())
20418 else {
20419 return;
20420 };
20421 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20422 gutter_highlights.retain(|highlight| {
20423 while let Some(range_to_remove) = ranges_to_remove.peek() {
20424 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20425 Ordering::Less | Ordering::Equal => {
20426 ranges_to_remove.next();
20427 }
20428 Ordering::Greater => {
20429 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20430 Ordering::Less | Ordering::Equal => {
20431 return false;
20432 }
20433 Ordering::Greater => break,
20434 }
20435 }
20436 }
20437 }
20438
20439 true
20440 });
20441 self.gutter_highlights
20442 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20443 }
20444
20445 #[cfg(feature = "test-support")]
20446 pub fn all_text_highlights(
20447 &self,
20448 window: &mut Window,
20449 cx: &mut Context<Self>,
20450 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20451 let snapshot = self.snapshot(window, cx);
20452 self.display_map.update(cx, |display_map, _| {
20453 display_map
20454 .all_text_highlights()
20455 .map(|highlight| {
20456 let (style, ranges) = highlight.as_ref();
20457 (
20458 *style,
20459 ranges
20460 .iter()
20461 .map(|range| range.clone().to_display_points(&snapshot))
20462 .collect(),
20463 )
20464 })
20465 .collect()
20466 })
20467 }
20468
20469 #[cfg(feature = "test-support")]
20470 pub fn all_text_background_highlights(
20471 &self,
20472 window: &mut Window,
20473 cx: &mut Context<Self>,
20474 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20475 let snapshot = self.snapshot(window, cx);
20476 let buffer = &snapshot.buffer_snapshot();
20477 let start = buffer.anchor_before(0);
20478 let end = buffer.anchor_after(buffer.len());
20479 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20480 }
20481
20482 #[cfg(any(test, feature = "test-support"))]
20483 pub fn sorted_background_highlights_in_range(
20484 &self,
20485 search_range: Range<Anchor>,
20486 display_snapshot: &DisplaySnapshot,
20487 theme: &Theme,
20488 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20489 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20490 res.sort_by(|a, b| {
20491 a.0.start
20492 .cmp(&b.0.start)
20493 .then_with(|| a.0.end.cmp(&b.0.end))
20494 .then_with(|| a.1.cmp(&b.1))
20495 });
20496 res
20497 }
20498
20499 #[cfg(feature = "test-support")]
20500 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20501 let snapshot = self.buffer().read(cx).snapshot(cx);
20502
20503 let highlights = self
20504 .background_highlights
20505 .get(&HighlightKey::Type(TypeId::of::<
20506 items::BufferSearchHighlights,
20507 >()));
20508
20509 if let Some((_color, ranges)) = highlights {
20510 ranges
20511 .iter()
20512 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20513 .collect_vec()
20514 } else {
20515 vec![]
20516 }
20517 }
20518
20519 fn document_highlights_for_position<'a>(
20520 &'a self,
20521 position: Anchor,
20522 buffer: &'a MultiBufferSnapshot,
20523 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20524 let read_highlights = self
20525 .background_highlights
20526 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20527 .map(|h| &h.1);
20528 let write_highlights = self
20529 .background_highlights
20530 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20531 .map(|h| &h.1);
20532 let left_position = position.bias_left(buffer);
20533 let right_position = position.bias_right(buffer);
20534 read_highlights
20535 .into_iter()
20536 .chain(write_highlights)
20537 .flat_map(move |ranges| {
20538 let start_ix = match ranges.binary_search_by(|probe| {
20539 let cmp = probe.end.cmp(&left_position, buffer);
20540 if cmp.is_ge() {
20541 Ordering::Greater
20542 } else {
20543 Ordering::Less
20544 }
20545 }) {
20546 Ok(i) | Err(i) => i,
20547 };
20548
20549 ranges[start_ix..]
20550 .iter()
20551 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20552 })
20553 }
20554
20555 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20556 self.background_highlights
20557 .get(&HighlightKey::Type(TypeId::of::<T>()))
20558 .is_some_and(|(_, highlights)| !highlights.is_empty())
20559 }
20560
20561 /// Returns all background highlights for a given range.
20562 ///
20563 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20564 pub fn background_highlights_in_range(
20565 &self,
20566 search_range: Range<Anchor>,
20567 display_snapshot: &DisplaySnapshot,
20568 theme: &Theme,
20569 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20570 let mut results = Vec::new();
20571 for (color_fetcher, ranges) in self.background_highlights.values() {
20572 let color = color_fetcher(theme);
20573 let start_ix = match ranges.binary_search_by(|probe| {
20574 let cmp = probe
20575 .end
20576 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20577 if cmp.is_gt() {
20578 Ordering::Greater
20579 } else {
20580 Ordering::Less
20581 }
20582 }) {
20583 Ok(i) | Err(i) => i,
20584 };
20585 for range in &ranges[start_ix..] {
20586 if range
20587 .start
20588 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20589 .is_ge()
20590 {
20591 break;
20592 }
20593
20594 let start = range.start.to_display_point(display_snapshot);
20595 let end = range.end.to_display_point(display_snapshot);
20596 results.push((start..end, color))
20597 }
20598 }
20599 results
20600 }
20601
20602 pub fn gutter_highlights_in_range(
20603 &self,
20604 search_range: Range<Anchor>,
20605 display_snapshot: &DisplaySnapshot,
20606 cx: &App,
20607 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20608 let mut results = Vec::new();
20609 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20610 let color = color_fetcher(cx);
20611 let start_ix = match ranges.binary_search_by(|probe| {
20612 let cmp = probe
20613 .end
20614 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20615 if cmp.is_gt() {
20616 Ordering::Greater
20617 } else {
20618 Ordering::Less
20619 }
20620 }) {
20621 Ok(i) | Err(i) => i,
20622 };
20623 for range in &ranges[start_ix..] {
20624 if range
20625 .start
20626 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20627 .is_ge()
20628 {
20629 break;
20630 }
20631
20632 let start = range.start.to_display_point(display_snapshot);
20633 let end = range.end.to_display_point(display_snapshot);
20634 results.push((start..end, color))
20635 }
20636 }
20637 results
20638 }
20639
20640 /// Get the text ranges corresponding to the redaction query
20641 pub fn redacted_ranges(
20642 &self,
20643 search_range: Range<Anchor>,
20644 display_snapshot: &DisplaySnapshot,
20645 cx: &App,
20646 ) -> Vec<Range<DisplayPoint>> {
20647 display_snapshot
20648 .buffer_snapshot()
20649 .redacted_ranges(search_range, |file| {
20650 if let Some(file) = file {
20651 file.is_private()
20652 && EditorSettings::get(
20653 Some(SettingsLocation {
20654 worktree_id: file.worktree_id(cx),
20655 path: file.path().as_ref(),
20656 }),
20657 cx,
20658 )
20659 .redact_private_values
20660 } else {
20661 false
20662 }
20663 })
20664 .map(|range| {
20665 range.start.to_display_point(display_snapshot)
20666 ..range.end.to_display_point(display_snapshot)
20667 })
20668 .collect()
20669 }
20670
20671 pub fn highlight_text_key<T: 'static>(
20672 &mut self,
20673 key: usize,
20674 ranges: Vec<Range<Anchor>>,
20675 style: HighlightStyle,
20676 cx: &mut Context<Self>,
20677 ) {
20678 self.display_map.update(cx, |map, _| {
20679 map.highlight_text(
20680 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20681 ranges,
20682 style,
20683 );
20684 });
20685 cx.notify();
20686 }
20687
20688 pub fn highlight_text<T: 'static>(
20689 &mut self,
20690 ranges: Vec<Range<Anchor>>,
20691 style: HighlightStyle,
20692 cx: &mut Context<Self>,
20693 ) {
20694 self.display_map.update(cx, |map, _| {
20695 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20696 });
20697 cx.notify();
20698 }
20699
20700 pub(crate) fn highlight_inlays<T: 'static>(
20701 &mut self,
20702 highlights: Vec<InlayHighlight>,
20703 style: HighlightStyle,
20704 cx: &mut Context<Self>,
20705 ) {
20706 self.display_map.update(cx, |map, _| {
20707 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20708 });
20709 cx.notify();
20710 }
20711
20712 pub fn text_highlights<'a, T: 'static>(
20713 &'a self,
20714 cx: &'a App,
20715 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20716 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20717 }
20718
20719 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20720 let cleared = self
20721 .display_map
20722 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20723 if cleared {
20724 cx.notify();
20725 }
20726 }
20727
20728 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20729 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20730 && self.focus_handle.is_focused(window)
20731 }
20732
20733 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20734 self.show_cursor_when_unfocused = is_enabled;
20735 cx.notify();
20736 }
20737
20738 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20739 cx.notify();
20740 }
20741
20742 fn on_debug_session_event(
20743 &mut self,
20744 _session: Entity<Session>,
20745 event: &SessionEvent,
20746 cx: &mut Context<Self>,
20747 ) {
20748 if let SessionEvent::InvalidateInlineValue = event {
20749 self.refresh_inline_values(cx);
20750 }
20751 }
20752
20753 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20754 let Some(project) = self.project.clone() else {
20755 return;
20756 };
20757
20758 if !self.inline_value_cache.enabled {
20759 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20760 self.splice_inlays(&inlays, Vec::new(), cx);
20761 return;
20762 }
20763
20764 let current_execution_position = self
20765 .highlighted_rows
20766 .get(&TypeId::of::<ActiveDebugLine>())
20767 .and_then(|lines| lines.last().map(|line| line.range.end));
20768
20769 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20770 let inline_values = editor
20771 .update(cx, |editor, cx| {
20772 let Some(current_execution_position) = current_execution_position else {
20773 return Some(Task::ready(Ok(Vec::new())));
20774 };
20775
20776 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20777 let snapshot = buffer.snapshot(cx);
20778
20779 let excerpt = snapshot.excerpt_containing(
20780 current_execution_position..current_execution_position,
20781 )?;
20782
20783 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20784 })?;
20785
20786 let range =
20787 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20788
20789 project.inline_values(buffer, range, cx)
20790 })
20791 .ok()
20792 .flatten()?
20793 .await
20794 .context("refreshing debugger inlays")
20795 .log_err()?;
20796
20797 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20798
20799 for (buffer_id, inline_value) in inline_values
20800 .into_iter()
20801 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20802 {
20803 buffer_inline_values
20804 .entry(buffer_id)
20805 .or_default()
20806 .push(inline_value);
20807 }
20808
20809 editor
20810 .update(cx, |editor, cx| {
20811 let snapshot = editor.buffer.read(cx).snapshot(cx);
20812 let mut new_inlays = Vec::default();
20813
20814 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20815 let buffer_id = buffer_snapshot.remote_id();
20816 buffer_inline_values
20817 .get(&buffer_id)
20818 .into_iter()
20819 .flatten()
20820 .for_each(|hint| {
20821 let inlay = Inlay::debugger(
20822 post_inc(&mut editor.next_inlay_id),
20823 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20824 hint.text(),
20825 );
20826 if !inlay.text().chars().contains(&'\n') {
20827 new_inlays.push(inlay);
20828 }
20829 });
20830 }
20831
20832 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20833 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20834
20835 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20836 })
20837 .ok()?;
20838 Some(())
20839 });
20840 }
20841
20842 fn on_buffer_event(
20843 &mut self,
20844 multibuffer: &Entity<MultiBuffer>,
20845 event: &multi_buffer::Event,
20846 window: &mut Window,
20847 cx: &mut Context<Self>,
20848 ) {
20849 match event {
20850 multi_buffer::Event::Edited {
20851 singleton_buffer_edited,
20852 edited_buffer,
20853 } => {
20854 self.scrollbar_marker_state.dirty = true;
20855 self.active_indent_guides_state.dirty = true;
20856 self.refresh_active_diagnostics(cx);
20857 self.refresh_code_actions(window, cx);
20858 self.refresh_selected_text_highlights(true, window, cx);
20859 self.refresh_single_line_folds(window, cx);
20860 refresh_matching_bracket_highlights(self, cx);
20861 if self.has_active_edit_prediction() {
20862 self.update_visible_edit_prediction(window, cx);
20863 }
20864 if let Some(project) = self.project.as_ref()
20865 && let Some(edited_buffer) = edited_buffer
20866 {
20867 project.update(cx, |project, cx| {
20868 self.registered_buffers
20869 .entry(edited_buffer.read(cx).remote_id())
20870 .or_insert_with(|| {
20871 project.register_buffer_with_language_servers(edited_buffer, cx)
20872 });
20873 });
20874 }
20875 cx.emit(EditorEvent::BufferEdited);
20876 cx.emit(SearchEvent::MatchesInvalidated);
20877
20878 if let Some(buffer) = edited_buffer {
20879 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20880 }
20881
20882 if *singleton_buffer_edited {
20883 if let Some(buffer) = edited_buffer
20884 && buffer.read(cx).file().is_none()
20885 {
20886 cx.emit(EditorEvent::TitleChanged);
20887 }
20888 if let Some(project) = &self.project {
20889 #[allow(clippy::mutable_key_type)]
20890 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20891 multibuffer
20892 .all_buffers()
20893 .into_iter()
20894 .filter_map(|buffer| {
20895 buffer.update(cx, |buffer, cx| {
20896 let language = buffer.language()?;
20897 let should_discard = project.update(cx, |project, cx| {
20898 project.is_local()
20899 && !project.has_language_servers_for(buffer, cx)
20900 });
20901 should_discard.not().then_some(language.clone())
20902 })
20903 })
20904 .collect::<HashSet<_>>()
20905 });
20906 if !languages_affected.is_empty() {
20907 self.refresh_inlay_hints(
20908 InlayHintRefreshReason::BufferEdited(languages_affected),
20909 cx,
20910 );
20911 }
20912 }
20913 }
20914
20915 let Some(project) = &self.project else { return };
20916 let (telemetry, is_via_ssh) = {
20917 let project = project.read(cx);
20918 let telemetry = project.client().telemetry().clone();
20919 let is_via_ssh = project.is_via_remote_server();
20920 (telemetry, is_via_ssh)
20921 };
20922 refresh_linked_ranges(self, window, cx);
20923 telemetry.log_edit_event("editor", is_via_ssh);
20924 }
20925 multi_buffer::Event::ExcerptsAdded {
20926 buffer,
20927 predecessor,
20928 excerpts,
20929 } => {
20930 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20931 let buffer_id = buffer.read(cx).remote_id();
20932 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20933 && let Some(project) = &self.project
20934 {
20935 update_uncommitted_diff_for_buffer(
20936 cx.entity(),
20937 project,
20938 [buffer.clone()],
20939 self.buffer.clone(),
20940 cx,
20941 )
20942 .detach();
20943 }
20944 if self.active_diagnostics != ActiveDiagnostic::All {
20945 self.update_lsp_data(false, Some(buffer_id), window, cx);
20946 }
20947 cx.emit(EditorEvent::ExcerptsAdded {
20948 buffer: buffer.clone(),
20949 predecessor: *predecessor,
20950 excerpts: excerpts.clone(),
20951 });
20952 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20953 }
20954 multi_buffer::Event::ExcerptsRemoved {
20955 ids,
20956 removed_buffer_ids,
20957 } => {
20958 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20959 let buffer = self.buffer.read(cx);
20960 self.registered_buffers
20961 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20962 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20963 cx.emit(EditorEvent::ExcerptsRemoved {
20964 ids: ids.clone(),
20965 removed_buffer_ids: removed_buffer_ids.clone(),
20966 });
20967 }
20968 multi_buffer::Event::ExcerptsEdited {
20969 excerpt_ids,
20970 buffer_ids,
20971 } => {
20972 self.display_map.update(cx, |map, cx| {
20973 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20974 });
20975 cx.emit(EditorEvent::ExcerptsEdited {
20976 ids: excerpt_ids.clone(),
20977 });
20978 }
20979 multi_buffer::Event::ExcerptsExpanded { ids } => {
20980 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20981 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20982 }
20983 multi_buffer::Event::Reparsed(buffer_id) => {
20984 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20985 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20986
20987 cx.emit(EditorEvent::Reparsed(*buffer_id));
20988 }
20989 multi_buffer::Event::DiffHunksToggled => {
20990 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20991 }
20992 multi_buffer::Event::LanguageChanged(buffer_id) => {
20993 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20994 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20995 cx.emit(EditorEvent::Reparsed(*buffer_id));
20996 cx.notify();
20997 }
20998 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20999 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21000 multi_buffer::Event::FileHandleChanged
21001 | multi_buffer::Event::Reloaded
21002 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21003 multi_buffer::Event::DiagnosticsUpdated => {
21004 self.update_diagnostics_state(window, cx);
21005 }
21006 _ => {}
21007 };
21008 }
21009
21010 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21011 if !self.diagnostics_enabled() {
21012 return;
21013 }
21014 self.refresh_active_diagnostics(cx);
21015 self.refresh_inline_diagnostics(true, window, cx);
21016 self.scrollbar_marker_state.dirty = true;
21017 cx.notify();
21018 }
21019
21020 pub fn start_temporary_diff_override(&mut self) {
21021 self.load_diff_task.take();
21022 self.temporary_diff_override = true;
21023 }
21024
21025 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21026 self.temporary_diff_override = false;
21027 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21028 self.buffer.update(cx, |buffer, cx| {
21029 buffer.set_all_diff_hunks_collapsed(cx);
21030 });
21031
21032 if let Some(project) = self.project.clone() {
21033 self.load_diff_task = Some(
21034 update_uncommitted_diff_for_buffer(
21035 cx.entity(),
21036 &project,
21037 self.buffer.read(cx).all_buffers(),
21038 self.buffer.clone(),
21039 cx,
21040 )
21041 .shared(),
21042 );
21043 }
21044 }
21045
21046 fn on_display_map_changed(
21047 &mut self,
21048 _: Entity<DisplayMap>,
21049 _: &mut Window,
21050 cx: &mut Context<Self>,
21051 ) {
21052 cx.notify();
21053 }
21054
21055 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21056 if self.diagnostics_enabled() {
21057 let new_severity = EditorSettings::get_global(cx)
21058 .diagnostics_max_severity
21059 .unwrap_or(DiagnosticSeverity::Hint);
21060 self.set_max_diagnostics_severity(new_severity, cx);
21061 }
21062 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21063 self.update_edit_prediction_settings(cx);
21064 self.refresh_edit_prediction(true, false, window, cx);
21065 self.refresh_inline_values(cx);
21066 self.refresh_inlay_hints(
21067 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21068 self.selections.newest_anchor().head(),
21069 &self.buffer.read(cx).snapshot(cx),
21070 cx,
21071 )),
21072 cx,
21073 );
21074
21075 let old_cursor_shape = self.cursor_shape;
21076 let old_show_breadcrumbs = self.show_breadcrumbs;
21077
21078 {
21079 let editor_settings = EditorSettings::get_global(cx);
21080 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21081 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21082 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21083 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21084 }
21085
21086 if old_cursor_shape != self.cursor_shape {
21087 cx.emit(EditorEvent::CursorShapeChanged);
21088 }
21089
21090 if old_show_breadcrumbs != self.show_breadcrumbs {
21091 cx.emit(EditorEvent::BreadcrumbsChanged);
21092 }
21093
21094 let project_settings = ProjectSettings::get_global(cx);
21095 self.serialize_dirty_buffers =
21096 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21097
21098 if self.mode.is_full() {
21099 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21100 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21101 if self.show_inline_diagnostics != show_inline_diagnostics {
21102 self.show_inline_diagnostics = show_inline_diagnostics;
21103 self.refresh_inline_diagnostics(false, window, cx);
21104 }
21105
21106 if self.git_blame_inline_enabled != inline_blame_enabled {
21107 self.toggle_git_blame_inline_internal(false, window, cx);
21108 }
21109
21110 let minimap_settings = EditorSettings::get_global(cx).minimap;
21111 if self.minimap_visibility != MinimapVisibility::Disabled {
21112 if self.minimap_visibility.settings_visibility()
21113 != minimap_settings.minimap_enabled()
21114 {
21115 self.set_minimap_visibility(
21116 MinimapVisibility::for_mode(self.mode(), cx),
21117 window,
21118 cx,
21119 );
21120 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21121 minimap_entity.update(cx, |minimap_editor, cx| {
21122 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21123 })
21124 }
21125 }
21126 }
21127
21128 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21129 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21130 }) {
21131 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21132 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21133 }
21134 self.refresh_colors(false, None, window, cx);
21135 }
21136
21137 cx.notify();
21138 }
21139
21140 pub fn set_searchable(&mut self, searchable: bool) {
21141 self.searchable = searchable;
21142 }
21143
21144 pub fn searchable(&self) -> bool {
21145 self.searchable
21146 }
21147
21148 fn open_proposed_changes_editor(
21149 &mut self,
21150 _: &OpenProposedChangesEditor,
21151 window: &mut Window,
21152 cx: &mut Context<Self>,
21153 ) {
21154 let Some(workspace) = self.workspace() else {
21155 cx.propagate();
21156 return;
21157 };
21158
21159 let selections = self.selections.all::<usize>(cx);
21160 let multi_buffer = self.buffer.read(cx);
21161 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21162 let mut new_selections_by_buffer = HashMap::default();
21163 for selection in selections {
21164 for (buffer, range, _) in
21165 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21166 {
21167 let mut range = range.to_point(buffer);
21168 range.start.column = 0;
21169 range.end.column = buffer.line_len(range.end.row);
21170 new_selections_by_buffer
21171 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21172 .or_insert(Vec::new())
21173 .push(range)
21174 }
21175 }
21176
21177 let proposed_changes_buffers = new_selections_by_buffer
21178 .into_iter()
21179 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21180 .collect::<Vec<_>>();
21181 let proposed_changes_editor = cx.new(|cx| {
21182 ProposedChangesEditor::new(
21183 "Proposed changes",
21184 proposed_changes_buffers,
21185 self.project.clone(),
21186 window,
21187 cx,
21188 )
21189 });
21190
21191 window.defer(cx, move |window, cx| {
21192 workspace.update(cx, |workspace, cx| {
21193 workspace.active_pane().update(cx, |pane, cx| {
21194 pane.add_item(
21195 Box::new(proposed_changes_editor),
21196 true,
21197 true,
21198 None,
21199 window,
21200 cx,
21201 );
21202 });
21203 });
21204 });
21205 }
21206
21207 pub fn open_excerpts_in_split(
21208 &mut self,
21209 _: &OpenExcerptsSplit,
21210 window: &mut Window,
21211 cx: &mut Context<Self>,
21212 ) {
21213 self.open_excerpts_common(None, true, window, cx)
21214 }
21215
21216 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21217 self.open_excerpts_common(None, false, window, cx)
21218 }
21219
21220 fn open_excerpts_common(
21221 &mut self,
21222 jump_data: Option<JumpData>,
21223 split: bool,
21224 window: &mut Window,
21225 cx: &mut Context<Self>,
21226 ) {
21227 let Some(workspace) = self.workspace() else {
21228 cx.propagate();
21229 return;
21230 };
21231
21232 if self.buffer.read(cx).is_singleton() {
21233 cx.propagate();
21234 return;
21235 }
21236
21237 let mut new_selections_by_buffer = HashMap::default();
21238 match &jump_data {
21239 Some(JumpData::MultiBufferPoint {
21240 excerpt_id,
21241 position,
21242 anchor,
21243 line_offset_from_top,
21244 }) => {
21245 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21246 if let Some(buffer) = multi_buffer_snapshot
21247 .buffer_id_for_excerpt(*excerpt_id)
21248 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21249 {
21250 let buffer_snapshot = buffer.read(cx).snapshot();
21251 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21252 language::ToPoint::to_point(anchor, &buffer_snapshot)
21253 } else {
21254 buffer_snapshot.clip_point(*position, Bias::Left)
21255 };
21256 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21257 new_selections_by_buffer.insert(
21258 buffer,
21259 (
21260 vec![jump_to_offset..jump_to_offset],
21261 Some(*line_offset_from_top),
21262 ),
21263 );
21264 }
21265 }
21266 Some(JumpData::MultiBufferRow {
21267 row,
21268 line_offset_from_top,
21269 }) => {
21270 let point = MultiBufferPoint::new(row.0, 0);
21271 if let Some((buffer, buffer_point, _)) =
21272 self.buffer.read(cx).point_to_buffer_point(point, cx)
21273 {
21274 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21275 new_selections_by_buffer
21276 .entry(buffer)
21277 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21278 .0
21279 .push(buffer_offset..buffer_offset)
21280 }
21281 }
21282 None => {
21283 let selections = self.selections.all::<usize>(cx);
21284 let multi_buffer = self.buffer.read(cx);
21285 for selection in selections {
21286 for (snapshot, range, _, anchor) in multi_buffer
21287 .snapshot(cx)
21288 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21289 {
21290 if let Some(anchor) = anchor {
21291 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21292 else {
21293 continue;
21294 };
21295 let offset = text::ToOffset::to_offset(
21296 &anchor.text_anchor,
21297 &buffer_handle.read(cx).snapshot(),
21298 );
21299 let range = offset..offset;
21300 new_selections_by_buffer
21301 .entry(buffer_handle)
21302 .or_insert((Vec::new(), None))
21303 .0
21304 .push(range)
21305 } else {
21306 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21307 else {
21308 continue;
21309 };
21310 new_selections_by_buffer
21311 .entry(buffer_handle)
21312 .or_insert((Vec::new(), None))
21313 .0
21314 .push(range)
21315 }
21316 }
21317 }
21318 }
21319 }
21320
21321 new_selections_by_buffer
21322 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21323
21324 if new_selections_by_buffer.is_empty() {
21325 return;
21326 }
21327
21328 // We defer the pane interaction because we ourselves are a workspace item
21329 // and activating a new item causes the pane to call a method on us reentrantly,
21330 // which panics if we're on the stack.
21331 window.defer(cx, move |window, cx| {
21332 workspace.update(cx, |workspace, cx| {
21333 let pane = if split {
21334 workspace.adjacent_pane(window, cx)
21335 } else {
21336 workspace.active_pane().clone()
21337 };
21338
21339 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21340 let editor = buffer
21341 .read(cx)
21342 .file()
21343 .is_none()
21344 .then(|| {
21345 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21346 // so `workspace.open_project_item` will never find them, always opening a new editor.
21347 // Instead, we try to activate the existing editor in the pane first.
21348 let (editor, pane_item_index) =
21349 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21350 let editor = item.downcast::<Editor>()?;
21351 let singleton_buffer =
21352 editor.read(cx).buffer().read(cx).as_singleton()?;
21353 if singleton_buffer == buffer {
21354 Some((editor, i))
21355 } else {
21356 None
21357 }
21358 })?;
21359 pane.update(cx, |pane, cx| {
21360 pane.activate_item(pane_item_index, true, true, window, cx)
21361 });
21362 Some(editor)
21363 })
21364 .flatten()
21365 .unwrap_or_else(|| {
21366 workspace.open_project_item::<Self>(
21367 pane.clone(),
21368 buffer,
21369 true,
21370 true,
21371 window,
21372 cx,
21373 )
21374 });
21375
21376 editor.update(cx, |editor, cx| {
21377 let autoscroll = match scroll_offset {
21378 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21379 None => Autoscroll::newest(),
21380 };
21381 let nav_history = editor.nav_history.take();
21382 editor.change_selections(
21383 SelectionEffects::scroll(autoscroll),
21384 window,
21385 cx,
21386 |s| {
21387 s.select_ranges(ranges);
21388 },
21389 );
21390 editor.nav_history = nav_history;
21391 });
21392 }
21393 })
21394 });
21395 }
21396
21397 // For now, don't allow opening excerpts in buffers that aren't backed by
21398 // regular project files.
21399 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21400 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21401 }
21402
21403 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21404 let snapshot = self.buffer.read(cx).read(cx);
21405 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21406 Some(
21407 ranges
21408 .iter()
21409 .map(move |range| {
21410 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21411 })
21412 .collect(),
21413 )
21414 }
21415
21416 fn selection_replacement_ranges(
21417 &self,
21418 range: Range<OffsetUtf16>,
21419 cx: &mut App,
21420 ) -> Vec<Range<OffsetUtf16>> {
21421 let selections = self.selections.all::<OffsetUtf16>(cx);
21422 let newest_selection = selections
21423 .iter()
21424 .max_by_key(|selection| selection.id)
21425 .unwrap();
21426 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21427 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21428 let snapshot = self.buffer.read(cx).read(cx);
21429 selections
21430 .into_iter()
21431 .map(|mut selection| {
21432 selection.start.0 =
21433 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21434 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21435 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21436 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21437 })
21438 .collect()
21439 }
21440
21441 fn report_editor_event(
21442 &self,
21443 reported_event: ReportEditorEvent,
21444 file_extension: Option<String>,
21445 cx: &App,
21446 ) {
21447 if cfg!(any(test, feature = "test-support")) {
21448 return;
21449 }
21450
21451 let Some(project) = &self.project else { return };
21452
21453 // If None, we are in a file without an extension
21454 let file = self
21455 .buffer
21456 .read(cx)
21457 .as_singleton()
21458 .and_then(|b| b.read(cx).file());
21459 let file_extension = file_extension.or(file
21460 .as_ref()
21461 .and_then(|file| Path::new(file.file_name(cx)).extension())
21462 .and_then(|e| e.to_str())
21463 .map(|a| a.to_string()));
21464
21465 let vim_mode = vim_enabled(cx);
21466
21467 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21468 let copilot_enabled = edit_predictions_provider
21469 == language::language_settings::EditPredictionProvider::Copilot;
21470 let copilot_enabled_for_language = self
21471 .buffer
21472 .read(cx)
21473 .language_settings(cx)
21474 .show_edit_predictions;
21475
21476 let project = project.read(cx);
21477 let event_type = reported_event.event_type();
21478
21479 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21480 telemetry::event!(
21481 event_type,
21482 type = if auto_saved {"autosave"} else {"manual"},
21483 file_extension,
21484 vim_mode,
21485 copilot_enabled,
21486 copilot_enabled_for_language,
21487 edit_predictions_provider,
21488 is_via_ssh = project.is_via_remote_server(),
21489 );
21490 } else {
21491 telemetry::event!(
21492 event_type,
21493 file_extension,
21494 vim_mode,
21495 copilot_enabled,
21496 copilot_enabled_for_language,
21497 edit_predictions_provider,
21498 is_via_ssh = project.is_via_remote_server(),
21499 );
21500 };
21501 }
21502
21503 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21504 /// with each line being an array of {text, highlight} objects.
21505 fn copy_highlight_json(
21506 &mut self,
21507 _: &CopyHighlightJson,
21508 window: &mut Window,
21509 cx: &mut Context<Self>,
21510 ) {
21511 #[derive(Serialize)]
21512 struct Chunk<'a> {
21513 text: String,
21514 highlight: Option<&'a str>,
21515 }
21516
21517 let snapshot = self.buffer.read(cx).snapshot(cx);
21518 let range = self
21519 .selected_text_range(false, window, cx)
21520 .and_then(|selection| {
21521 if selection.range.is_empty() {
21522 None
21523 } else {
21524 Some(selection.range)
21525 }
21526 })
21527 .unwrap_or_else(|| 0..snapshot.len());
21528
21529 let chunks = snapshot.chunks(range, true);
21530 let mut lines = Vec::new();
21531 let mut line: VecDeque<Chunk> = VecDeque::new();
21532
21533 let Some(style) = self.style.as_ref() else {
21534 return;
21535 };
21536
21537 for chunk in chunks {
21538 let highlight = chunk
21539 .syntax_highlight_id
21540 .and_then(|id| id.name(&style.syntax));
21541 let mut chunk_lines = chunk.text.split('\n').peekable();
21542 while let Some(text) = chunk_lines.next() {
21543 let mut merged_with_last_token = false;
21544 if let Some(last_token) = line.back_mut()
21545 && last_token.highlight == highlight
21546 {
21547 last_token.text.push_str(text);
21548 merged_with_last_token = true;
21549 }
21550
21551 if !merged_with_last_token {
21552 line.push_back(Chunk {
21553 text: text.into(),
21554 highlight,
21555 });
21556 }
21557
21558 if chunk_lines.peek().is_some() {
21559 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21560 line.pop_front();
21561 }
21562 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21563 line.pop_back();
21564 }
21565
21566 lines.push(mem::take(&mut line));
21567 }
21568 }
21569 }
21570
21571 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21572 return;
21573 };
21574 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21575 }
21576
21577 pub fn open_context_menu(
21578 &mut self,
21579 _: &OpenContextMenu,
21580 window: &mut Window,
21581 cx: &mut Context<Self>,
21582 ) {
21583 self.request_autoscroll(Autoscroll::newest(), cx);
21584 let position = self.selections.newest_display(cx).start;
21585 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21586 }
21587
21588 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21589 &self.inlay_hint_cache
21590 }
21591
21592 pub fn replay_insert_event(
21593 &mut self,
21594 text: &str,
21595 relative_utf16_range: Option<Range<isize>>,
21596 window: &mut Window,
21597 cx: &mut Context<Self>,
21598 ) {
21599 if !self.input_enabled {
21600 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21601 return;
21602 }
21603 if let Some(relative_utf16_range) = relative_utf16_range {
21604 let selections = self.selections.all::<OffsetUtf16>(cx);
21605 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21606 let new_ranges = selections.into_iter().map(|range| {
21607 let start = OffsetUtf16(
21608 range
21609 .head()
21610 .0
21611 .saturating_add_signed(relative_utf16_range.start),
21612 );
21613 let end = OffsetUtf16(
21614 range
21615 .head()
21616 .0
21617 .saturating_add_signed(relative_utf16_range.end),
21618 );
21619 start..end
21620 });
21621 s.select_ranges(new_ranges);
21622 });
21623 }
21624
21625 self.handle_input(text, window, cx);
21626 }
21627
21628 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21629 let Some(provider) = self.semantics_provider.as_ref() else {
21630 return false;
21631 };
21632
21633 let mut supports = false;
21634 self.buffer().update(cx, |this, cx| {
21635 this.for_each_buffer(|buffer| {
21636 supports |= provider.supports_inlay_hints(buffer, cx);
21637 });
21638 });
21639
21640 supports
21641 }
21642
21643 pub fn is_focused(&self, window: &Window) -> bool {
21644 self.focus_handle.is_focused(window)
21645 }
21646
21647 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21648 cx.emit(EditorEvent::Focused);
21649
21650 if let Some(descendant) = self
21651 .last_focused_descendant
21652 .take()
21653 .and_then(|descendant| descendant.upgrade())
21654 {
21655 window.focus(&descendant);
21656 } else {
21657 if let Some(blame) = self.blame.as_ref() {
21658 blame.update(cx, GitBlame::focus)
21659 }
21660
21661 self.blink_manager.update(cx, BlinkManager::enable);
21662 self.show_cursor_names(window, cx);
21663 self.buffer.update(cx, |buffer, cx| {
21664 buffer.finalize_last_transaction(cx);
21665 if self.leader_id.is_none() {
21666 buffer.set_active_selections(
21667 &self.selections.disjoint_anchors_arc(),
21668 self.selections.line_mode(),
21669 self.cursor_shape,
21670 cx,
21671 );
21672 }
21673 });
21674 }
21675 }
21676
21677 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21678 cx.emit(EditorEvent::FocusedIn)
21679 }
21680
21681 fn handle_focus_out(
21682 &mut self,
21683 event: FocusOutEvent,
21684 _window: &mut Window,
21685 cx: &mut Context<Self>,
21686 ) {
21687 if event.blurred != self.focus_handle {
21688 self.last_focused_descendant = Some(event.blurred);
21689 }
21690 self.selection_drag_state = SelectionDragState::None;
21691 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21692 }
21693
21694 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21695 self.blink_manager.update(cx, BlinkManager::disable);
21696 self.buffer
21697 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21698
21699 if let Some(blame) = self.blame.as_ref() {
21700 blame.update(cx, GitBlame::blur)
21701 }
21702 if !self.hover_state.focused(window, cx) {
21703 hide_hover(self, cx);
21704 }
21705 if !self
21706 .context_menu
21707 .borrow()
21708 .as_ref()
21709 .is_some_and(|context_menu| context_menu.focused(window, cx))
21710 {
21711 self.hide_context_menu(window, cx);
21712 }
21713 self.take_active_edit_prediction(cx);
21714 cx.emit(EditorEvent::Blurred);
21715 cx.notify();
21716 }
21717
21718 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21719 let mut pending: String = window
21720 .pending_input_keystrokes()
21721 .into_iter()
21722 .flatten()
21723 .filter_map(|keystroke| {
21724 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21725 keystroke.key_char.clone()
21726 } else {
21727 None
21728 }
21729 })
21730 .collect();
21731
21732 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21733 pending = "".to_string();
21734 }
21735
21736 let existing_pending = self
21737 .text_highlights::<PendingInput>(cx)
21738 .map(|(_, ranges)| ranges.to_vec());
21739 if existing_pending.is_none() && pending.is_empty() {
21740 return;
21741 }
21742 let transaction =
21743 self.transact(window, cx, |this, window, cx| {
21744 let selections = this.selections.all::<usize>(cx);
21745 let edits = selections
21746 .iter()
21747 .map(|selection| (selection.end..selection.end, pending.clone()));
21748 this.edit(edits, cx);
21749 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21750 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21751 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21752 }));
21753 });
21754 if let Some(existing_ranges) = existing_pending {
21755 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21756 this.edit(edits, cx);
21757 }
21758 });
21759
21760 let snapshot = self.snapshot(window, cx);
21761 let ranges = self
21762 .selections
21763 .all::<usize>(cx)
21764 .into_iter()
21765 .map(|selection| {
21766 snapshot.buffer_snapshot().anchor_after(selection.end)
21767 ..snapshot
21768 .buffer_snapshot()
21769 .anchor_before(selection.end + pending.len())
21770 })
21771 .collect();
21772
21773 if pending.is_empty() {
21774 self.clear_highlights::<PendingInput>(cx);
21775 } else {
21776 self.highlight_text::<PendingInput>(
21777 ranges,
21778 HighlightStyle {
21779 underline: Some(UnderlineStyle {
21780 thickness: px(1.),
21781 color: None,
21782 wavy: false,
21783 }),
21784 ..Default::default()
21785 },
21786 cx,
21787 );
21788 }
21789
21790 self.ime_transaction = self.ime_transaction.or(transaction);
21791 if let Some(transaction) = self.ime_transaction {
21792 self.buffer.update(cx, |buffer, cx| {
21793 buffer.group_until_transaction(transaction, cx);
21794 });
21795 }
21796
21797 if self.text_highlights::<PendingInput>(cx).is_none() {
21798 self.ime_transaction.take();
21799 }
21800 }
21801
21802 pub fn register_action_renderer(
21803 &mut self,
21804 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21805 ) -> Subscription {
21806 let id = self.next_editor_action_id.post_inc();
21807 self.editor_actions
21808 .borrow_mut()
21809 .insert(id, Box::new(listener));
21810
21811 let editor_actions = self.editor_actions.clone();
21812 Subscription::new(move || {
21813 editor_actions.borrow_mut().remove(&id);
21814 })
21815 }
21816
21817 pub fn register_action<A: Action>(
21818 &mut self,
21819 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21820 ) -> Subscription {
21821 let id = self.next_editor_action_id.post_inc();
21822 let listener = Arc::new(listener);
21823 self.editor_actions.borrow_mut().insert(
21824 id,
21825 Box::new(move |_, window, _| {
21826 let listener = listener.clone();
21827 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21828 let action = action.downcast_ref().unwrap();
21829 if phase == DispatchPhase::Bubble {
21830 listener(action, window, cx)
21831 }
21832 })
21833 }),
21834 );
21835
21836 let editor_actions = self.editor_actions.clone();
21837 Subscription::new(move || {
21838 editor_actions.borrow_mut().remove(&id);
21839 })
21840 }
21841
21842 pub fn file_header_size(&self) -> u32 {
21843 FILE_HEADER_HEIGHT
21844 }
21845
21846 pub fn restore(
21847 &mut self,
21848 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21849 window: &mut Window,
21850 cx: &mut Context<Self>,
21851 ) {
21852 let workspace = self.workspace();
21853 let project = self.project();
21854 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21855 let mut tasks = Vec::new();
21856 for (buffer_id, changes) in revert_changes {
21857 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21858 buffer.update(cx, |buffer, cx| {
21859 buffer.edit(
21860 changes
21861 .into_iter()
21862 .map(|(range, text)| (range, text.to_string())),
21863 None,
21864 cx,
21865 );
21866 });
21867
21868 if let Some(project) =
21869 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21870 {
21871 project.update(cx, |project, cx| {
21872 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21873 })
21874 }
21875 }
21876 }
21877 tasks
21878 });
21879 cx.spawn_in(window, async move |_, cx| {
21880 for (buffer, task) in save_tasks {
21881 let result = task.await;
21882 if result.is_err() {
21883 let Some(path) = buffer
21884 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21885 .ok()
21886 else {
21887 continue;
21888 };
21889 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21890 let Some(task) = cx
21891 .update_window_entity(workspace, |workspace, window, cx| {
21892 workspace
21893 .open_path_preview(path, None, false, false, false, window, cx)
21894 })
21895 .ok()
21896 else {
21897 continue;
21898 };
21899 task.await.log_err();
21900 }
21901 }
21902 }
21903 })
21904 .detach();
21905 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21906 selections.refresh()
21907 });
21908 }
21909
21910 pub fn to_pixel_point(
21911 &self,
21912 source: multi_buffer::Anchor,
21913 editor_snapshot: &EditorSnapshot,
21914 window: &mut Window,
21915 ) -> Option<gpui::Point<Pixels>> {
21916 let source_point = source.to_display_point(editor_snapshot);
21917 self.display_to_pixel_point(source_point, editor_snapshot, window)
21918 }
21919
21920 pub fn display_to_pixel_point(
21921 &self,
21922 source: DisplayPoint,
21923 editor_snapshot: &EditorSnapshot,
21924 window: &mut Window,
21925 ) -> Option<gpui::Point<Pixels>> {
21926 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21927 let text_layout_details = self.text_layout_details(window);
21928 let scroll_top = text_layout_details
21929 .scroll_anchor
21930 .scroll_position(editor_snapshot)
21931 .y;
21932
21933 if source.row().as_f64() < scroll_top.floor() {
21934 return None;
21935 }
21936 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21937 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21938 Some(gpui::Point::new(source_x, source_y))
21939 }
21940
21941 pub fn has_visible_completions_menu(&self) -> bool {
21942 !self.edit_prediction_preview_is_active()
21943 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21944 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21945 })
21946 }
21947
21948 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21949 if self.mode.is_minimap() {
21950 return;
21951 }
21952 self.addons
21953 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21954 }
21955
21956 pub fn unregister_addon<T: Addon>(&mut self) {
21957 self.addons.remove(&std::any::TypeId::of::<T>());
21958 }
21959
21960 pub fn addon<T: Addon>(&self) -> Option<&T> {
21961 let type_id = std::any::TypeId::of::<T>();
21962 self.addons
21963 .get(&type_id)
21964 .and_then(|item| item.to_any().downcast_ref::<T>())
21965 }
21966
21967 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21968 let type_id = std::any::TypeId::of::<T>();
21969 self.addons
21970 .get_mut(&type_id)
21971 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21972 }
21973
21974 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21975 let text_layout_details = self.text_layout_details(window);
21976 let style = &text_layout_details.editor_style;
21977 let font_id = window.text_system().resolve_font(&style.text.font());
21978 let font_size = style.text.font_size.to_pixels(window.rem_size());
21979 let line_height = style.text.line_height_in_pixels(window.rem_size());
21980 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21981 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21982
21983 CharacterDimensions {
21984 em_width,
21985 em_advance,
21986 line_height,
21987 }
21988 }
21989
21990 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21991 self.load_diff_task.clone()
21992 }
21993
21994 fn read_metadata_from_db(
21995 &mut self,
21996 item_id: u64,
21997 workspace_id: WorkspaceId,
21998 window: &mut Window,
21999 cx: &mut Context<Editor>,
22000 ) {
22001 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22002 && !self.mode.is_minimap()
22003 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22004 {
22005 let buffer_snapshot = OnceCell::new();
22006
22007 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22008 && !folds.is_empty()
22009 {
22010 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22011 self.fold_ranges(
22012 folds
22013 .into_iter()
22014 .map(|(start, end)| {
22015 snapshot.clip_offset(start, Bias::Left)
22016 ..snapshot.clip_offset(end, Bias::Right)
22017 })
22018 .collect(),
22019 false,
22020 window,
22021 cx,
22022 );
22023 }
22024
22025 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22026 && !selections.is_empty()
22027 {
22028 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22029 // skip adding the initial selection to selection history
22030 self.selection_history.mode = SelectionHistoryMode::Skipping;
22031 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22032 s.select_ranges(selections.into_iter().map(|(start, end)| {
22033 snapshot.clip_offset(start, Bias::Left)
22034 ..snapshot.clip_offset(end, Bias::Right)
22035 }));
22036 });
22037 self.selection_history.mode = SelectionHistoryMode::Normal;
22038 };
22039 }
22040
22041 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22042 }
22043
22044 fn update_lsp_data(
22045 &mut self,
22046 ignore_cache: bool,
22047 for_buffer: Option<BufferId>,
22048 window: &mut Window,
22049 cx: &mut Context<'_, Self>,
22050 ) {
22051 self.pull_diagnostics(for_buffer, window, cx);
22052 self.refresh_colors(ignore_cache, for_buffer, window, cx);
22053 }
22054}
22055
22056fn edit_for_markdown_paste<'a>(
22057 buffer: &MultiBufferSnapshot,
22058 range: Range<usize>,
22059 to_insert: &'a str,
22060 url: Option<url::Url>,
22061) -> (Range<usize>, Cow<'a, str>) {
22062 if url.is_none() {
22063 return (range, Cow::Borrowed(to_insert));
22064 };
22065
22066 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22067
22068 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22069 Cow::Borrowed(to_insert)
22070 } else {
22071 Cow::Owned(format!("[{old_text}]({to_insert})"))
22072 };
22073 (range, new_text)
22074}
22075
22076fn vim_enabled(cx: &App) -> bool {
22077 vim_mode_setting::VimModeSetting::try_get(cx)
22078 .map(|vim_mode| vim_mode.0)
22079 .unwrap_or(false)
22080}
22081
22082fn process_completion_for_edit(
22083 completion: &Completion,
22084 intent: CompletionIntent,
22085 buffer: &Entity<Buffer>,
22086 cursor_position: &text::Anchor,
22087 cx: &mut Context<Editor>,
22088) -> CompletionEdit {
22089 let buffer = buffer.read(cx);
22090 let buffer_snapshot = buffer.snapshot();
22091 let (snippet, new_text) = if completion.is_snippet() {
22092 let mut snippet_source = completion.new_text.clone();
22093 // Workaround for typescript language server issues so that methods don't expand within
22094 // strings and functions with type expressions. The previous point is used because the query
22095 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22096 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22097 let previous_point = if previous_point.column > 0 {
22098 cursor_position.to_previous_offset(&buffer_snapshot)
22099 } else {
22100 cursor_position.to_offset(&buffer_snapshot)
22101 };
22102 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22103 && scope.prefers_label_for_snippet_in_completion()
22104 && let Some(label) = completion.label()
22105 && matches!(
22106 completion.kind(),
22107 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22108 )
22109 {
22110 snippet_source = label;
22111 }
22112 match Snippet::parse(&snippet_source).log_err() {
22113 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22114 None => (None, completion.new_text.clone()),
22115 }
22116 } else {
22117 (None, completion.new_text.clone())
22118 };
22119
22120 let mut range_to_replace = {
22121 let replace_range = &completion.replace_range;
22122 if let CompletionSource::Lsp {
22123 insert_range: Some(insert_range),
22124 ..
22125 } = &completion.source
22126 {
22127 debug_assert_eq!(
22128 insert_range.start, replace_range.start,
22129 "insert_range and replace_range should start at the same position"
22130 );
22131 debug_assert!(
22132 insert_range
22133 .start
22134 .cmp(cursor_position, &buffer_snapshot)
22135 .is_le(),
22136 "insert_range should start before or at cursor position"
22137 );
22138 debug_assert!(
22139 replace_range
22140 .start
22141 .cmp(cursor_position, &buffer_snapshot)
22142 .is_le(),
22143 "replace_range should start before or at cursor position"
22144 );
22145
22146 let should_replace = match intent {
22147 CompletionIntent::CompleteWithInsert => false,
22148 CompletionIntent::CompleteWithReplace => true,
22149 CompletionIntent::Complete | CompletionIntent::Compose => {
22150 let insert_mode =
22151 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22152 .completions
22153 .lsp_insert_mode;
22154 match insert_mode {
22155 LspInsertMode::Insert => false,
22156 LspInsertMode::Replace => true,
22157 LspInsertMode::ReplaceSubsequence => {
22158 let mut text_to_replace = buffer.chars_for_range(
22159 buffer.anchor_before(replace_range.start)
22160 ..buffer.anchor_after(replace_range.end),
22161 );
22162 let mut current_needle = text_to_replace.next();
22163 for haystack_ch in completion.label.text.chars() {
22164 if let Some(needle_ch) = current_needle
22165 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22166 {
22167 current_needle = text_to_replace.next();
22168 }
22169 }
22170 current_needle.is_none()
22171 }
22172 LspInsertMode::ReplaceSuffix => {
22173 if replace_range
22174 .end
22175 .cmp(cursor_position, &buffer_snapshot)
22176 .is_gt()
22177 {
22178 let range_after_cursor = *cursor_position..replace_range.end;
22179 let text_after_cursor = buffer
22180 .text_for_range(
22181 buffer.anchor_before(range_after_cursor.start)
22182 ..buffer.anchor_after(range_after_cursor.end),
22183 )
22184 .collect::<String>()
22185 .to_ascii_lowercase();
22186 completion
22187 .label
22188 .text
22189 .to_ascii_lowercase()
22190 .ends_with(&text_after_cursor)
22191 } else {
22192 true
22193 }
22194 }
22195 }
22196 }
22197 };
22198
22199 if should_replace {
22200 replace_range.clone()
22201 } else {
22202 insert_range.clone()
22203 }
22204 } else {
22205 replace_range.clone()
22206 }
22207 };
22208
22209 if range_to_replace
22210 .end
22211 .cmp(cursor_position, &buffer_snapshot)
22212 .is_lt()
22213 {
22214 range_to_replace.end = *cursor_position;
22215 }
22216
22217 CompletionEdit {
22218 new_text,
22219 replace_range: range_to_replace.to_offset(buffer),
22220 snippet,
22221 }
22222}
22223
22224struct CompletionEdit {
22225 new_text: String,
22226 replace_range: Range<usize>,
22227 snippet: Option<Snippet>,
22228}
22229
22230fn insert_extra_newline_brackets(
22231 buffer: &MultiBufferSnapshot,
22232 range: Range<usize>,
22233 language: &language::LanguageScope,
22234) -> bool {
22235 let leading_whitespace_len = buffer
22236 .reversed_chars_at(range.start)
22237 .take_while(|c| c.is_whitespace() && *c != '\n')
22238 .map(|c| c.len_utf8())
22239 .sum::<usize>();
22240 let trailing_whitespace_len = buffer
22241 .chars_at(range.end)
22242 .take_while(|c| c.is_whitespace() && *c != '\n')
22243 .map(|c| c.len_utf8())
22244 .sum::<usize>();
22245 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22246
22247 language.brackets().any(|(pair, enabled)| {
22248 let pair_start = pair.start.trim_end();
22249 let pair_end = pair.end.trim_start();
22250
22251 enabled
22252 && pair.newline
22253 && buffer.contains_str_at(range.end, pair_end)
22254 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22255 })
22256}
22257
22258fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22259 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22260 [(buffer, range, _)] => (*buffer, range.clone()),
22261 _ => return false,
22262 };
22263 let pair = {
22264 let mut result: Option<BracketMatch> = None;
22265
22266 for pair in buffer
22267 .all_bracket_ranges(range.clone())
22268 .filter(move |pair| {
22269 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22270 })
22271 {
22272 let len = pair.close_range.end - pair.open_range.start;
22273
22274 if let Some(existing) = &result {
22275 let existing_len = existing.close_range.end - existing.open_range.start;
22276 if len > existing_len {
22277 continue;
22278 }
22279 }
22280
22281 result = Some(pair);
22282 }
22283
22284 result
22285 };
22286 let Some(pair) = pair else {
22287 return false;
22288 };
22289 pair.newline_only
22290 && buffer
22291 .chars_for_range(pair.open_range.end..range.start)
22292 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22293 .all(|c| c.is_whitespace() && c != '\n')
22294}
22295
22296fn update_uncommitted_diff_for_buffer(
22297 editor: Entity<Editor>,
22298 project: &Entity<Project>,
22299 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22300 buffer: Entity<MultiBuffer>,
22301 cx: &mut App,
22302) -> Task<()> {
22303 let mut tasks = Vec::new();
22304 project.update(cx, |project, cx| {
22305 for buffer in buffers {
22306 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22307 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22308 }
22309 }
22310 });
22311 cx.spawn(async move |cx| {
22312 let diffs = future::join_all(tasks).await;
22313 if editor
22314 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22315 .unwrap_or(false)
22316 {
22317 return;
22318 }
22319
22320 buffer
22321 .update(cx, |buffer, cx| {
22322 for diff in diffs.into_iter().flatten() {
22323 buffer.add_diff(diff, cx);
22324 }
22325 })
22326 .ok();
22327 })
22328}
22329
22330fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22331 let tab_size = tab_size.get() as usize;
22332 let mut width = offset;
22333
22334 for ch in text.chars() {
22335 width += if ch == '\t' {
22336 tab_size - (width % tab_size)
22337 } else {
22338 1
22339 };
22340 }
22341
22342 width - offset
22343}
22344
22345#[cfg(test)]
22346mod tests {
22347 use super::*;
22348
22349 #[test]
22350 fn test_string_size_with_expanded_tabs() {
22351 let nz = |val| NonZeroU32::new(val).unwrap();
22352 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22353 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22354 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22355 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22356 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22357 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22358 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22359 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22360 }
22361}
22362
22363/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22364struct WordBreakingTokenizer<'a> {
22365 input: &'a str,
22366}
22367
22368impl<'a> WordBreakingTokenizer<'a> {
22369 fn new(input: &'a str) -> Self {
22370 Self { input }
22371 }
22372}
22373
22374fn is_char_ideographic(ch: char) -> bool {
22375 use unicode_script::Script::*;
22376 use unicode_script::UnicodeScript;
22377 matches!(ch.script(), Han | Tangut | Yi)
22378}
22379
22380fn is_grapheme_ideographic(text: &str) -> bool {
22381 text.chars().any(is_char_ideographic)
22382}
22383
22384fn is_grapheme_whitespace(text: &str) -> bool {
22385 text.chars().any(|x| x.is_whitespace())
22386}
22387
22388fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22389 text.chars()
22390 .next()
22391 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22392}
22393
22394#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22395enum WordBreakToken<'a> {
22396 Word { token: &'a str, grapheme_len: usize },
22397 InlineWhitespace { token: &'a str, grapheme_len: usize },
22398 Newline,
22399}
22400
22401impl<'a> Iterator for WordBreakingTokenizer<'a> {
22402 /// Yields a span, the count of graphemes in the token, and whether it was
22403 /// whitespace. Note that it also breaks at word boundaries.
22404 type Item = WordBreakToken<'a>;
22405
22406 fn next(&mut self) -> Option<Self::Item> {
22407 use unicode_segmentation::UnicodeSegmentation;
22408 if self.input.is_empty() {
22409 return None;
22410 }
22411
22412 let mut iter = self.input.graphemes(true).peekable();
22413 let mut offset = 0;
22414 let mut grapheme_len = 0;
22415 if let Some(first_grapheme) = iter.next() {
22416 let is_newline = first_grapheme == "\n";
22417 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22418 offset += first_grapheme.len();
22419 grapheme_len += 1;
22420 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22421 if let Some(grapheme) = iter.peek().copied()
22422 && should_stay_with_preceding_ideograph(grapheme)
22423 {
22424 offset += grapheme.len();
22425 grapheme_len += 1;
22426 }
22427 } else {
22428 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22429 let mut next_word_bound = words.peek().copied();
22430 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22431 next_word_bound = words.next();
22432 }
22433 while let Some(grapheme) = iter.peek().copied() {
22434 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22435 break;
22436 };
22437 if is_grapheme_whitespace(grapheme) != is_whitespace
22438 || (grapheme == "\n") != is_newline
22439 {
22440 break;
22441 };
22442 offset += grapheme.len();
22443 grapheme_len += 1;
22444 iter.next();
22445 }
22446 }
22447 let token = &self.input[..offset];
22448 self.input = &self.input[offset..];
22449 if token == "\n" {
22450 Some(WordBreakToken::Newline)
22451 } else if is_whitespace {
22452 Some(WordBreakToken::InlineWhitespace {
22453 token,
22454 grapheme_len,
22455 })
22456 } else {
22457 Some(WordBreakToken::Word {
22458 token,
22459 grapheme_len,
22460 })
22461 }
22462 } else {
22463 None
22464 }
22465 }
22466}
22467
22468#[test]
22469fn test_word_breaking_tokenizer() {
22470 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22471 ("", &[]),
22472 (" ", &[whitespace(" ", 2)]),
22473 ("Ʒ", &[word("Ʒ", 1)]),
22474 ("Ǽ", &[word("Ǽ", 1)]),
22475 ("⋑", &[word("⋑", 1)]),
22476 ("⋑⋑", &[word("⋑⋑", 2)]),
22477 (
22478 "原理,进而",
22479 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22480 ),
22481 (
22482 "hello world",
22483 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22484 ),
22485 (
22486 "hello, world",
22487 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22488 ),
22489 (
22490 " hello world",
22491 &[
22492 whitespace(" ", 2),
22493 word("hello", 5),
22494 whitespace(" ", 1),
22495 word("world", 5),
22496 ],
22497 ),
22498 (
22499 "这是什么 \n 钢笔",
22500 &[
22501 word("这", 1),
22502 word("是", 1),
22503 word("什", 1),
22504 word("么", 1),
22505 whitespace(" ", 1),
22506 newline(),
22507 whitespace(" ", 1),
22508 word("钢", 1),
22509 word("笔", 1),
22510 ],
22511 ),
22512 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22513 ];
22514
22515 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22516 WordBreakToken::Word {
22517 token,
22518 grapheme_len,
22519 }
22520 }
22521
22522 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22523 WordBreakToken::InlineWhitespace {
22524 token,
22525 grapheme_len,
22526 }
22527 }
22528
22529 fn newline() -> WordBreakToken<'static> {
22530 WordBreakToken::Newline
22531 }
22532
22533 for (input, result) in tests {
22534 assert_eq!(
22535 WordBreakingTokenizer::new(input)
22536 .collect::<Vec<_>>()
22537 .as_slice(),
22538 *result,
22539 );
22540 }
22541}
22542
22543fn wrap_with_prefix(
22544 first_line_prefix: String,
22545 subsequent_lines_prefix: String,
22546 unwrapped_text: String,
22547 wrap_column: usize,
22548 tab_size: NonZeroU32,
22549 preserve_existing_whitespace: bool,
22550) -> String {
22551 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22552 let subsequent_lines_prefix_len =
22553 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22554 let mut wrapped_text = String::new();
22555 let mut current_line = first_line_prefix;
22556 let mut is_first_line = true;
22557
22558 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22559 let mut current_line_len = first_line_prefix_len;
22560 let mut in_whitespace = false;
22561 for token in tokenizer {
22562 let have_preceding_whitespace = in_whitespace;
22563 match token {
22564 WordBreakToken::Word {
22565 token,
22566 grapheme_len,
22567 } => {
22568 in_whitespace = false;
22569 let current_prefix_len = if is_first_line {
22570 first_line_prefix_len
22571 } else {
22572 subsequent_lines_prefix_len
22573 };
22574 if current_line_len + grapheme_len > wrap_column
22575 && current_line_len != current_prefix_len
22576 {
22577 wrapped_text.push_str(current_line.trim_end());
22578 wrapped_text.push('\n');
22579 is_first_line = false;
22580 current_line = subsequent_lines_prefix.clone();
22581 current_line_len = subsequent_lines_prefix_len;
22582 }
22583 current_line.push_str(token);
22584 current_line_len += grapheme_len;
22585 }
22586 WordBreakToken::InlineWhitespace {
22587 mut token,
22588 mut grapheme_len,
22589 } => {
22590 in_whitespace = true;
22591 if have_preceding_whitespace && !preserve_existing_whitespace {
22592 continue;
22593 }
22594 if !preserve_existing_whitespace {
22595 // Keep a single whitespace grapheme as-is
22596 if let Some(first) =
22597 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22598 {
22599 token = first;
22600 } else {
22601 token = " ";
22602 }
22603 grapheme_len = 1;
22604 }
22605 let current_prefix_len = if is_first_line {
22606 first_line_prefix_len
22607 } else {
22608 subsequent_lines_prefix_len
22609 };
22610 if current_line_len + grapheme_len > wrap_column {
22611 wrapped_text.push_str(current_line.trim_end());
22612 wrapped_text.push('\n');
22613 is_first_line = false;
22614 current_line = subsequent_lines_prefix.clone();
22615 current_line_len = subsequent_lines_prefix_len;
22616 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22617 current_line.push_str(token);
22618 current_line_len += grapheme_len;
22619 }
22620 }
22621 WordBreakToken::Newline => {
22622 in_whitespace = true;
22623 let current_prefix_len = if is_first_line {
22624 first_line_prefix_len
22625 } else {
22626 subsequent_lines_prefix_len
22627 };
22628 if preserve_existing_whitespace {
22629 wrapped_text.push_str(current_line.trim_end());
22630 wrapped_text.push('\n');
22631 is_first_line = false;
22632 current_line = subsequent_lines_prefix.clone();
22633 current_line_len = subsequent_lines_prefix_len;
22634 } else if have_preceding_whitespace {
22635 continue;
22636 } else if current_line_len + 1 > wrap_column
22637 && current_line_len != current_prefix_len
22638 {
22639 wrapped_text.push_str(current_line.trim_end());
22640 wrapped_text.push('\n');
22641 is_first_line = false;
22642 current_line = subsequent_lines_prefix.clone();
22643 current_line_len = subsequent_lines_prefix_len;
22644 } else if current_line_len != current_prefix_len {
22645 current_line.push(' ');
22646 current_line_len += 1;
22647 }
22648 }
22649 }
22650 }
22651
22652 if !current_line.is_empty() {
22653 wrapped_text.push_str(¤t_line);
22654 }
22655 wrapped_text
22656}
22657
22658#[test]
22659fn test_wrap_with_prefix() {
22660 assert_eq!(
22661 wrap_with_prefix(
22662 "# ".to_string(),
22663 "# ".to_string(),
22664 "abcdefg".to_string(),
22665 4,
22666 NonZeroU32::new(4).unwrap(),
22667 false,
22668 ),
22669 "# abcdefg"
22670 );
22671 assert_eq!(
22672 wrap_with_prefix(
22673 "".to_string(),
22674 "".to_string(),
22675 "\thello world".to_string(),
22676 8,
22677 NonZeroU32::new(4).unwrap(),
22678 false,
22679 ),
22680 "hello\nworld"
22681 );
22682 assert_eq!(
22683 wrap_with_prefix(
22684 "// ".to_string(),
22685 "// ".to_string(),
22686 "xx \nyy zz aa bb cc".to_string(),
22687 12,
22688 NonZeroU32::new(4).unwrap(),
22689 false,
22690 ),
22691 "// xx yy zz\n// aa bb cc"
22692 );
22693 assert_eq!(
22694 wrap_with_prefix(
22695 String::new(),
22696 String::new(),
22697 "这是什么 \n 钢笔".to_string(),
22698 3,
22699 NonZeroU32::new(4).unwrap(),
22700 false,
22701 ),
22702 "这是什\n么 钢\n笔"
22703 );
22704 assert_eq!(
22705 wrap_with_prefix(
22706 String::new(),
22707 String::new(),
22708 format!("foo{}bar", '\u{2009}'), // thin space
22709 80,
22710 NonZeroU32::new(4).unwrap(),
22711 false,
22712 ),
22713 format!("foo{}bar", '\u{2009}')
22714 );
22715}
22716
22717pub trait CollaborationHub {
22718 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22719 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22720 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22721}
22722
22723impl CollaborationHub for Entity<Project> {
22724 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22725 self.read(cx).collaborators()
22726 }
22727
22728 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22729 self.read(cx).user_store().read(cx).participant_indices()
22730 }
22731
22732 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22733 let this = self.read(cx);
22734 let user_ids = this.collaborators().values().map(|c| c.user_id);
22735 this.user_store().read(cx).participant_names(user_ids, cx)
22736 }
22737}
22738
22739pub trait SemanticsProvider {
22740 fn hover(
22741 &self,
22742 buffer: &Entity<Buffer>,
22743 position: text::Anchor,
22744 cx: &mut App,
22745 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22746
22747 fn inline_values(
22748 &self,
22749 buffer_handle: Entity<Buffer>,
22750 range: Range<text::Anchor>,
22751 cx: &mut App,
22752 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22753
22754 fn inlay_hints(
22755 &self,
22756 buffer_handle: Entity<Buffer>,
22757 range: Range<text::Anchor>,
22758 cx: &mut App,
22759 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22760
22761 fn resolve_inlay_hint(
22762 &self,
22763 hint: InlayHint,
22764 buffer_handle: Entity<Buffer>,
22765 server_id: LanguageServerId,
22766 cx: &mut App,
22767 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22768
22769 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22770
22771 fn document_highlights(
22772 &self,
22773 buffer: &Entity<Buffer>,
22774 position: text::Anchor,
22775 cx: &mut App,
22776 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22777
22778 fn definitions(
22779 &self,
22780 buffer: &Entity<Buffer>,
22781 position: text::Anchor,
22782 kind: GotoDefinitionKind,
22783 cx: &mut App,
22784 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22785
22786 fn range_for_rename(
22787 &self,
22788 buffer: &Entity<Buffer>,
22789 position: text::Anchor,
22790 cx: &mut App,
22791 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22792
22793 fn perform_rename(
22794 &self,
22795 buffer: &Entity<Buffer>,
22796 position: text::Anchor,
22797 new_name: String,
22798 cx: &mut App,
22799 ) -> Option<Task<Result<ProjectTransaction>>>;
22800}
22801
22802pub trait CompletionProvider {
22803 fn completions(
22804 &self,
22805 excerpt_id: ExcerptId,
22806 buffer: &Entity<Buffer>,
22807 buffer_position: text::Anchor,
22808 trigger: CompletionContext,
22809 window: &mut Window,
22810 cx: &mut Context<Editor>,
22811 ) -> Task<Result<Vec<CompletionResponse>>>;
22812
22813 fn resolve_completions(
22814 &self,
22815 _buffer: Entity<Buffer>,
22816 _completion_indices: Vec<usize>,
22817 _completions: Rc<RefCell<Box<[Completion]>>>,
22818 _cx: &mut Context<Editor>,
22819 ) -> Task<Result<bool>> {
22820 Task::ready(Ok(false))
22821 }
22822
22823 fn apply_additional_edits_for_completion(
22824 &self,
22825 _buffer: Entity<Buffer>,
22826 _completions: Rc<RefCell<Box<[Completion]>>>,
22827 _completion_index: usize,
22828 _push_to_history: bool,
22829 _cx: &mut Context<Editor>,
22830 ) -> Task<Result<Option<language::Transaction>>> {
22831 Task::ready(Ok(None))
22832 }
22833
22834 fn is_completion_trigger(
22835 &self,
22836 buffer: &Entity<Buffer>,
22837 position: language::Anchor,
22838 text: &str,
22839 trigger_in_words: bool,
22840 menu_is_open: bool,
22841 cx: &mut Context<Editor>,
22842 ) -> bool;
22843
22844 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22845
22846 fn sort_completions(&self) -> bool {
22847 true
22848 }
22849
22850 fn filter_completions(&self) -> bool {
22851 true
22852 }
22853}
22854
22855pub trait CodeActionProvider {
22856 fn id(&self) -> Arc<str>;
22857
22858 fn code_actions(
22859 &self,
22860 buffer: &Entity<Buffer>,
22861 range: Range<text::Anchor>,
22862 window: &mut Window,
22863 cx: &mut App,
22864 ) -> Task<Result<Vec<CodeAction>>>;
22865
22866 fn apply_code_action(
22867 &self,
22868 buffer_handle: Entity<Buffer>,
22869 action: CodeAction,
22870 excerpt_id: ExcerptId,
22871 push_to_history: bool,
22872 window: &mut Window,
22873 cx: &mut App,
22874 ) -> Task<Result<ProjectTransaction>>;
22875}
22876
22877impl CodeActionProvider for Entity<Project> {
22878 fn id(&self) -> Arc<str> {
22879 "project".into()
22880 }
22881
22882 fn code_actions(
22883 &self,
22884 buffer: &Entity<Buffer>,
22885 range: Range<text::Anchor>,
22886 _window: &mut Window,
22887 cx: &mut App,
22888 ) -> Task<Result<Vec<CodeAction>>> {
22889 self.update(cx, |project, cx| {
22890 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22891 let code_actions = project.code_actions(buffer, range, None, cx);
22892 cx.background_spawn(async move {
22893 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22894 Ok(code_lens_actions
22895 .context("code lens fetch")?
22896 .into_iter()
22897 .flatten()
22898 .chain(
22899 code_actions
22900 .context("code action fetch")?
22901 .into_iter()
22902 .flatten(),
22903 )
22904 .collect())
22905 })
22906 })
22907 }
22908
22909 fn apply_code_action(
22910 &self,
22911 buffer_handle: Entity<Buffer>,
22912 action: CodeAction,
22913 _excerpt_id: ExcerptId,
22914 push_to_history: bool,
22915 _window: &mut Window,
22916 cx: &mut App,
22917 ) -> Task<Result<ProjectTransaction>> {
22918 self.update(cx, |project, cx| {
22919 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22920 })
22921 }
22922}
22923
22924fn snippet_completions(
22925 project: &Project,
22926 buffer: &Entity<Buffer>,
22927 buffer_position: text::Anchor,
22928 cx: &mut App,
22929) -> Task<Result<CompletionResponse>> {
22930 let languages = buffer.read(cx).languages_at(buffer_position);
22931 let snippet_store = project.snippets().read(cx);
22932
22933 let scopes: Vec<_> = languages
22934 .iter()
22935 .filter_map(|language| {
22936 let language_name = language.lsp_id();
22937 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22938
22939 if snippets.is_empty() {
22940 None
22941 } else {
22942 Some((language.default_scope(), snippets))
22943 }
22944 })
22945 .collect();
22946
22947 if scopes.is_empty() {
22948 return Task::ready(Ok(CompletionResponse {
22949 completions: vec![],
22950 display_options: CompletionDisplayOptions::default(),
22951 is_incomplete: false,
22952 }));
22953 }
22954
22955 let snapshot = buffer.read(cx).text_snapshot();
22956 let executor = cx.background_executor().clone();
22957
22958 cx.background_spawn(async move {
22959 let mut is_incomplete = false;
22960 let mut completions: Vec<Completion> = Vec::new();
22961 for (scope, snippets) in scopes.into_iter() {
22962 let classifier =
22963 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22964
22965 const MAX_WORD_PREFIX_LEN: usize = 128;
22966 let last_word: String = snapshot
22967 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22968 .take(MAX_WORD_PREFIX_LEN)
22969 .take_while(|c| classifier.is_word(*c))
22970 .collect::<String>()
22971 .chars()
22972 .rev()
22973 .collect();
22974
22975 if last_word.is_empty() {
22976 return Ok(CompletionResponse {
22977 completions: vec![],
22978 display_options: CompletionDisplayOptions::default(),
22979 is_incomplete: true,
22980 });
22981 }
22982
22983 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22984 let to_lsp = |point: &text::Anchor| {
22985 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22986 point_to_lsp(end)
22987 };
22988 let lsp_end = to_lsp(&buffer_position);
22989
22990 let candidates = snippets
22991 .iter()
22992 .enumerate()
22993 .flat_map(|(ix, snippet)| {
22994 snippet
22995 .prefix
22996 .iter()
22997 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22998 })
22999 .collect::<Vec<StringMatchCandidate>>();
23000
23001 const MAX_RESULTS: usize = 100;
23002 let mut matches = fuzzy::match_strings(
23003 &candidates,
23004 &last_word,
23005 last_word.chars().any(|c| c.is_uppercase()),
23006 true,
23007 MAX_RESULTS,
23008 &Default::default(),
23009 executor.clone(),
23010 )
23011 .await;
23012
23013 if matches.len() >= MAX_RESULTS {
23014 is_incomplete = true;
23015 }
23016
23017 // Remove all candidates where the query's start does not match the start of any word in the candidate
23018 if let Some(query_start) = last_word.chars().next() {
23019 matches.retain(|string_match| {
23020 split_words(&string_match.string).any(|word| {
23021 // Check that the first codepoint of the word as lowercase matches the first
23022 // codepoint of the query as lowercase
23023 word.chars()
23024 .flat_map(|codepoint| codepoint.to_lowercase())
23025 .zip(query_start.to_lowercase())
23026 .all(|(word_cp, query_cp)| word_cp == query_cp)
23027 })
23028 });
23029 }
23030
23031 let matched_strings = matches
23032 .into_iter()
23033 .map(|m| m.string)
23034 .collect::<HashSet<_>>();
23035
23036 completions.extend(snippets.iter().filter_map(|snippet| {
23037 let matching_prefix = snippet
23038 .prefix
23039 .iter()
23040 .find(|prefix| matched_strings.contains(*prefix))?;
23041 let start = as_offset - last_word.len();
23042 let start = snapshot.anchor_before(start);
23043 let range = start..buffer_position;
23044 let lsp_start = to_lsp(&start);
23045 let lsp_range = lsp::Range {
23046 start: lsp_start,
23047 end: lsp_end,
23048 };
23049 Some(Completion {
23050 replace_range: range,
23051 new_text: snippet.body.clone(),
23052 source: CompletionSource::Lsp {
23053 insert_range: None,
23054 server_id: LanguageServerId(usize::MAX),
23055 resolved: true,
23056 lsp_completion: Box::new(lsp::CompletionItem {
23057 label: snippet.prefix.first().unwrap().clone(),
23058 kind: Some(CompletionItemKind::SNIPPET),
23059 label_details: snippet.description.as_ref().map(|description| {
23060 lsp::CompletionItemLabelDetails {
23061 detail: Some(description.clone()),
23062 description: None,
23063 }
23064 }),
23065 insert_text_format: Some(InsertTextFormat::SNIPPET),
23066 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23067 lsp::InsertReplaceEdit {
23068 new_text: snippet.body.clone(),
23069 insert: lsp_range,
23070 replace: lsp_range,
23071 },
23072 )),
23073 filter_text: Some(snippet.body.clone()),
23074 sort_text: Some(char::MAX.to_string()),
23075 ..lsp::CompletionItem::default()
23076 }),
23077 lsp_defaults: None,
23078 },
23079 label: CodeLabel {
23080 text: matching_prefix.clone(),
23081 runs: Vec::new(),
23082 filter_range: 0..matching_prefix.len(),
23083 },
23084 icon_path: None,
23085 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23086 single_line: snippet.name.clone().into(),
23087 plain_text: snippet
23088 .description
23089 .clone()
23090 .map(|description| description.into()),
23091 }),
23092 insert_text_mode: None,
23093 confirm: None,
23094 })
23095 }))
23096 }
23097
23098 Ok(CompletionResponse {
23099 completions,
23100 display_options: CompletionDisplayOptions::default(),
23101 is_incomplete,
23102 })
23103 })
23104}
23105
23106impl CompletionProvider for Entity<Project> {
23107 fn completions(
23108 &self,
23109 _excerpt_id: ExcerptId,
23110 buffer: &Entity<Buffer>,
23111 buffer_position: text::Anchor,
23112 options: CompletionContext,
23113 _window: &mut Window,
23114 cx: &mut Context<Editor>,
23115 ) -> Task<Result<Vec<CompletionResponse>>> {
23116 self.update(cx, |project, cx| {
23117 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23118 let project_completions = project.completions(buffer, buffer_position, options, cx);
23119 cx.background_spawn(async move {
23120 let mut responses = project_completions.await?;
23121 let snippets = snippets.await?;
23122 if !snippets.completions.is_empty() {
23123 responses.push(snippets);
23124 }
23125 Ok(responses)
23126 })
23127 })
23128 }
23129
23130 fn resolve_completions(
23131 &self,
23132 buffer: Entity<Buffer>,
23133 completion_indices: Vec<usize>,
23134 completions: Rc<RefCell<Box<[Completion]>>>,
23135 cx: &mut Context<Editor>,
23136 ) -> Task<Result<bool>> {
23137 self.update(cx, |project, cx| {
23138 project.lsp_store().update(cx, |lsp_store, cx| {
23139 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23140 })
23141 })
23142 }
23143
23144 fn apply_additional_edits_for_completion(
23145 &self,
23146 buffer: Entity<Buffer>,
23147 completions: Rc<RefCell<Box<[Completion]>>>,
23148 completion_index: usize,
23149 push_to_history: bool,
23150 cx: &mut Context<Editor>,
23151 ) -> Task<Result<Option<language::Transaction>>> {
23152 self.update(cx, |project, cx| {
23153 project.lsp_store().update(cx, |lsp_store, cx| {
23154 lsp_store.apply_additional_edits_for_completion(
23155 buffer,
23156 completions,
23157 completion_index,
23158 push_to_history,
23159 cx,
23160 )
23161 })
23162 })
23163 }
23164
23165 fn is_completion_trigger(
23166 &self,
23167 buffer: &Entity<Buffer>,
23168 position: language::Anchor,
23169 text: &str,
23170 trigger_in_words: bool,
23171 menu_is_open: bool,
23172 cx: &mut Context<Editor>,
23173 ) -> bool {
23174 let mut chars = text.chars();
23175 let char = if let Some(char) = chars.next() {
23176 char
23177 } else {
23178 return false;
23179 };
23180 if chars.next().is_some() {
23181 return false;
23182 }
23183
23184 let buffer = buffer.read(cx);
23185 let snapshot = buffer.snapshot();
23186 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23187 return false;
23188 }
23189 let classifier = snapshot
23190 .char_classifier_at(position)
23191 .scope_context(Some(CharScopeContext::Completion));
23192 if trigger_in_words && classifier.is_word(char) {
23193 return true;
23194 }
23195
23196 buffer.completion_triggers().contains(text)
23197 }
23198}
23199
23200impl SemanticsProvider for Entity<Project> {
23201 fn hover(
23202 &self,
23203 buffer: &Entity<Buffer>,
23204 position: text::Anchor,
23205 cx: &mut App,
23206 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23207 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23208 }
23209
23210 fn document_highlights(
23211 &self,
23212 buffer: &Entity<Buffer>,
23213 position: text::Anchor,
23214 cx: &mut App,
23215 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23216 Some(self.update(cx, |project, cx| {
23217 project.document_highlights(buffer, position, cx)
23218 }))
23219 }
23220
23221 fn definitions(
23222 &self,
23223 buffer: &Entity<Buffer>,
23224 position: text::Anchor,
23225 kind: GotoDefinitionKind,
23226 cx: &mut App,
23227 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23228 Some(self.update(cx, |project, cx| match kind {
23229 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23230 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23231 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23232 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23233 }))
23234 }
23235
23236 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23237 self.update(cx, |project, cx| {
23238 if project
23239 .active_debug_session(cx)
23240 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23241 {
23242 return true;
23243 }
23244
23245 buffer.update(cx, |buffer, cx| {
23246 project.any_language_server_supports_inlay_hints(buffer, cx)
23247 })
23248 })
23249 }
23250
23251 fn inline_values(
23252 &self,
23253 buffer_handle: Entity<Buffer>,
23254 range: Range<text::Anchor>,
23255 cx: &mut App,
23256 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23257 self.update(cx, |project, cx| {
23258 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23259
23260 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23261 })
23262 }
23263
23264 fn inlay_hints(
23265 &self,
23266 buffer_handle: Entity<Buffer>,
23267 range: Range<text::Anchor>,
23268 cx: &mut App,
23269 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23270 Some(self.update(cx, |project, cx| {
23271 project.inlay_hints(buffer_handle, range, cx)
23272 }))
23273 }
23274
23275 fn resolve_inlay_hint(
23276 &self,
23277 hint: InlayHint,
23278 buffer_handle: Entity<Buffer>,
23279 server_id: LanguageServerId,
23280 cx: &mut App,
23281 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23282 Some(self.update(cx, |project, cx| {
23283 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23284 }))
23285 }
23286
23287 fn range_for_rename(
23288 &self,
23289 buffer: &Entity<Buffer>,
23290 position: text::Anchor,
23291 cx: &mut App,
23292 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23293 Some(self.update(cx, |project, cx| {
23294 let buffer = buffer.clone();
23295 let task = project.prepare_rename(buffer.clone(), position, cx);
23296 cx.spawn(async move |_, cx| {
23297 Ok(match task.await? {
23298 PrepareRenameResponse::Success(range) => Some(range),
23299 PrepareRenameResponse::InvalidPosition => None,
23300 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23301 // Fallback on using TreeSitter info to determine identifier range
23302 buffer.read_with(cx, |buffer, _| {
23303 let snapshot = buffer.snapshot();
23304 let (range, kind) = snapshot.surrounding_word(position, None);
23305 if kind != Some(CharKind::Word) {
23306 return None;
23307 }
23308 Some(
23309 snapshot.anchor_before(range.start)
23310 ..snapshot.anchor_after(range.end),
23311 )
23312 })?
23313 }
23314 })
23315 })
23316 }))
23317 }
23318
23319 fn perform_rename(
23320 &self,
23321 buffer: &Entity<Buffer>,
23322 position: text::Anchor,
23323 new_name: String,
23324 cx: &mut App,
23325 ) -> Option<Task<Result<ProjectTransaction>>> {
23326 Some(self.update(cx, |project, cx| {
23327 project.perform_rename(buffer.clone(), position, new_name, cx)
23328 }))
23329 }
23330}
23331
23332fn inlay_hint_settings(
23333 location: Anchor,
23334 snapshot: &MultiBufferSnapshot,
23335 cx: &mut Context<Editor>,
23336) -> InlayHintSettings {
23337 let file = snapshot.file_at(location);
23338 let language = snapshot.language_at(location).map(|l| l.name());
23339 language_settings(language, file, cx).inlay_hints
23340}
23341
23342fn consume_contiguous_rows(
23343 contiguous_row_selections: &mut Vec<Selection<Point>>,
23344 selection: &Selection<Point>,
23345 display_map: &DisplaySnapshot,
23346 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23347) -> (MultiBufferRow, MultiBufferRow) {
23348 contiguous_row_selections.push(selection.clone());
23349 let start_row = starting_row(selection, display_map);
23350 let mut end_row = ending_row(selection, display_map);
23351
23352 while let Some(next_selection) = selections.peek() {
23353 if next_selection.start.row <= end_row.0 {
23354 end_row = ending_row(next_selection, display_map);
23355 contiguous_row_selections.push(selections.next().unwrap().clone());
23356 } else {
23357 break;
23358 }
23359 }
23360 (start_row, end_row)
23361}
23362
23363fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23364 if selection.start.column > 0 {
23365 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23366 } else {
23367 MultiBufferRow(selection.start.row)
23368 }
23369}
23370
23371fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23372 if next_selection.end.column > 0 || next_selection.is_empty() {
23373 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23374 } else {
23375 MultiBufferRow(next_selection.end.row)
23376 }
23377}
23378
23379impl EditorSnapshot {
23380 pub fn remote_selections_in_range<'a>(
23381 &'a self,
23382 range: &'a Range<Anchor>,
23383 collaboration_hub: &dyn CollaborationHub,
23384 cx: &'a App,
23385 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23386 let participant_names = collaboration_hub.user_names(cx);
23387 let participant_indices = collaboration_hub.user_participant_indices(cx);
23388 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23389 let collaborators_by_replica_id = collaborators_by_peer_id
23390 .values()
23391 .map(|collaborator| (collaborator.replica_id, collaborator))
23392 .collect::<HashMap<_, _>>();
23393 self.buffer_snapshot()
23394 .selections_in_range(range, false)
23395 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23396 if replica_id == AGENT_REPLICA_ID {
23397 Some(RemoteSelection {
23398 replica_id,
23399 selection,
23400 cursor_shape,
23401 line_mode,
23402 collaborator_id: CollaboratorId::Agent,
23403 user_name: Some("Agent".into()),
23404 color: cx.theme().players().agent(),
23405 })
23406 } else {
23407 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23408 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23409 let user_name = participant_names.get(&collaborator.user_id).cloned();
23410 Some(RemoteSelection {
23411 replica_id,
23412 selection,
23413 cursor_shape,
23414 line_mode,
23415 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23416 user_name,
23417 color: if let Some(index) = participant_index {
23418 cx.theme().players().color_for_participant(index.0)
23419 } else {
23420 cx.theme().players().absent()
23421 },
23422 })
23423 }
23424 })
23425 }
23426
23427 pub fn hunks_for_ranges(
23428 &self,
23429 ranges: impl IntoIterator<Item = Range<Point>>,
23430 ) -> Vec<MultiBufferDiffHunk> {
23431 let mut hunks = Vec::new();
23432 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23433 HashMap::default();
23434 for query_range in ranges {
23435 let query_rows =
23436 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23437 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23438 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23439 ) {
23440 // Include deleted hunks that are adjacent to the query range, because
23441 // otherwise they would be missed.
23442 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23443 if hunk.status().is_deleted() {
23444 intersects_range |= hunk.row_range.start == query_rows.end;
23445 intersects_range |= hunk.row_range.end == query_rows.start;
23446 }
23447 if intersects_range {
23448 if !processed_buffer_rows
23449 .entry(hunk.buffer_id)
23450 .or_default()
23451 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23452 {
23453 continue;
23454 }
23455 hunks.push(hunk);
23456 }
23457 }
23458 }
23459
23460 hunks
23461 }
23462
23463 fn display_diff_hunks_for_rows<'a>(
23464 &'a self,
23465 display_rows: Range<DisplayRow>,
23466 folded_buffers: &'a HashSet<BufferId>,
23467 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23468 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23469 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23470
23471 self.buffer_snapshot()
23472 .diff_hunks_in_range(buffer_start..buffer_end)
23473 .filter_map(|hunk| {
23474 if folded_buffers.contains(&hunk.buffer_id) {
23475 return None;
23476 }
23477
23478 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23479 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23480
23481 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23482 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23483
23484 let display_hunk = if hunk_display_start.column() != 0 {
23485 DisplayDiffHunk::Folded {
23486 display_row: hunk_display_start.row(),
23487 }
23488 } else {
23489 let mut end_row = hunk_display_end.row();
23490 if hunk_display_end.column() > 0 {
23491 end_row.0 += 1;
23492 }
23493 let is_created_file = hunk.is_created_file();
23494 DisplayDiffHunk::Unfolded {
23495 status: hunk.status(),
23496 diff_base_byte_range: hunk.diff_base_byte_range,
23497 display_row_range: hunk_display_start.row()..end_row,
23498 multi_buffer_range: Anchor::range_in_buffer(
23499 hunk.excerpt_id,
23500 hunk.buffer_id,
23501 hunk.buffer_range,
23502 ),
23503 is_created_file,
23504 }
23505 };
23506
23507 Some(display_hunk)
23508 })
23509 }
23510
23511 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23512 self.display_snapshot
23513 .buffer_snapshot()
23514 .language_at(position)
23515 }
23516
23517 pub fn is_focused(&self) -> bool {
23518 self.is_focused
23519 }
23520
23521 pub fn placeholder_text(&self) -> Option<String> {
23522 self.placeholder_display_snapshot
23523 .as_ref()
23524 .map(|display_map| display_map.text())
23525 }
23526
23527 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23528 self.scroll_anchor.scroll_position(&self.display_snapshot)
23529 }
23530
23531 fn gutter_dimensions(
23532 &self,
23533 font_id: FontId,
23534 font_size: Pixels,
23535 max_line_number_width: Pixels,
23536 cx: &App,
23537 ) -> Option<GutterDimensions> {
23538 if !self.show_gutter {
23539 return None;
23540 }
23541
23542 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23543 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23544
23545 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23546 matches!(
23547 ProjectSettings::get_global(cx).git.git_gutter,
23548 GitGutterSetting::TrackedFiles
23549 )
23550 });
23551 let gutter_settings = EditorSettings::get_global(cx).gutter;
23552 let show_line_numbers = self
23553 .show_line_numbers
23554 .unwrap_or(gutter_settings.line_numbers);
23555 let line_gutter_width = if show_line_numbers {
23556 // Avoid flicker-like gutter resizes when the line number gains another digit by
23557 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23558 let min_width_for_number_on_gutter =
23559 ch_advance * gutter_settings.min_line_number_digits as f32;
23560 max_line_number_width.max(min_width_for_number_on_gutter)
23561 } else {
23562 0.0.into()
23563 };
23564
23565 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23566 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23567
23568 let git_blame_entries_width =
23569 self.git_blame_gutter_max_author_length
23570 .map(|max_author_length| {
23571 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23572 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23573
23574 /// The number of characters to dedicate to gaps and margins.
23575 const SPACING_WIDTH: usize = 4;
23576
23577 let max_char_count = max_author_length.min(renderer.max_author_length())
23578 + ::git::SHORT_SHA_LENGTH
23579 + MAX_RELATIVE_TIMESTAMP.len()
23580 + SPACING_WIDTH;
23581
23582 ch_advance * max_char_count
23583 });
23584
23585 let is_singleton = self.buffer_snapshot().is_singleton();
23586
23587 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23588 left_padding += if !is_singleton {
23589 ch_width * 4.0
23590 } else if show_runnables || show_breakpoints {
23591 ch_width * 3.0
23592 } else if show_git_gutter && show_line_numbers {
23593 ch_width * 2.0
23594 } else if show_git_gutter || show_line_numbers {
23595 ch_width
23596 } else {
23597 px(0.)
23598 };
23599
23600 let shows_folds = is_singleton && gutter_settings.folds;
23601
23602 let right_padding = if shows_folds && show_line_numbers {
23603 ch_width * 4.0
23604 } else if shows_folds || (!is_singleton && show_line_numbers) {
23605 ch_width * 3.0
23606 } else if show_line_numbers {
23607 ch_width
23608 } else {
23609 px(0.)
23610 };
23611
23612 Some(GutterDimensions {
23613 left_padding,
23614 right_padding,
23615 width: line_gutter_width + left_padding + right_padding,
23616 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23617 git_blame_entries_width,
23618 })
23619 }
23620
23621 pub fn render_crease_toggle(
23622 &self,
23623 buffer_row: MultiBufferRow,
23624 row_contains_cursor: bool,
23625 editor: Entity<Editor>,
23626 window: &mut Window,
23627 cx: &mut App,
23628 ) -> Option<AnyElement> {
23629 let folded = self.is_line_folded(buffer_row);
23630 let mut is_foldable = false;
23631
23632 if let Some(crease) = self
23633 .crease_snapshot
23634 .query_row(buffer_row, self.buffer_snapshot())
23635 {
23636 is_foldable = true;
23637 match crease {
23638 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23639 if let Some(render_toggle) = render_toggle {
23640 let toggle_callback =
23641 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23642 if folded {
23643 editor.update(cx, |editor, cx| {
23644 editor.fold_at(buffer_row, window, cx)
23645 });
23646 } else {
23647 editor.update(cx, |editor, cx| {
23648 editor.unfold_at(buffer_row, window, cx)
23649 });
23650 }
23651 });
23652 return Some((render_toggle)(
23653 buffer_row,
23654 folded,
23655 toggle_callback,
23656 window,
23657 cx,
23658 ));
23659 }
23660 }
23661 }
23662 }
23663
23664 is_foldable |= self.starts_indent(buffer_row);
23665
23666 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23667 Some(
23668 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23669 .toggle_state(folded)
23670 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23671 if folded {
23672 this.unfold_at(buffer_row, window, cx);
23673 } else {
23674 this.fold_at(buffer_row, window, cx);
23675 }
23676 }))
23677 .into_any_element(),
23678 )
23679 } else {
23680 None
23681 }
23682 }
23683
23684 pub fn render_crease_trailer(
23685 &self,
23686 buffer_row: MultiBufferRow,
23687 window: &mut Window,
23688 cx: &mut App,
23689 ) -> Option<AnyElement> {
23690 let folded = self.is_line_folded(buffer_row);
23691 if let Crease::Inline { render_trailer, .. } = self
23692 .crease_snapshot
23693 .query_row(buffer_row, self.buffer_snapshot())?
23694 {
23695 let render_trailer = render_trailer.as_ref()?;
23696 Some(render_trailer(buffer_row, folded, window, cx))
23697 } else {
23698 None
23699 }
23700 }
23701}
23702
23703impl Deref for EditorSnapshot {
23704 type Target = DisplaySnapshot;
23705
23706 fn deref(&self) -> &Self::Target {
23707 &self.display_snapshot
23708 }
23709}
23710
23711#[derive(Clone, Debug, PartialEq, Eq)]
23712pub enum EditorEvent {
23713 InputIgnored {
23714 text: Arc<str>,
23715 },
23716 InputHandled {
23717 utf16_range_to_replace: Option<Range<isize>>,
23718 text: Arc<str>,
23719 },
23720 ExcerptsAdded {
23721 buffer: Entity<Buffer>,
23722 predecessor: ExcerptId,
23723 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23724 },
23725 ExcerptsRemoved {
23726 ids: Vec<ExcerptId>,
23727 removed_buffer_ids: Vec<BufferId>,
23728 },
23729 BufferFoldToggled {
23730 ids: Vec<ExcerptId>,
23731 folded: bool,
23732 },
23733 ExcerptsEdited {
23734 ids: Vec<ExcerptId>,
23735 },
23736 ExcerptsExpanded {
23737 ids: Vec<ExcerptId>,
23738 },
23739 BufferEdited,
23740 Edited {
23741 transaction_id: clock::Lamport,
23742 },
23743 Reparsed(BufferId),
23744 Focused,
23745 FocusedIn,
23746 Blurred,
23747 DirtyChanged,
23748 Saved,
23749 TitleChanged,
23750 SelectionsChanged {
23751 local: bool,
23752 },
23753 ScrollPositionChanged {
23754 local: bool,
23755 autoscroll: bool,
23756 },
23757 TransactionUndone {
23758 transaction_id: clock::Lamport,
23759 },
23760 TransactionBegun {
23761 transaction_id: clock::Lamport,
23762 },
23763 CursorShapeChanged,
23764 BreadcrumbsChanged,
23765 PushedToNavHistory {
23766 anchor: Anchor,
23767 is_deactivate: bool,
23768 },
23769}
23770
23771impl EventEmitter<EditorEvent> for Editor {}
23772
23773impl Focusable for Editor {
23774 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23775 self.focus_handle.clone()
23776 }
23777}
23778
23779impl Render for Editor {
23780 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23781 let settings = ThemeSettings::get_global(cx);
23782
23783 let mut text_style = match self.mode {
23784 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23785 color: cx.theme().colors().editor_foreground,
23786 font_family: settings.ui_font.family.clone(),
23787 font_features: settings.ui_font.features.clone(),
23788 font_fallbacks: settings.ui_font.fallbacks.clone(),
23789 font_size: rems(0.875).into(),
23790 font_weight: settings.ui_font.weight,
23791 line_height: relative(settings.buffer_line_height.value()),
23792 ..Default::default()
23793 },
23794 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23795 color: cx.theme().colors().editor_foreground,
23796 font_family: settings.buffer_font.family.clone(),
23797 font_features: settings.buffer_font.features.clone(),
23798 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23799 font_size: settings.buffer_font_size(cx).into(),
23800 font_weight: settings.buffer_font.weight,
23801 line_height: relative(settings.buffer_line_height.value()),
23802 ..Default::default()
23803 },
23804 };
23805 if let Some(text_style_refinement) = &self.text_style_refinement {
23806 text_style.refine(text_style_refinement)
23807 }
23808
23809 let background = match self.mode {
23810 EditorMode::SingleLine => cx.theme().system().transparent,
23811 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23812 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23813 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23814 };
23815
23816 EditorElement::new(
23817 &cx.entity(),
23818 EditorStyle {
23819 background,
23820 border: cx.theme().colors().border,
23821 local_player: cx.theme().players().local(),
23822 text: text_style,
23823 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23824 syntax: cx.theme().syntax().clone(),
23825 status: cx.theme().status().clone(),
23826 inlay_hints_style: make_inlay_hints_style(cx),
23827 edit_prediction_styles: make_suggestion_styles(cx),
23828 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23829 show_underlines: self.diagnostics_enabled(),
23830 },
23831 )
23832 }
23833}
23834
23835impl EntityInputHandler for Editor {
23836 fn text_for_range(
23837 &mut self,
23838 range_utf16: Range<usize>,
23839 adjusted_range: &mut Option<Range<usize>>,
23840 _: &mut Window,
23841 cx: &mut Context<Self>,
23842 ) -> Option<String> {
23843 let snapshot = self.buffer.read(cx).read(cx);
23844 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23845 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23846 if (start.0..end.0) != range_utf16 {
23847 adjusted_range.replace(start.0..end.0);
23848 }
23849 Some(snapshot.text_for_range(start..end).collect())
23850 }
23851
23852 fn selected_text_range(
23853 &mut self,
23854 ignore_disabled_input: bool,
23855 _: &mut Window,
23856 cx: &mut Context<Self>,
23857 ) -> Option<UTF16Selection> {
23858 // Prevent the IME menu from appearing when holding down an alphabetic key
23859 // while input is disabled.
23860 if !ignore_disabled_input && !self.input_enabled {
23861 return None;
23862 }
23863
23864 let selection = self.selections.newest::<OffsetUtf16>(cx);
23865 let range = selection.range();
23866
23867 Some(UTF16Selection {
23868 range: range.start.0..range.end.0,
23869 reversed: selection.reversed,
23870 })
23871 }
23872
23873 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23874 let snapshot = self.buffer.read(cx).read(cx);
23875 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23876 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23877 }
23878
23879 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23880 self.clear_highlights::<InputComposition>(cx);
23881 self.ime_transaction.take();
23882 }
23883
23884 fn replace_text_in_range(
23885 &mut self,
23886 range_utf16: Option<Range<usize>>,
23887 text: &str,
23888 window: &mut Window,
23889 cx: &mut Context<Self>,
23890 ) {
23891 if !self.input_enabled {
23892 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23893 return;
23894 }
23895
23896 self.transact(window, cx, |this, window, cx| {
23897 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23898 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23899 Some(this.selection_replacement_ranges(range_utf16, cx))
23900 } else {
23901 this.marked_text_ranges(cx)
23902 };
23903
23904 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23905 let newest_selection_id = this.selections.newest_anchor().id;
23906 this.selections
23907 .all::<OffsetUtf16>(cx)
23908 .iter()
23909 .zip(ranges_to_replace.iter())
23910 .find_map(|(selection, range)| {
23911 if selection.id == newest_selection_id {
23912 Some(
23913 (range.start.0 as isize - selection.head().0 as isize)
23914 ..(range.end.0 as isize - selection.head().0 as isize),
23915 )
23916 } else {
23917 None
23918 }
23919 })
23920 });
23921
23922 cx.emit(EditorEvent::InputHandled {
23923 utf16_range_to_replace: range_to_replace,
23924 text: text.into(),
23925 });
23926
23927 if let Some(new_selected_ranges) = new_selected_ranges {
23928 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23929 selections.select_ranges(new_selected_ranges)
23930 });
23931 this.backspace(&Default::default(), window, cx);
23932 }
23933
23934 this.handle_input(text, window, cx);
23935 });
23936
23937 if let Some(transaction) = self.ime_transaction {
23938 self.buffer.update(cx, |buffer, cx| {
23939 buffer.group_until_transaction(transaction, cx);
23940 });
23941 }
23942
23943 self.unmark_text(window, cx);
23944 }
23945
23946 fn replace_and_mark_text_in_range(
23947 &mut self,
23948 range_utf16: Option<Range<usize>>,
23949 text: &str,
23950 new_selected_range_utf16: Option<Range<usize>>,
23951 window: &mut Window,
23952 cx: &mut Context<Self>,
23953 ) {
23954 if !self.input_enabled {
23955 return;
23956 }
23957
23958 let transaction = self.transact(window, cx, |this, window, cx| {
23959 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23960 let snapshot = this.buffer.read(cx).read(cx);
23961 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23962 for marked_range in &mut marked_ranges {
23963 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23964 marked_range.start.0 += relative_range_utf16.start;
23965 marked_range.start =
23966 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23967 marked_range.end =
23968 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23969 }
23970 }
23971 Some(marked_ranges)
23972 } else if let Some(range_utf16) = range_utf16 {
23973 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23974 Some(this.selection_replacement_ranges(range_utf16, cx))
23975 } else {
23976 None
23977 };
23978
23979 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23980 let newest_selection_id = this.selections.newest_anchor().id;
23981 this.selections
23982 .all::<OffsetUtf16>(cx)
23983 .iter()
23984 .zip(ranges_to_replace.iter())
23985 .find_map(|(selection, range)| {
23986 if selection.id == newest_selection_id {
23987 Some(
23988 (range.start.0 as isize - selection.head().0 as isize)
23989 ..(range.end.0 as isize - selection.head().0 as isize),
23990 )
23991 } else {
23992 None
23993 }
23994 })
23995 });
23996
23997 cx.emit(EditorEvent::InputHandled {
23998 utf16_range_to_replace: range_to_replace,
23999 text: text.into(),
24000 });
24001
24002 if let Some(ranges) = ranges_to_replace {
24003 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24004 s.select_ranges(ranges)
24005 });
24006 }
24007
24008 let marked_ranges = {
24009 let snapshot = this.buffer.read(cx).read(cx);
24010 this.selections
24011 .disjoint_anchors_arc()
24012 .iter()
24013 .map(|selection| {
24014 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24015 })
24016 .collect::<Vec<_>>()
24017 };
24018
24019 if text.is_empty() {
24020 this.unmark_text(window, cx);
24021 } else {
24022 this.highlight_text::<InputComposition>(
24023 marked_ranges.clone(),
24024 HighlightStyle {
24025 underline: Some(UnderlineStyle {
24026 thickness: px(1.),
24027 color: None,
24028 wavy: false,
24029 }),
24030 ..Default::default()
24031 },
24032 cx,
24033 );
24034 }
24035
24036 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24037 let use_autoclose = this.use_autoclose;
24038 let use_auto_surround = this.use_auto_surround;
24039 this.set_use_autoclose(false);
24040 this.set_use_auto_surround(false);
24041 this.handle_input(text, window, cx);
24042 this.set_use_autoclose(use_autoclose);
24043 this.set_use_auto_surround(use_auto_surround);
24044
24045 if let Some(new_selected_range) = new_selected_range_utf16 {
24046 let snapshot = this.buffer.read(cx).read(cx);
24047 let new_selected_ranges = marked_ranges
24048 .into_iter()
24049 .map(|marked_range| {
24050 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24051 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24052 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24053 snapshot.clip_offset_utf16(new_start, Bias::Left)
24054 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24055 })
24056 .collect::<Vec<_>>();
24057
24058 drop(snapshot);
24059 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24060 selections.select_ranges(new_selected_ranges)
24061 });
24062 }
24063 });
24064
24065 self.ime_transaction = self.ime_transaction.or(transaction);
24066 if let Some(transaction) = self.ime_transaction {
24067 self.buffer.update(cx, |buffer, cx| {
24068 buffer.group_until_transaction(transaction, cx);
24069 });
24070 }
24071
24072 if self.text_highlights::<InputComposition>(cx).is_none() {
24073 self.ime_transaction.take();
24074 }
24075 }
24076
24077 fn bounds_for_range(
24078 &mut self,
24079 range_utf16: Range<usize>,
24080 element_bounds: gpui::Bounds<Pixels>,
24081 window: &mut Window,
24082 cx: &mut Context<Self>,
24083 ) -> Option<gpui::Bounds<Pixels>> {
24084 let text_layout_details = self.text_layout_details(window);
24085 let CharacterDimensions {
24086 em_width,
24087 em_advance,
24088 line_height,
24089 } = self.character_dimensions(window);
24090
24091 let snapshot = self.snapshot(window, cx);
24092 let scroll_position = snapshot.scroll_position();
24093 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24094
24095 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24096 let x = Pixels::from(
24097 ScrollOffset::from(
24098 snapshot.x_for_display_point(start, &text_layout_details)
24099 + self.gutter_dimensions.full_width(),
24100 ) - scroll_left,
24101 );
24102 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24103
24104 Some(Bounds {
24105 origin: element_bounds.origin + point(x, y),
24106 size: size(em_width, line_height),
24107 })
24108 }
24109
24110 fn character_index_for_point(
24111 &mut self,
24112 point: gpui::Point<Pixels>,
24113 _window: &mut Window,
24114 _cx: &mut Context<Self>,
24115 ) -> Option<usize> {
24116 let position_map = self.last_position_map.as_ref()?;
24117 if !position_map.text_hitbox.contains(&point) {
24118 return None;
24119 }
24120 let display_point = position_map.point_for_position(point).previous_valid;
24121 let anchor = position_map
24122 .snapshot
24123 .display_point_to_anchor(display_point, Bias::Left);
24124 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24125 Some(utf16_offset.0)
24126 }
24127}
24128
24129trait SelectionExt {
24130 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24131 fn spanned_rows(
24132 &self,
24133 include_end_if_at_line_start: bool,
24134 map: &DisplaySnapshot,
24135 ) -> Range<MultiBufferRow>;
24136}
24137
24138impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24139 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24140 let start = self
24141 .start
24142 .to_point(map.buffer_snapshot())
24143 .to_display_point(map);
24144 let end = self
24145 .end
24146 .to_point(map.buffer_snapshot())
24147 .to_display_point(map);
24148 if self.reversed {
24149 end..start
24150 } else {
24151 start..end
24152 }
24153 }
24154
24155 fn spanned_rows(
24156 &self,
24157 include_end_if_at_line_start: bool,
24158 map: &DisplaySnapshot,
24159 ) -> Range<MultiBufferRow> {
24160 let start = self.start.to_point(map.buffer_snapshot());
24161 let mut end = self.end.to_point(map.buffer_snapshot());
24162 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24163 end.row -= 1;
24164 }
24165
24166 let buffer_start = map.prev_line_boundary(start).0;
24167 let buffer_end = map.next_line_boundary(end).0;
24168 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24169 }
24170}
24171
24172impl<T: InvalidationRegion> InvalidationStack<T> {
24173 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24174 where
24175 S: Clone + ToOffset,
24176 {
24177 while let Some(region) = self.last() {
24178 let all_selections_inside_invalidation_ranges =
24179 if selections.len() == region.ranges().len() {
24180 selections
24181 .iter()
24182 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24183 .all(|(selection, invalidation_range)| {
24184 let head = selection.head().to_offset(buffer);
24185 invalidation_range.start <= head && invalidation_range.end >= head
24186 })
24187 } else {
24188 false
24189 };
24190
24191 if all_selections_inside_invalidation_ranges {
24192 break;
24193 } else {
24194 self.pop();
24195 }
24196 }
24197 }
24198}
24199
24200impl<T> Default for InvalidationStack<T> {
24201 fn default() -> Self {
24202 Self(Default::default())
24203 }
24204}
24205
24206impl<T> Deref for InvalidationStack<T> {
24207 type Target = Vec<T>;
24208
24209 fn deref(&self) -> &Self::Target {
24210 &self.0
24211 }
24212}
24213
24214impl<T> DerefMut for InvalidationStack<T> {
24215 fn deref_mut(&mut self) -> &mut Self::Target {
24216 &mut self.0
24217 }
24218}
24219
24220impl InvalidationRegion for SnippetState {
24221 fn ranges(&self) -> &[Range<Anchor>] {
24222 &self.ranges[self.active_index]
24223 }
24224}
24225
24226fn edit_prediction_edit_text(
24227 current_snapshot: &BufferSnapshot,
24228 edits: &[(Range<Anchor>, String)],
24229 edit_preview: &EditPreview,
24230 include_deletions: bool,
24231 cx: &App,
24232) -> HighlightedText {
24233 let edits = edits
24234 .iter()
24235 .map(|(anchor, text)| {
24236 (
24237 anchor.start.text_anchor..anchor.end.text_anchor,
24238 text.clone(),
24239 )
24240 })
24241 .collect::<Vec<_>>();
24242
24243 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24244}
24245
24246fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24247 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24248 // Just show the raw edit text with basic styling
24249 let mut text = String::new();
24250 let mut highlights = Vec::new();
24251
24252 let insertion_highlight_style = HighlightStyle {
24253 color: Some(cx.theme().colors().text),
24254 ..Default::default()
24255 };
24256
24257 for (_, edit_text) in edits {
24258 let start_offset = text.len();
24259 text.push_str(edit_text);
24260 let end_offset = text.len();
24261
24262 if start_offset < end_offset {
24263 highlights.push((start_offset..end_offset, insertion_highlight_style));
24264 }
24265 }
24266
24267 HighlightedText {
24268 text: text.into(),
24269 highlights,
24270 }
24271}
24272
24273pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24274 match severity {
24275 lsp::DiagnosticSeverity::ERROR => colors.error,
24276 lsp::DiagnosticSeverity::WARNING => colors.warning,
24277 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24278 lsp::DiagnosticSeverity::HINT => colors.info,
24279 _ => colors.ignored,
24280 }
24281}
24282
24283pub fn styled_runs_for_code_label<'a>(
24284 label: &'a CodeLabel,
24285 syntax_theme: &'a theme::SyntaxTheme,
24286) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24287 let fade_out = HighlightStyle {
24288 fade_out: Some(0.35),
24289 ..Default::default()
24290 };
24291
24292 let mut prev_end = label.filter_range.end;
24293 label
24294 .runs
24295 .iter()
24296 .enumerate()
24297 .flat_map(move |(ix, (range, highlight_id))| {
24298 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24299 style
24300 } else {
24301 return Default::default();
24302 };
24303 let muted_style = style.highlight(fade_out);
24304
24305 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24306 if range.start >= label.filter_range.end {
24307 if range.start > prev_end {
24308 runs.push((prev_end..range.start, fade_out));
24309 }
24310 runs.push((range.clone(), muted_style));
24311 } else if range.end <= label.filter_range.end {
24312 runs.push((range.clone(), style));
24313 } else {
24314 runs.push((range.start..label.filter_range.end, style));
24315 runs.push((label.filter_range.end..range.end, muted_style));
24316 }
24317 prev_end = cmp::max(prev_end, range.end);
24318
24319 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24320 runs.push((prev_end..label.text.len(), fade_out));
24321 }
24322
24323 runs
24324 })
24325}
24326
24327pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24328 let mut prev_index = 0;
24329 let mut prev_codepoint: Option<char> = None;
24330 text.char_indices()
24331 .chain([(text.len(), '\0')])
24332 .filter_map(move |(index, codepoint)| {
24333 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24334 let is_boundary = index == text.len()
24335 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24336 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24337 if is_boundary {
24338 let chunk = &text[prev_index..index];
24339 prev_index = index;
24340 Some(chunk)
24341 } else {
24342 None
24343 }
24344 })
24345}
24346
24347pub trait RangeToAnchorExt: Sized {
24348 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24349
24350 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24351 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24352 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24353 }
24354}
24355
24356impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24357 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24358 let start_offset = self.start.to_offset(snapshot);
24359 let end_offset = self.end.to_offset(snapshot);
24360 if start_offset == end_offset {
24361 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24362 } else {
24363 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24364 }
24365 }
24366}
24367
24368pub trait RowExt {
24369 fn as_f64(&self) -> f64;
24370
24371 fn next_row(&self) -> Self;
24372
24373 fn previous_row(&self) -> Self;
24374
24375 fn minus(&self, other: Self) -> u32;
24376}
24377
24378impl RowExt for DisplayRow {
24379 fn as_f64(&self) -> f64 {
24380 self.0 as _
24381 }
24382
24383 fn next_row(&self) -> Self {
24384 Self(self.0 + 1)
24385 }
24386
24387 fn previous_row(&self) -> Self {
24388 Self(self.0.saturating_sub(1))
24389 }
24390
24391 fn minus(&self, other: Self) -> u32 {
24392 self.0 - other.0
24393 }
24394}
24395
24396impl RowExt for MultiBufferRow {
24397 fn as_f64(&self) -> f64 {
24398 self.0 as _
24399 }
24400
24401 fn next_row(&self) -> Self {
24402 Self(self.0 + 1)
24403 }
24404
24405 fn previous_row(&self) -> Self {
24406 Self(self.0.saturating_sub(1))
24407 }
24408
24409 fn minus(&self, other: Self) -> u32 {
24410 self.0 - other.0
24411 }
24412}
24413
24414trait RowRangeExt {
24415 type Row;
24416
24417 fn len(&self) -> usize;
24418
24419 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24420}
24421
24422impl RowRangeExt for Range<MultiBufferRow> {
24423 type Row = MultiBufferRow;
24424
24425 fn len(&self) -> usize {
24426 (self.end.0 - self.start.0) as usize
24427 }
24428
24429 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24430 (self.start.0..self.end.0).map(MultiBufferRow)
24431 }
24432}
24433
24434impl RowRangeExt for Range<DisplayRow> {
24435 type Row = DisplayRow;
24436
24437 fn len(&self) -> usize {
24438 (self.end.0 - self.start.0) as usize
24439 }
24440
24441 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24442 (self.start.0..self.end.0).map(DisplayRow)
24443 }
24444}
24445
24446/// If select range has more than one line, we
24447/// just point the cursor to range.start.
24448fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24449 if range.start.row == range.end.row {
24450 range
24451 } else {
24452 range.start..range.start
24453 }
24454}
24455pub struct KillRing(ClipboardItem);
24456impl Global for KillRing {}
24457
24458const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24459
24460enum BreakpointPromptEditAction {
24461 Log,
24462 Condition,
24463 HitCondition,
24464}
24465
24466struct BreakpointPromptEditor {
24467 pub(crate) prompt: Entity<Editor>,
24468 editor: WeakEntity<Editor>,
24469 breakpoint_anchor: Anchor,
24470 breakpoint: Breakpoint,
24471 edit_action: BreakpointPromptEditAction,
24472 block_ids: HashSet<CustomBlockId>,
24473 editor_margins: Arc<Mutex<EditorMargins>>,
24474 _subscriptions: Vec<Subscription>,
24475}
24476
24477impl BreakpointPromptEditor {
24478 const MAX_LINES: u8 = 4;
24479
24480 fn new(
24481 editor: WeakEntity<Editor>,
24482 breakpoint_anchor: Anchor,
24483 breakpoint: Breakpoint,
24484 edit_action: BreakpointPromptEditAction,
24485 window: &mut Window,
24486 cx: &mut Context<Self>,
24487 ) -> Self {
24488 let base_text = match edit_action {
24489 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24490 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24491 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24492 }
24493 .map(|msg| msg.to_string())
24494 .unwrap_or_default();
24495
24496 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24497 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24498
24499 let prompt = cx.new(|cx| {
24500 let mut prompt = Editor::new(
24501 EditorMode::AutoHeight {
24502 min_lines: 1,
24503 max_lines: Some(Self::MAX_LINES as usize),
24504 },
24505 buffer,
24506 None,
24507 window,
24508 cx,
24509 );
24510 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24511 prompt.set_show_cursor_when_unfocused(false, cx);
24512 prompt.set_placeholder_text(
24513 match edit_action {
24514 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24515 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24516 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24517 },
24518 window,
24519 cx,
24520 );
24521
24522 prompt
24523 });
24524
24525 Self {
24526 prompt,
24527 editor,
24528 breakpoint_anchor,
24529 breakpoint,
24530 edit_action,
24531 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24532 block_ids: Default::default(),
24533 _subscriptions: vec![],
24534 }
24535 }
24536
24537 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24538 self.block_ids.extend(block_ids)
24539 }
24540
24541 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24542 if let Some(editor) = self.editor.upgrade() {
24543 let message = self
24544 .prompt
24545 .read(cx)
24546 .buffer
24547 .read(cx)
24548 .as_singleton()
24549 .expect("A multi buffer in breakpoint prompt isn't possible")
24550 .read(cx)
24551 .as_rope()
24552 .to_string();
24553
24554 editor.update(cx, |editor, cx| {
24555 editor.edit_breakpoint_at_anchor(
24556 self.breakpoint_anchor,
24557 self.breakpoint.clone(),
24558 match self.edit_action {
24559 BreakpointPromptEditAction::Log => {
24560 BreakpointEditAction::EditLogMessage(message.into())
24561 }
24562 BreakpointPromptEditAction::Condition => {
24563 BreakpointEditAction::EditCondition(message.into())
24564 }
24565 BreakpointPromptEditAction::HitCondition => {
24566 BreakpointEditAction::EditHitCondition(message.into())
24567 }
24568 },
24569 cx,
24570 );
24571
24572 editor.remove_blocks(self.block_ids.clone(), None, cx);
24573 cx.focus_self(window);
24574 });
24575 }
24576 }
24577
24578 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24579 self.editor
24580 .update(cx, |editor, cx| {
24581 editor.remove_blocks(self.block_ids.clone(), None, cx);
24582 window.focus(&editor.focus_handle);
24583 })
24584 .log_err();
24585 }
24586
24587 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24588 let settings = ThemeSettings::get_global(cx);
24589 let text_style = TextStyle {
24590 color: if self.prompt.read(cx).read_only(cx) {
24591 cx.theme().colors().text_disabled
24592 } else {
24593 cx.theme().colors().text
24594 },
24595 font_family: settings.buffer_font.family.clone(),
24596 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24597 font_size: settings.buffer_font_size(cx).into(),
24598 font_weight: settings.buffer_font.weight,
24599 line_height: relative(settings.buffer_line_height.value()),
24600 ..Default::default()
24601 };
24602 EditorElement::new(
24603 &self.prompt,
24604 EditorStyle {
24605 background: cx.theme().colors().editor_background,
24606 local_player: cx.theme().players().local(),
24607 text: text_style,
24608 ..Default::default()
24609 },
24610 )
24611 }
24612}
24613
24614impl Render for BreakpointPromptEditor {
24615 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24616 let editor_margins = *self.editor_margins.lock();
24617 let gutter_dimensions = editor_margins.gutter;
24618 h_flex()
24619 .key_context("Editor")
24620 .bg(cx.theme().colors().editor_background)
24621 .border_y_1()
24622 .border_color(cx.theme().status().info_border)
24623 .size_full()
24624 .py(window.line_height() / 2.5)
24625 .on_action(cx.listener(Self::confirm))
24626 .on_action(cx.listener(Self::cancel))
24627 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24628 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24629 }
24630}
24631
24632impl Focusable for BreakpointPromptEditor {
24633 fn focus_handle(&self, cx: &App) -> FocusHandle {
24634 self.prompt.focus_handle(cx)
24635 }
24636}
24637
24638fn all_edits_insertions_or_deletions(
24639 edits: &Vec<(Range<Anchor>, String)>,
24640 snapshot: &MultiBufferSnapshot,
24641) -> bool {
24642 let mut all_insertions = true;
24643 let mut all_deletions = true;
24644
24645 for (range, new_text) in edits.iter() {
24646 let range_is_empty = range.to_offset(snapshot).is_empty();
24647 let text_is_empty = new_text.is_empty();
24648
24649 if range_is_empty != text_is_empty {
24650 if range_is_empty {
24651 all_deletions = false;
24652 } else {
24653 all_insertions = false;
24654 }
24655 } else {
24656 return false;
24657 }
24658
24659 if !all_insertions && !all_deletions {
24660 return false;
24661 }
24662 }
24663 all_insertions || all_deletions
24664}
24665
24666struct MissingEditPredictionKeybindingTooltip;
24667
24668impl Render for MissingEditPredictionKeybindingTooltip {
24669 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24670 ui::tooltip_container(cx, |container, cx| {
24671 container
24672 .flex_shrink_0()
24673 .max_w_80()
24674 .min_h(rems_from_px(124.))
24675 .justify_between()
24676 .child(
24677 v_flex()
24678 .flex_1()
24679 .text_ui_sm(cx)
24680 .child(Label::new("Conflict with Accept Keybinding"))
24681 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24682 )
24683 .child(
24684 h_flex()
24685 .pb_1()
24686 .gap_1()
24687 .items_end()
24688 .w_full()
24689 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24690 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24691 }))
24692 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24693 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24694 })),
24695 )
24696 })
24697 }
24698}
24699
24700#[derive(Debug, Clone, Copy, PartialEq)]
24701pub struct LineHighlight {
24702 pub background: Background,
24703 pub border: Option<gpui::Hsla>,
24704 pub include_gutter: bool,
24705 pub type_id: Option<TypeId>,
24706}
24707
24708struct LineManipulationResult {
24709 pub new_text: String,
24710 pub line_count_before: usize,
24711 pub line_count_after: usize,
24712}
24713
24714fn render_diff_hunk_controls(
24715 row: u32,
24716 status: &DiffHunkStatus,
24717 hunk_range: Range<Anchor>,
24718 is_created_file: bool,
24719 line_height: Pixels,
24720 editor: &Entity<Editor>,
24721 _window: &mut Window,
24722 cx: &mut App,
24723) -> AnyElement {
24724 h_flex()
24725 .h(line_height)
24726 .mr_1()
24727 .gap_1()
24728 .px_0p5()
24729 .pb_1()
24730 .border_x_1()
24731 .border_b_1()
24732 .border_color(cx.theme().colors().border_variant)
24733 .rounded_b_lg()
24734 .bg(cx.theme().colors().editor_background)
24735 .gap_1()
24736 .block_mouse_except_scroll()
24737 .shadow_md()
24738 .child(if status.has_secondary_hunk() {
24739 Button::new(("stage", row as u64), "Stage")
24740 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24741 .tooltip({
24742 let focus_handle = editor.focus_handle(cx);
24743 move |window, cx| {
24744 Tooltip::for_action_in(
24745 "Stage Hunk",
24746 &::git::ToggleStaged,
24747 &focus_handle,
24748 window,
24749 cx,
24750 )
24751 }
24752 })
24753 .on_click({
24754 let editor = editor.clone();
24755 move |_event, _window, cx| {
24756 editor.update(cx, |editor, cx| {
24757 editor.stage_or_unstage_diff_hunks(
24758 true,
24759 vec![hunk_range.start..hunk_range.start],
24760 cx,
24761 );
24762 });
24763 }
24764 })
24765 } else {
24766 Button::new(("unstage", row as u64), "Unstage")
24767 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24768 .tooltip({
24769 let focus_handle = editor.focus_handle(cx);
24770 move |window, cx| {
24771 Tooltip::for_action_in(
24772 "Unstage Hunk",
24773 &::git::ToggleStaged,
24774 &focus_handle,
24775 window,
24776 cx,
24777 )
24778 }
24779 })
24780 .on_click({
24781 let editor = editor.clone();
24782 move |_event, _window, cx| {
24783 editor.update(cx, |editor, cx| {
24784 editor.stage_or_unstage_diff_hunks(
24785 false,
24786 vec![hunk_range.start..hunk_range.start],
24787 cx,
24788 );
24789 });
24790 }
24791 })
24792 })
24793 .child(
24794 Button::new(("restore", row as u64), "Restore")
24795 .tooltip({
24796 let focus_handle = editor.focus_handle(cx);
24797 move |window, cx| {
24798 Tooltip::for_action_in(
24799 "Restore Hunk",
24800 &::git::Restore,
24801 &focus_handle,
24802 window,
24803 cx,
24804 )
24805 }
24806 })
24807 .on_click({
24808 let editor = editor.clone();
24809 move |_event, window, cx| {
24810 editor.update(cx, |editor, cx| {
24811 let snapshot = editor.snapshot(window, cx);
24812 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24813 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24814 });
24815 }
24816 })
24817 .disabled(is_created_file),
24818 )
24819 .when(
24820 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24821 |el| {
24822 el.child(
24823 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24824 .shape(IconButtonShape::Square)
24825 .icon_size(IconSize::Small)
24826 // .disabled(!has_multiple_hunks)
24827 .tooltip({
24828 let focus_handle = editor.focus_handle(cx);
24829 move |window, cx| {
24830 Tooltip::for_action_in(
24831 "Next Hunk",
24832 &GoToHunk,
24833 &focus_handle,
24834 window,
24835 cx,
24836 )
24837 }
24838 })
24839 .on_click({
24840 let editor = editor.clone();
24841 move |_event, window, cx| {
24842 editor.update(cx, |editor, cx| {
24843 let snapshot = editor.snapshot(window, cx);
24844 let position =
24845 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24846 editor.go_to_hunk_before_or_after_position(
24847 &snapshot,
24848 position,
24849 Direction::Next,
24850 window,
24851 cx,
24852 );
24853 editor.expand_selected_diff_hunks(cx);
24854 });
24855 }
24856 }),
24857 )
24858 .child(
24859 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24860 .shape(IconButtonShape::Square)
24861 .icon_size(IconSize::Small)
24862 // .disabled(!has_multiple_hunks)
24863 .tooltip({
24864 let focus_handle = editor.focus_handle(cx);
24865 move |window, cx| {
24866 Tooltip::for_action_in(
24867 "Previous Hunk",
24868 &GoToPreviousHunk,
24869 &focus_handle,
24870 window,
24871 cx,
24872 )
24873 }
24874 })
24875 .on_click({
24876 let editor = editor.clone();
24877 move |_event, window, cx| {
24878 editor.update(cx, |editor, cx| {
24879 let snapshot = editor.snapshot(window, cx);
24880 let point =
24881 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24882 editor.go_to_hunk_before_or_after_position(
24883 &snapshot,
24884 point,
24885 Direction::Prev,
24886 window,
24887 cx,
24888 );
24889 editor.expand_selected_diff_hunks(cx);
24890 });
24891 }
24892 }),
24893 )
24894 },
24895 )
24896 .into_any_element()
24897}
24898
24899pub fn multibuffer_context_lines(cx: &App) -> u32 {
24900 EditorSettings::try_get(cx)
24901 .map(|settings| settings.excerpt_context_lines)
24902 .unwrap_or(2)
24903 .min(32)
24904}