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 editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
152 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
153 ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{
164 DiagnosticSeverity, GitGutterSetting, GoToDiagnosticSeverityFilter, ProjectSettings,
165 },
166};
167use rand::seq::SliceRandom;
168use rpc::{ErrorCode, ErrorExt, proto::PeerId};
169use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
170use selections_collection::{
171 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
172};
173use serde::{Deserialize, Serialize};
174use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
175use smallvec::{SmallVec, smallvec};
176use snippet::Snippet;
177use std::{
178 any::{Any, TypeId},
179 borrow::Cow,
180 cell::{OnceCell, RefCell},
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
186 path::{Path, PathBuf},
187 rc::Rc,
188 sync::Arc,
189 time::{Duration, Instant},
190};
191use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
192use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
193use theme::{
194 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
195 observe_buffer_font_size_adjustment,
196};
197use ui::{
198 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
199 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
200};
201use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
202use workspace::{
203 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
204 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
205 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
206 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
207 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
208 searchable::SearchEvent,
209};
210
211use crate::{
212 code_context_menus::CompletionsMenuSource,
213 editor_settings::MultiCursorModifier,
214 hover_links::{find_url, find_url_from_range},
215 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
216};
217
218pub const FILE_HEADER_HEIGHT: u32 = 2;
219pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
220const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
221const MAX_LINE_LEN: usize = 1024;
222const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
223const MAX_SELECTION_HISTORY_LEN: usize = 1024;
224pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
225#[doc(hidden)]
226pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
227pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
228
229pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
232
233pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
234pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
235pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
236
237pub type RenderDiffHunkControlsFn = Arc<
238 dyn Fn(
239 u32,
240 &DiffHunkStatus,
241 Range<Anchor>,
242 bool,
243 Pixels,
244 &Entity<Editor>,
245 &mut Window,
246 &mut App,
247 ) -> AnyElement,
248>;
249
250enum ReportEditorEvent {
251 Saved { auto_saved: bool },
252 EditorOpened,
253 Closed,
254}
255
256impl ReportEditorEvent {
257 pub fn event_type(&self) -> &'static str {
258 match self {
259 Self::Saved { .. } => "Editor Saved",
260 Self::EditorOpened => "Editor Opened",
261 Self::Closed => "Editor Closed",
262 }
263 }
264}
265
266struct InlineValueCache {
267 enabled: bool,
268 inlays: Vec<InlayId>,
269 refresh_task: Task<Option<()>>,
270}
271
272impl InlineValueCache {
273 fn new(enabled: bool) -> Self {
274 Self {
275 enabled,
276 inlays: Vec::new(),
277 refresh_task: Task::ready(None),
278 }
279 }
280}
281
282#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
283pub enum InlayId {
284 EditPrediction(usize),
285 DebuggerValue(usize),
286 // LSP
287 Hint(usize),
288 Color(usize),
289}
290
291impl InlayId {
292 fn id(&self) -> usize {
293 match self {
294 Self::EditPrediction(id) => *id,
295 Self::DebuggerValue(id) => *id,
296 Self::Hint(id) => *id,
297 Self::Color(id) => *id,
298 }
299 }
300}
301
302pub enum ActiveDebugLine {}
303pub enum DebugStackFrameLine {}
304enum DocumentHighlightRead {}
305enum DocumentHighlightWrite {}
306enum InputComposition {}
307pub enum PendingInput {}
308enum SelectedTextHighlight {}
309
310pub enum ConflictsOuter {}
311pub enum ConflictsOurs {}
312pub enum ConflictsTheirs {}
313pub enum ConflictsOursMarker {}
314pub enum ConflictsTheirsMarker {}
315
316#[derive(Debug, Copy, Clone, PartialEq, Eq)]
317pub enum Navigated {
318 Yes,
319 No,
320}
321
322impl Navigated {
323 pub fn from_bool(yes: bool) -> Navigated {
324 if yes { Navigated::Yes } else { Navigated::No }
325 }
326}
327
328#[derive(Debug, Clone, PartialEq, Eq)]
329enum DisplayDiffHunk {
330 Folded {
331 display_row: DisplayRow,
332 },
333 Unfolded {
334 is_created_file: bool,
335 diff_base_byte_range: Range<usize>,
336 display_row_range: Range<DisplayRow>,
337 multi_buffer_range: Range<Anchor>,
338 status: DiffHunkStatus,
339 },
340}
341
342pub enum HideMouseCursorOrigin {
343 TypingAction,
344 MovementAction,
345}
346
347pub fn init_settings(cx: &mut App) {
348 EditorSettings::register(cx);
349}
350
351pub fn init(cx: &mut App) {
352 init_settings(cx);
353
354 cx.set_global(GlobalBlameRenderer(Arc::new(())));
355
356 workspace::register_project_item::<Editor>(cx);
357 workspace::FollowableViewRegistry::register::<Editor>(cx);
358 workspace::register_serializable_item::<Editor>(cx);
359
360 cx.observe_new(
361 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
362 workspace.register_action(Editor::new_file);
363 workspace.register_action(Editor::new_file_vertical);
364 workspace.register_action(Editor::new_file_horizontal);
365 workspace.register_action(Editor::cancel_language_server_work);
366 workspace.register_action(Editor::toggle_focus);
367 },
368 )
369 .detach();
370
371 cx.on_action(move |_: &workspace::NewFile, cx| {
372 let app_state = workspace::AppState::global(cx);
373 if let Some(app_state) = app_state.upgrade() {
374 workspace::open_new(
375 Default::default(),
376 app_state,
377 cx,
378 |workspace, window, cx| {
379 Editor::new_file(workspace, &Default::default(), window, cx)
380 },
381 )
382 .detach();
383 }
384 });
385 cx.on_action(move |_: &workspace::NewWindow, cx| {
386 let app_state = workspace::AppState::global(cx);
387 if let Some(app_state) = app_state.upgrade() {
388 workspace::open_new(
389 Default::default(),
390 app_state,
391 cx,
392 |workspace, window, cx| {
393 cx.activate(true);
394 Editor::new_file(workspace, &Default::default(), window, cx)
395 },
396 )
397 .detach();
398 }
399 });
400}
401
402pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
403 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
404}
405
406pub trait DiagnosticRenderer {
407 fn render_group(
408 &self,
409 diagnostic_group: Vec<DiagnosticEntry<Point>>,
410 buffer_id: BufferId,
411 snapshot: EditorSnapshot,
412 editor: WeakEntity<Editor>,
413 cx: &mut App,
414 ) -> Vec<BlockProperties<Anchor>>;
415
416 fn render_hover(
417 &self,
418 diagnostic_group: Vec<DiagnosticEntry<Point>>,
419 range: Range<Point>,
420 buffer_id: BufferId,
421 cx: &mut App,
422 ) -> Option<Entity<markdown::Markdown>>;
423
424 fn open_link(
425 &self,
426 editor: &mut Editor,
427 link: SharedString,
428 window: &mut Window,
429 cx: &mut Context<Editor>,
430 );
431}
432
433pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
434
435impl GlobalDiagnosticRenderer {
436 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
437 cx.try_global::<Self>().map(|g| g.0.clone())
438 }
439}
440
441impl gpui::Global for GlobalDiagnosticRenderer {}
442pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
443 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
444}
445
446pub struct SearchWithinRange;
447
448trait InvalidationRegion {
449 fn ranges(&self) -> &[Range<Anchor>];
450}
451
452#[derive(Clone, Debug, PartialEq)]
453pub enum SelectPhase {
454 Begin {
455 position: DisplayPoint,
456 add: bool,
457 click_count: usize,
458 },
459 BeginColumnar {
460 position: DisplayPoint,
461 reset: bool,
462 mode: ColumnarMode,
463 goal_column: u32,
464 },
465 Extend {
466 position: DisplayPoint,
467 click_count: usize,
468 },
469 Update {
470 position: DisplayPoint,
471 goal_column: u32,
472 scroll_delta: gpui::Point<f32>,
473 },
474 End,
475}
476
477#[derive(Clone, Debug, PartialEq)]
478pub enum ColumnarMode {
479 FromMouse,
480 FromSelection,
481}
482
483#[derive(Clone, Debug)]
484pub enum SelectMode {
485 Character,
486 Word(Range<Anchor>),
487 Line(Range<Anchor>),
488 All,
489}
490
491#[derive(Clone, PartialEq, Eq, Debug)]
492pub enum EditorMode {
493 SingleLine,
494 AutoHeight {
495 min_lines: usize,
496 max_lines: Option<usize>,
497 },
498 Full {
499 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
500 scale_ui_elements_with_buffer_font_size: bool,
501 /// When set to `true`, the editor will render a background for the active line.
502 show_active_line_background: bool,
503 /// When set to `true`, the editor's height will be determined by its content.
504 sized_by_content: bool,
505 },
506 Minimap {
507 parent: WeakEntity<Editor>,
508 },
509}
510
511impl EditorMode {
512 pub fn full() -> Self {
513 Self::Full {
514 scale_ui_elements_with_buffer_font_size: true,
515 show_active_line_background: true,
516 sized_by_content: false,
517 }
518 }
519
520 #[inline]
521 pub fn is_full(&self) -> bool {
522 matches!(self, Self::Full { .. })
523 }
524
525 #[inline]
526 pub fn is_single_line(&self) -> bool {
527 matches!(self, Self::SingleLine { .. })
528 }
529
530 #[inline]
531 fn is_minimap(&self) -> bool {
532 matches!(self, Self::Minimap { .. })
533 }
534}
535
536#[derive(Copy, Clone, Debug)]
537pub enum SoftWrap {
538 /// Prefer not to wrap at all.
539 ///
540 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
541 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
542 GitDiff,
543 /// Prefer a single line generally, unless an overly long line is encountered.
544 None,
545 /// Soft wrap lines that exceed the editor width.
546 EditorWidth,
547 /// Soft wrap lines at the preferred line length.
548 Column(u32),
549 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
550 Bounded(u32),
551}
552
553#[derive(Clone)]
554pub struct EditorStyle {
555 pub background: Hsla,
556 pub border: Hsla,
557 pub local_player: PlayerColor,
558 pub text: TextStyle,
559 pub scrollbar_width: Pixels,
560 pub syntax: Arc<SyntaxTheme>,
561 pub status: StatusColors,
562 pub inlay_hints_style: HighlightStyle,
563 pub edit_prediction_styles: EditPredictionStyles,
564 pub unnecessary_code_fade: f32,
565 pub show_underlines: bool,
566}
567
568impl Default for EditorStyle {
569 fn default() -> Self {
570 Self {
571 background: Hsla::default(),
572 border: Hsla::default(),
573 local_player: PlayerColor::default(),
574 text: TextStyle::default(),
575 scrollbar_width: Pixels::default(),
576 syntax: Default::default(),
577 // HACK: Status colors don't have a real default.
578 // We should look into removing the status colors from the editor
579 // style and retrieve them directly from the theme.
580 status: StatusColors::dark(),
581 inlay_hints_style: HighlightStyle::default(),
582 edit_prediction_styles: EditPredictionStyles {
583 insertion: HighlightStyle::default(),
584 whitespace: HighlightStyle::default(),
585 },
586 unnecessary_code_fade: Default::default(),
587 show_underlines: true,
588 }
589 }
590}
591
592pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
593 let show_background = language_settings::language_settings(None, None, cx)
594 .inlay_hints
595 .show_background;
596
597 HighlightStyle {
598 color: Some(cx.theme().status().hint),
599 background_color: show_background.then(|| cx.theme().status().hint_background),
600 ..HighlightStyle::default()
601 }
602}
603
604pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
605 EditPredictionStyles {
606 insertion: HighlightStyle {
607 color: Some(cx.theme().status().predictive),
608 ..HighlightStyle::default()
609 },
610 whitespace: HighlightStyle {
611 background_color: Some(cx.theme().status().created_background),
612 ..HighlightStyle::default()
613 },
614 }
615}
616
617type CompletionId = usize;
618
619pub(crate) enum EditDisplayMode {
620 TabAccept,
621 DiffPopover,
622 Inline,
623}
624
625enum EditPrediction {
626 Edit {
627 edits: Vec<(Range<Anchor>, String)>,
628 edit_preview: Option<EditPreview>,
629 display_mode: EditDisplayMode,
630 snapshot: BufferSnapshot,
631 },
632 Move {
633 target: Anchor,
634 snapshot: BufferSnapshot,
635 },
636}
637
638struct EditPredictionState {
639 inlay_ids: Vec<InlayId>,
640 completion: EditPrediction,
641 completion_id: Option<SharedString>,
642 invalidation_range: Range<Anchor>,
643}
644
645enum EditPredictionSettings {
646 Disabled,
647 Enabled {
648 show_in_menu: bool,
649 preview_requires_modifier: bool,
650 },
651}
652
653enum EditPredictionHighlight {}
654
655#[derive(Debug, Clone)]
656struct InlineDiagnostic {
657 message: SharedString,
658 group_id: usize,
659 is_primary: bool,
660 start: Point,
661 severity: lsp::DiagnosticSeverity,
662}
663
664pub enum MenuEditPredictionsPolicy {
665 Never,
666 ByProvider,
667}
668
669pub enum EditPredictionPreview {
670 /// Modifier is not pressed
671 Inactive { released_too_fast: bool },
672 /// Modifier pressed
673 Active {
674 since: Instant,
675 previous_scroll_position: Option<ScrollAnchor>,
676 },
677}
678
679impl EditPredictionPreview {
680 pub fn released_too_fast(&self) -> bool {
681 match self {
682 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
683 EditPredictionPreview::Active { .. } => false,
684 }
685 }
686
687 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
688 if let EditPredictionPreview::Active {
689 previous_scroll_position,
690 ..
691 } = self
692 {
693 *previous_scroll_position = scroll_position;
694 }
695 }
696}
697
698pub struct ContextMenuOptions {
699 pub min_entries_visible: usize,
700 pub max_entries_visible: usize,
701 pub placement: Option<ContextMenuPlacement>,
702}
703
704#[derive(Debug, Clone, PartialEq, Eq)]
705pub enum ContextMenuPlacement {
706 Above,
707 Below,
708}
709
710#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
711struct EditorActionId(usize);
712
713impl EditorActionId {
714 pub fn post_inc(&mut self) -> Self {
715 let answer = self.0;
716
717 *self = Self(answer + 1);
718
719 Self(answer)
720 }
721}
722
723// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
724// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
725
726type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
727type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
728
729#[derive(Default)]
730struct ScrollbarMarkerState {
731 scrollbar_size: Size<Pixels>,
732 dirty: bool,
733 markers: Arc<[PaintQuad]>,
734 pending_refresh: Option<Task<Result<()>>>,
735}
736
737impl ScrollbarMarkerState {
738 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
739 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
740 }
741}
742
743#[derive(Clone, Copy, PartialEq, Eq)]
744pub enum MinimapVisibility {
745 Disabled,
746 Enabled {
747 /// The configuration currently present in the users settings.
748 setting_configuration: bool,
749 /// Whether to override the currently set visibility from the users setting.
750 toggle_override: bool,
751 },
752}
753
754impl MinimapVisibility {
755 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
756 if mode.is_full() {
757 Self::Enabled {
758 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
759 toggle_override: false,
760 }
761 } else {
762 Self::Disabled
763 }
764 }
765
766 fn hidden(&self) -> Self {
767 match *self {
768 Self::Enabled {
769 setting_configuration,
770 ..
771 } => Self::Enabled {
772 setting_configuration,
773 toggle_override: setting_configuration,
774 },
775 Self::Disabled => Self::Disabled,
776 }
777 }
778
779 fn disabled(&self) -> bool {
780 matches!(*self, Self::Disabled)
781 }
782
783 fn settings_visibility(&self) -> bool {
784 match *self {
785 Self::Enabled {
786 setting_configuration,
787 ..
788 } => setting_configuration,
789 _ => false,
790 }
791 }
792
793 fn visible(&self) -> bool {
794 match *self {
795 Self::Enabled {
796 setting_configuration,
797 toggle_override,
798 } => setting_configuration ^ toggle_override,
799 _ => false,
800 }
801 }
802
803 fn toggle_visibility(&self) -> Self {
804 match *self {
805 Self::Enabled {
806 toggle_override,
807 setting_configuration,
808 } => Self::Enabled {
809 setting_configuration,
810 toggle_override: !toggle_override,
811 },
812 Self::Disabled => Self::Disabled,
813 }
814 }
815}
816
817#[derive(Clone, Debug)]
818struct RunnableTasks {
819 templates: Vec<(TaskSourceKind, TaskTemplate)>,
820 offset: multi_buffer::Anchor,
821 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
822 column: u32,
823 // Values of all named captures, including those starting with '_'
824 extra_variables: HashMap<String, String>,
825 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
826 context_range: Range<BufferOffset>,
827}
828
829impl RunnableTasks {
830 fn resolve<'a>(
831 &'a self,
832 cx: &'a task::TaskContext,
833 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
834 self.templates.iter().filter_map(|(kind, template)| {
835 template
836 .resolve_task(&kind.to_id_base(), cx)
837 .map(|task| (kind.clone(), task))
838 })
839 }
840}
841
842#[derive(Clone)]
843pub struct ResolvedTasks {
844 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
845 position: Anchor,
846}
847
848#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
849struct BufferOffset(usize);
850
851/// Addons allow storing per-editor state in other crates (e.g. Vim)
852pub trait Addon: 'static {
853 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
854
855 fn render_buffer_header_controls(
856 &self,
857 _: &ExcerptInfo,
858 _: &Window,
859 _: &App,
860 ) -> Option<AnyElement> {
861 None
862 }
863
864 fn to_any(&self) -> &dyn std::any::Any;
865
866 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
867 None
868 }
869}
870
871struct ChangeLocation {
872 current: Option<Vec<Anchor>>,
873 original: Vec<Anchor>,
874}
875impl ChangeLocation {
876 fn locations(&self) -> &[Anchor] {
877 self.current.as_ref().unwrap_or(&self.original)
878 }
879}
880
881/// A set of caret positions, registered when the editor was edited.
882pub struct ChangeList {
883 changes: Vec<ChangeLocation>,
884 /// Currently "selected" change.
885 position: Option<usize>,
886}
887
888impl ChangeList {
889 pub fn new() -> Self {
890 Self {
891 changes: Vec::new(),
892 position: None,
893 }
894 }
895
896 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
897 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
898 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
899 if self.changes.is_empty() {
900 return None;
901 }
902
903 let prev = self.position.unwrap_or(self.changes.len());
904 let next = if direction == Direction::Prev {
905 prev.saturating_sub(count)
906 } else {
907 (prev + count).min(self.changes.len() - 1)
908 };
909 self.position = Some(next);
910 self.changes.get(next).map(|change| change.locations())
911 }
912
913 /// Adds a new change to the list, resetting the change list position.
914 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
915 self.position.take();
916 if let Some(last) = self.changes.last_mut()
917 && group
918 {
919 last.current = Some(new_positions)
920 } else {
921 self.changes.push(ChangeLocation {
922 original: new_positions,
923 current: None,
924 });
925 }
926 }
927
928 pub fn last(&self) -> Option<&[Anchor]> {
929 self.changes.last().map(|change| change.locations())
930 }
931
932 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
933 self.changes.last().map(|change| change.original.as_slice())
934 }
935
936 pub fn invert_last_group(&mut self) {
937 if let Some(last) = self.changes.last_mut()
938 && let Some(current) = last.current.as_mut()
939 {
940 mem::swap(&mut last.original, current);
941 }
942 }
943}
944
945#[derive(Clone)]
946struct InlineBlamePopoverState {
947 scroll_handle: ScrollHandle,
948 commit_message: Option<ParsedCommitMessage>,
949 markdown: Entity<Markdown>,
950}
951
952struct InlineBlamePopover {
953 position: gpui::Point<Pixels>,
954 hide_task: Option<Task<()>>,
955 popover_bounds: Option<Bounds<Pixels>>,
956 popover_state: InlineBlamePopoverState,
957 keyboard_grace: bool,
958}
959
960enum SelectionDragState {
961 /// State when no drag related activity is detected.
962 None,
963 /// State when the mouse is down on a selection that is about to be dragged.
964 ReadyToDrag {
965 selection: Selection<Anchor>,
966 click_position: gpui::Point<Pixels>,
967 mouse_down_time: Instant,
968 },
969 /// State when the mouse is dragging the selection in the editor.
970 Dragging {
971 selection: Selection<Anchor>,
972 drop_cursor: Selection<Anchor>,
973 hide_drop_cursor: bool,
974 },
975}
976
977enum ColumnarSelectionState {
978 FromMouse {
979 selection_tail: Anchor,
980 display_point: Option<DisplayPoint>,
981 },
982 FromSelection {
983 selection_tail: Anchor,
984 },
985}
986
987/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
988/// a breakpoint on them.
989#[derive(Clone, Copy, Debug, PartialEq, Eq)]
990struct PhantomBreakpointIndicator {
991 display_row: DisplayRow,
992 /// There's a small debounce between hovering over the line and showing the indicator.
993 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
994 is_active: bool,
995 collides_with_existing_breakpoint: bool,
996}
997
998/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
999///
1000/// See the [module level documentation](self) for more information.
1001pub struct Editor {
1002 focus_handle: FocusHandle,
1003 last_focused_descendant: Option<WeakFocusHandle>,
1004 /// The text buffer being edited
1005 buffer: Entity<MultiBuffer>,
1006 /// Map of how text in the buffer should be displayed.
1007 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1008 pub display_map: Entity<DisplayMap>,
1009 placeholder_display_map: Option<Entity<DisplayMap>>,
1010 pub selections: SelectionsCollection,
1011 pub scroll_manager: ScrollManager,
1012 /// When inline assist editors are linked, they all render cursors because
1013 /// typing enters text into each of them, even the ones that aren't focused.
1014 pub(crate) show_cursor_when_unfocused: bool,
1015 columnar_selection_state: Option<ColumnarSelectionState>,
1016 add_selections_state: Option<AddSelectionsState>,
1017 select_next_state: Option<SelectNextState>,
1018 select_prev_state: Option<SelectNextState>,
1019 selection_history: SelectionHistory,
1020 defer_selection_effects: bool,
1021 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1022 autoclose_regions: Vec<AutocloseRegion>,
1023 snippet_stack: InvalidationStack<SnippetState>,
1024 select_syntax_node_history: SelectSyntaxNodeHistory,
1025 ime_transaction: Option<TransactionId>,
1026 pub diagnostics_max_severity: DiagnosticSeverity,
1027 active_diagnostics: ActiveDiagnostic,
1028 show_inline_diagnostics: bool,
1029 inline_diagnostics_update: Task<()>,
1030 inline_diagnostics_enabled: bool,
1031 diagnostics_enabled: bool,
1032 word_completions_enabled: bool,
1033 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1034 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1035 hard_wrap: Option<usize>,
1036 project: Option<Entity<Project>>,
1037 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1038 completion_provider: Option<Rc<dyn CompletionProvider>>,
1039 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1040 blink_manager: Entity<BlinkManager>,
1041 show_cursor_names: bool,
1042 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1043 pub show_local_selections: bool,
1044 mode: EditorMode,
1045 show_breadcrumbs: bool,
1046 show_gutter: bool,
1047 show_scrollbars: ScrollbarAxes,
1048 minimap_visibility: MinimapVisibility,
1049 offset_content: bool,
1050 disable_expand_excerpt_buttons: bool,
1051 show_line_numbers: Option<bool>,
1052 use_relative_line_numbers: Option<bool>,
1053 show_git_diff_gutter: Option<bool>,
1054 show_code_actions: Option<bool>,
1055 show_runnables: Option<bool>,
1056 show_breakpoints: Option<bool>,
1057 show_wrap_guides: Option<bool>,
1058 show_indent_guides: Option<bool>,
1059 highlight_order: usize,
1060 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1061 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1062 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1063 scrollbar_marker_state: ScrollbarMarkerState,
1064 active_indent_guides_state: ActiveIndentGuidesState,
1065 nav_history: Option<ItemNavHistory>,
1066 context_menu: RefCell<Option<CodeContextMenu>>,
1067 context_menu_options: Option<ContextMenuOptions>,
1068 mouse_context_menu: Option<MouseContextMenu>,
1069 completion_tasks: Vec<(CompletionId, Task<()>)>,
1070 inline_blame_popover: Option<InlineBlamePopover>,
1071 inline_blame_popover_show_task: Option<Task<()>>,
1072 signature_help_state: SignatureHelpState,
1073 auto_signature_help: Option<bool>,
1074 find_all_references_task_sources: Vec<Anchor>,
1075 next_completion_id: CompletionId,
1076 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1077 code_actions_task: Option<Task<Result<()>>>,
1078 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1079 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1080 document_highlights_task: Option<Task<()>>,
1081 linked_editing_range_task: Option<Task<Option<()>>>,
1082 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1083 pending_rename: Option<RenameState>,
1084 searchable: bool,
1085 cursor_shape: CursorShape,
1086 current_line_highlight: Option<CurrentLineHighlight>,
1087 collapse_matches: bool,
1088 autoindent_mode: Option<AutoindentMode>,
1089 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1090 input_enabled: bool,
1091 use_modal_editing: bool,
1092 read_only: bool,
1093 leader_id: Option<CollaboratorId>,
1094 remote_id: Option<ViewId>,
1095 pub hover_state: HoverState,
1096 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1097 gutter_hovered: bool,
1098 hovered_link_state: Option<HoveredLinkState>,
1099 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1100 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1101 active_edit_prediction: Option<EditPredictionState>,
1102 /// Used to prevent flickering as the user types while the menu is open
1103 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1104 edit_prediction_settings: EditPredictionSettings,
1105 edit_predictions_hidden_for_vim_mode: bool,
1106 show_edit_predictions_override: Option<bool>,
1107 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1108 edit_prediction_preview: EditPredictionPreview,
1109 edit_prediction_indent_conflict: bool,
1110 edit_prediction_requires_modifier_in_indent_conflict: bool,
1111 inlay_hint_cache: InlayHintCache,
1112 next_inlay_id: usize,
1113 _subscriptions: Vec<Subscription>,
1114 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1115 gutter_dimensions: GutterDimensions,
1116 style: Option<EditorStyle>,
1117 text_style_refinement: Option<TextStyleRefinement>,
1118 next_editor_action_id: EditorActionId,
1119 editor_actions: Rc<
1120 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1121 >,
1122 use_autoclose: bool,
1123 use_auto_surround: bool,
1124 auto_replace_emoji_shortcode: bool,
1125 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1126 show_git_blame_gutter: bool,
1127 show_git_blame_inline: bool,
1128 show_git_blame_inline_delay_task: Option<Task<()>>,
1129 git_blame_inline_enabled: bool,
1130 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1131 serialize_dirty_buffers: bool,
1132 show_selection_menu: Option<bool>,
1133 blame: Option<Entity<GitBlame>>,
1134 blame_subscription: Option<Subscription>,
1135 custom_context_menu: Option<
1136 Box<
1137 dyn 'static
1138 + Fn(
1139 &mut Self,
1140 DisplayPoint,
1141 &mut Window,
1142 &mut Context<Self>,
1143 ) -> Option<Entity<ui::ContextMenu>>,
1144 >,
1145 >,
1146 last_bounds: Option<Bounds<Pixels>>,
1147 last_position_map: Option<Rc<PositionMap>>,
1148 expect_bounds_change: Option<Bounds<Pixels>>,
1149 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1150 tasks_update_task: Option<Task<()>>,
1151 breakpoint_store: Option<Entity<BreakpointStore>>,
1152 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1153 hovered_diff_hunk_row: Option<DisplayRow>,
1154 pull_diagnostics_task: Task<()>,
1155 in_project_search: bool,
1156 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1157 breadcrumb_header: Option<String>,
1158 focused_block: Option<FocusedBlock>,
1159 next_scroll_position: NextScrollCursorCenterTopBottom,
1160 addons: HashMap<TypeId, Box<dyn Addon>>,
1161 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1162 load_diff_task: Option<Shared<Task<()>>>,
1163 /// Whether we are temporarily displaying a diff other than git's
1164 temporary_diff_override: bool,
1165 selection_mark_mode: bool,
1166 toggle_fold_multiple_buffers: Task<()>,
1167 _scroll_cursor_center_top_bottom_task: Task<()>,
1168 serialize_selections: Task<()>,
1169 serialize_folds: Task<()>,
1170 mouse_cursor_hidden: bool,
1171 minimap: Option<Entity<Self>>,
1172 hide_mouse_mode: HideMouseMode,
1173 pub change_list: ChangeList,
1174 inline_value_cache: InlineValueCache,
1175 selection_drag_state: SelectionDragState,
1176 next_color_inlay_id: usize,
1177 colors: Option<LspColorData>,
1178 folding_newlines: Task<()>,
1179 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1180}
1181
1182#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1183enum NextScrollCursorCenterTopBottom {
1184 #[default]
1185 Center,
1186 Top,
1187 Bottom,
1188}
1189
1190impl NextScrollCursorCenterTopBottom {
1191 fn next(&self) -> Self {
1192 match self {
1193 Self::Center => Self::Top,
1194 Self::Top => Self::Bottom,
1195 Self::Bottom => Self::Center,
1196 }
1197 }
1198}
1199
1200#[derive(Clone)]
1201pub struct EditorSnapshot {
1202 pub mode: EditorMode,
1203 show_gutter: bool,
1204 show_line_numbers: Option<bool>,
1205 show_git_diff_gutter: Option<bool>,
1206 show_code_actions: Option<bool>,
1207 show_runnables: Option<bool>,
1208 show_breakpoints: Option<bool>,
1209 git_blame_gutter_max_author_length: Option<usize>,
1210 pub display_snapshot: DisplaySnapshot,
1211 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1212 is_focused: bool,
1213 scroll_anchor: ScrollAnchor,
1214 ongoing_scroll: OngoingScroll,
1215 current_line_highlight: CurrentLineHighlight,
1216 gutter_hovered: bool,
1217}
1218
1219#[derive(Default, Debug, Clone, Copy)]
1220pub struct GutterDimensions {
1221 pub left_padding: Pixels,
1222 pub right_padding: Pixels,
1223 pub width: Pixels,
1224 pub margin: Pixels,
1225 pub git_blame_entries_width: Option<Pixels>,
1226}
1227
1228impl GutterDimensions {
1229 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1230 Self {
1231 margin: Self::default_gutter_margin(font_id, font_size, cx),
1232 ..Default::default()
1233 }
1234 }
1235
1236 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1237 -cx.text_system().descent(font_id, font_size)
1238 }
1239 /// The full width of the space taken up by the gutter.
1240 pub fn full_width(&self) -> Pixels {
1241 self.margin + self.width
1242 }
1243
1244 /// The width of the space reserved for the fold indicators,
1245 /// use alongside 'justify_end' and `gutter_width` to
1246 /// right align content with the line numbers
1247 pub fn fold_area_width(&self) -> Pixels {
1248 self.margin + self.right_padding
1249 }
1250}
1251
1252struct CharacterDimensions {
1253 em_width: Pixels,
1254 em_advance: Pixels,
1255 line_height: Pixels,
1256}
1257
1258#[derive(Debug)]
1259pub struct RemoteSelection {
1260 pub replica_id: ReplicaId,
1261 pub selection: Selection<Anchor>,
1262 pub cursor_shape: CursorShape,
1263 pub collaborator_id: CollaboratorId,
1264 pub line_mode: bool,
1265 pub user_name: Option<SharedString>,
1266 pub color: PlayerColor,
1267}
1268
1269#[derive(Clone, Debug)]
1270struct SelectionHistoryEntry {
1271 selections: Arc<[Selection<Anchor>]>,
1272 select_next_state: Option<SelectNextState>,
1273 select_prev_state: Option<SelectNextState>,
1274 add_selections_state: Option<AddSelectionsState>,
1275}
1276
1277#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1278enum SelectionHistoryMode {
1279 Normal,
1280 Undoing,
1281 Redoing,
1282 Skipping,
1283}
1284
1285#[derive(Clone, PartialEq, Eq, Hash)]
1286struct HoveredCursor {
1287 replica_id: u16,
1288 selection_id: usize,
1289}
1290
1291impl Default for SelectionHistoryMode {
1292 fn default() -> Self {
1293 Self::Normal
1294 }
1295}
1296
1297#[derive(Debug)]
1298/// SelectionEffects controls the side-effects of updating the selection.
1299///
1300/// The default behaviour does "what you mostly want":
1301/// - it pushes to the nav history if the cursor moved by >10 lines
1302/// - it re-triggers completion requests
1303/// - it scrolls to fit
1304///
1305/// You might want to modify these behaviours. For example when doing a "jump"
1306/// like go to definition, we always want to add to nav history; but when scrolling
1307/// in vim mode we never do.
1308///
1309/// Similarly, you might want to disable scrolling if you don't want the viewport to
1310/// move.
1311#[derive(Clone)]
1312pub struct SelectionEffects {
1313 nav_history: Option<bool>,
1314 completions: bool,
1315 scroll: Option<Autoscroll>,
1316}
1317
1318impl Default for SelectionEffects {
1319 fn default() -> Self {
1320 Self {
1321 nav_history: None,
1322 completions: true,
1323 scroll: Some(Autoscroll::fit()),
1324 }
1325 }
1326}
1327impl SelectionEffects {
1328 pub fn scroll(scroll: Autoscroll) -> Self {
1329 Self {
1330 scroll: Some(scroll),
1331 ..Default::default()
1332 }
1333 }
1334
1335 pub fn no_scroll() -> Self {
1336 Self {
1337 scroll: None,
1338 ..Default::default()
1339 }
1340 }
1341
1342 pub fn completions(self, completions: bool) -> Self {
1343 Self {
1344 completions,
1345 ..self
1346 }
1347 }
1348
1349 pub fn nav_history(self, nav_history: bool) -> Self {
1350 Self {
1351 nav_history: Some(nav_history),
1352 ..self
1353 }
1354 }
1355}
1356
1357struct DeferredSelectionEffectsState {
1358 changed: bool,
1359 effects: SelectionEffects,
1360 old_cursor_position: Anchor,
1361 history_entry: SelectionHistoryEntry,
1362}
1363
1364#[derive(Default)]
1365struct SelectionHistory {
1366 #[allow(clippy::type_complexity)]
1367 selections_by_transaction:
1368 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1369 mode: SelectionHistoryMode,
1370 undo_stack: VecDeque<SelectionHistoryEntry>,
1371 redo_stack: VecDeque<SelectionHistoryEntry>,
1372}
1373
1374impl SelectionHistory {
1375 #[track_caller]
1376 fn insert_transaction(
1377 &mut self,
1378 transaction_id: TransactionId,
1379 selections: Arc<[Selection<Anchor>]>,
1380 ) {
1381 if selections.is_empty() {
1382 log::error!(
1383 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1384 std::panic::Location::caller()
1385 );
1386 return;
1387 }
1388 self.selections_by_transaction
1389 .insert(transaction_id, (selections, None));
1390 }
1391
1392 #[allow(clippy::type_complexity)]
1393 fn transaction(
1394 &self,
1395 transaction_id: TransactionId,
1396 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1397 self.selections_by_transaction.get(&transaction_id)
1398 }
1399
1400 #[allow(clippy::type_complexity)]
1401 fn transaction_mut(
1402 &mut self,
1403 transaction_id: TransactionId,
1404 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1405 self.selections_by_transaction.get_mut(&transaction_id)
1406 }
1407
1408 fn push(&mut self, entry: SelectionHistoryEntry) {
1409 if !entry.selections.is_empty() {
1410 match self.mode {
1411 SelectionHistoryMode::Normal => {
1412 self.push_undo(entry);
1413 self.redo_stack.clear();
1414 }
1415 SelectionHistoryMode::Undoing => self.push_redo(entry),
1416 SelectionHistoryMode::Redoing => self.push_undo(entry),
1417 SelectionHistoryMode::Skipping => {}
1418 }
1419 }
1420 }
1421
1422 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1423 if self
1424 .undo_stack
1425 .back()
1426 .is_none_or(|e| e.selections != entry.selections)
1427 {
1428 self.undo_stack.push_back(entry);
1429 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1430 self.undo_stack.pop_front();
1431 }
1432 }
1433 }
1434
1435 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1436 if self
1437 .redo_stack
1438 .back()
1439 .is_none_or(|e| e.selections != entry.selections)
1440 {
1441 self.redo_stack.push_back(entry);
1442 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1443 self.redo_stack.pop_front();
1444 }
1445 }
1446 }
1447}
1448
1449#[derive(Clone, Copy)]
1450pub struct RowHighlightOptions {
1451 pub autoscroll: bool,
1452 pub include_gutter: bool,
1453}
1454
1455impl Default for RowHighlightOptions {
1456 fn default() -> Self {
1457 Self {
1458 autoscroll: Default::default(),
1459 include_gutter: true,
1460 }
1461 }
1462}
1463
1464struct RowHighlight {
1465 index: usize,
1466 range: Range<Anchor>,
1467 color: Hsla,
1468 options: RowHighlightOptions,
1469 type_id: TypeId,
1470}
1471
1472#[derive(Clone, Debug)]
1473struct AddSelectionsState {
1474 groups: Vec<AddSelectionsGroup>,
1475}
1476
1477#[derive(Clone, Debug)]
1478struct AddSelectionsGroup {
1479 above: bool,
1480 stack: Vec<usize>,
1481}
1482
1483#[derive(Clone)]
1484struct SelectNextState {
1485 query: AhoCorasick,
1486 wordwise: bool,
1487 done: bool,
1488}
1489
1490impl std::fmt::Debug for SelectNextState {
1491 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1492 f.debug_struct(std::any::type_name::<Self>())
1493 .field("wordwise", &self.wordwise)
1494 .field("done", &self.done)
1495 .finish()
1496 }
1497}
1498
1499#[derive(Debug)]
1500struct AutocloseRegion {
1501 selection_id: usize,
1502 range: Range<Anchor>,
1503 pair: BracketPair,
1504}
1505
1506#[derive(Debug)]
1507struct SnippetState {
1508 ranges: Vec<Vec<Range<Anchor>>>,
1509 active_index: usize,
1510 choices: Vec<Option<Vec<String>>>,
1511}
1512
1513#[doc(hidden)]
1514pub struct RenameState {
1515 pub range: Range<Anchor>,
1516 pub old_name: Arc<str>,
1517 pub editor: Entity<Editor>,
1518 block_id: CustomBlockId,
1519}
1520
1521struct InvalidationStack<T>(Vec<T>);
1522
1523struct RegisteredEditPredictionProvider {
1524 provider: Arc<dyn EditPredictionProviderHandle>,
1525 _subscription: Subscription,
1526}
1527
1528#[derive(Debug, PartialEq, Eq)]
1529pub struct ActiveDiagnosticGroup {
1530 pub active_range: Range<Anchor>,
1531 pub active_message: String,
1532 pub group_id: usize,
1533 pub blocks: HashSet<CustomBlockId>,
1534}
1535
1536#[derive(Debug, PartialEq, Eq)]
1537
1538pub(crate) enum ActiveDiagnostic {
1539 None,
1540 All,
1541 Group(ActiveDiagnosticGroup),
1542}
1543
1544#[derive(Serialize, Deserialize, Clone, Debug)]
1545pub struct ClipboardSelection {
1546 /// The number of bytes in this selection.
1547 pub len: usize,
1548 /// Whether this was a full-line selection.
1549 pub is_entire_line: bool,
1550 /// The indentation of the first line when this content was originally copied.
1551 pub first_line_indent: u32,
1552}
1553
1554// selections, scroll behavior, was newest selection reversed
1555type SelectSyntaxNodeHistoryState = (
1556 Box<[Selection<usize>]>,
1557 SelectSyntaxNodeScrollBehavior,
1558 bool,
1559);
1560
1561#[derive(Default)]
1562struct SelectSyntaxNodeHistory {
1563 stack: Vec<SelectSyntaxNodeHistoryState>,
1564 // disable temporarily to allow changing selections without losing the stack
1565 pub disable_clearing: bool,
1566}
1567
1568impl SelectSyntaxNodeHistory {
1569 pub fn try_clear(&mut self) {
1570 if !self.disable_clearing {
1571 self.stack.clear();
1572 }
1573 }
1574
1575 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1576 self.stack.push(selection);
1577 }
1578
1579 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1580 self.stack.pop()
1581 }
1582}
1583
1584enum SelectSyntaxNodeScrollBehavior {
1585 CursorTop,
1586 FitSelection,
1587 CursorBottom,
1588}
1589
1590#[derive(Debug)]
1591pub(crate) struct NavigationData {
1592 cursor_anchor: Anchor,
1593 cursor_position: Point,
1594 scroll_anchor: ScrollAnchor,
1595 scroll_top_row: u32,
1596}
1597
1598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1599pub enum GotoDefinitionKind {
1600 Symbol,
1601 Declaration,
1602 Type,
1603 Implementation,
1604}
1605
1606#[derive(Debug, Clone)]
1607enum InlayHintRefreshReason {
1608 ModifiersChanged(bool),
1609 Toggle(bool),
1610 SettingsChange(InlayHintSettings),
1611 NewLinesShown,
1612 BufferEdited(HashSet<Arc<Language>>),
1613 RefreshRequested,
1614 ExcerptsRemoved(Vec<ExcerptId>),
1615}
1616
1617impl InlayHintRefreshReason {
1618 fn description(&self) -> &'static str {
1619 match self {
1620 Self::ModifiersChanged(_) => "modifiers changed",
1621 Self::Toggle(_) => "toggle",
1622 Self::SettingsChange(_) => "settings change",
1623 Self::NewLinesShown => "new lines shown",
1624 Self::BufferEdited(_) => "buffer edited",
1625 Self::RefreshRequested => "refresh requested",
1626 Self::ExcerptsRemoved(_) => "excerpts removed",
1627 }
1628 }
1629}
1630
1631pub enum FormatTarget {
1632 Buffers(HashSet<Entity<Buffer>>),
1633 Ranges(Vec<Range<MultiBufferPoint>>),
1634}
1635
1636pub(crate) struct FocusedBlock {
1637 id: BlockId,
1638 focus_handle: WeakFocusHandle,
1639}
1640
1641#[derive(Clone)]
1642enum JumpData {
1643 MultiBufferRow {
1644 row: MultiBufferRow,
1645 line_offset_from_top: u32,
1646 },
1647 MultiBufferPoint {
1648 excerpt_id: ExcerptId,
1649 position: Point,
1650 anchor: text::Anchor,
1651 line_offset_from_top: u32,
1652 },
1653}
1654
1655pub enum MultibufferSelectionMode {
1656 First,
1657 All,
1658}
1659
1660#[derive(Clone, Copy, Debug, Default)]
1661pub struct RewrapOptions {
1662 pub override_language_settings: bool,
1663 pub preserve_existing_whitespace: bool,
1664}
1665
1666impl Editor {
1667 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1668 let buffer = cx.new(|cx| Buffer::local("", cx));
1669 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1670 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1671 }
1672
1673 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let buffer = cx.new(|cx| Buffer::local("", cx));
1675 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1676 Self::new(EditorMode::full(), buffer, None, window, cx)
1677 }
1678
1679 pub fn auto_height(
1680 min_lines: usize,
1681 max_lines: usize,
1682 window: &mut Window,
1683 cx: &mut Context<Self>,
1684 ) -> Self {
1685 let buffer = cx.new(|cx| Buffer::local("", cx));
1686 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1687 Self::new(
1688 EditorMode::AutoHeight {
1689 min_lines,
1690 max_lines: Some(max_lines),
1691 },
1692 buffer,
1693 None,
1694 window,
1695 cx,
1696 )
1697 }
1698
1699 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1700 /// The editor grows as tall as needed to fit its content.
1701 pub fn auto_height_unbounded(
1702 min_lines: usize,
1703 window: &mut Window,
1704 cx: &mut Context<Self>,
1705 ) -> Self {
1706 let buffer = cx.new(|cx| Buffer::local("", cx));
1707 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1708 Self::new(
1709 EditorMode::AutoHeight {
1710 min_lines,
1711 max_lines: None,
1712 },
1713 buffer,
1714 None,
1715 window,
1716 cx,
1717 )
1718 }
1719
1720 pub fn for_buffer(
1721 buffer: Entity<Buffer>,
1722 project: Option<Entity<Project>>,
1723 window: &mut Window,
1724 cx: &mut Context<Self>,
1725 ) -> Self {
1726 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1727 Self::new(EditorMode::full(), buffer, project, window, cx)
1728 }
1729
1730 pub fn for_multibuffer(
1731 buffer: Entity<MultiBuffer>,
1732 project: Option<Entity<Project>>,
1733 window: &mut Window,
1734 cx: &mut Context<Self>,
1735 ) -> Self {
1736 Self::new(EditorMode::full(), buffer, project, window, cx)
1737 }
1738
1739 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1740 let mut clone = Self::new(
1741 self.mode.clone(),
1742 self.buffer.clone(),
1743 self.project.clone(),
1744 window,
1745 cx,
1746 );
1747 self.display_map.update(cx, |display_map, cx| {
1748 let snapshot = display_map.snapshot(cx);
1749 clone.display_map.update(cx, |display_map, cx| {
1750 display_map.set_state(&snapshot, cx);
1751 });
1752 });
1753 clone.folds_did_change(cx);
1754 clone.selections.clone_state(&self.selections);
1755 clone.scroll_manager.clone_state(&self.scroll_manager);
1756 clone.searchable = self.searchable;
1757 clone.read_only = self.read_only;
1758 clone
1759 }
1760
1761 pub fn new(
1762 mode: EditorMode,
1763 buffer: Entity<MultiBuffer>,
1764 project: Option<Entity<Project>>,
1765 window: &mut Window,
1766 cx: &mut Context<Self>,
1767 ) -> Self {
1768 Editor::new_internal(mode, buffer, project, None, window, cx)
1769 }
1770
1771 fn new_internal(
1772 mode: EditorMode,
1773 buffer: Entity<MultiBuffer>,
1774 project: Option<Entity<Project>>,
1775 display_map: Option<Entity<DisplayMap>>,
1776 window: &mut Window,
1777 cx: &mut Context<Self>,
1778 ) -> Self {
1779 debug_assert!(
1780 display_map.is_none() || mode.is_minimap(),
1781 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1782 );
1783
1784 let full_mode = mode.is_full();
1785 let is_minimap = mode.is_minimap();
1786 let diagnostics_max_severity = if full_mode {
1787 EditorSettings::get_global(cx)
1788 .diagnostics_max_severity
1789 .unwrap_or(DiagnosticSeverity::Hint)
1790 } else {
1791 DiagnosticSeverity::Off
1792 };
1793 let style = window.text_style();
1794 let font_size = style.font_size.to_pixels(window.rem_size());
1795 let editor = cx.entity().downgrade();
1796 let fold_placeholder = FoldPlaceholder {
1797 constrain_width: false,
1798 render: Arc::new(move |fold_id, fold_range, cx| {
1799 let editor = editor.clone();
1800 div()
1801 .id(fold_id)
1802 .bg(cx.theme().colors().ghost_element_background)
1803 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1804 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1805 .rounded_xs()
1806 .size_full()
1807 .cursor_pointer()
1808 .child("⋯")
1809 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1810 .on_click(move |_, _window, cx| {
1811 editor
1812 .update(cx, |editor, cx| {
1813 editor.unfold_ranges(
1814 &[fold_range.start..fold_range.end],
1815 true,
1816 false,
1817 cx,
1818 );
1819 cx.stop_propagation();
1820 })
1821 .ok();
1822 })
1823 .into_any()
1824 }),
1825 merge_adjacent: true,
1826 ..FoldPlaceholder::default()
1827 };
1828 let display_map = display_map.unwrap_or_else(|| {
1829 cx.new(|cx| {
1830 DisplayMap::new(
1831 buffer.clone(),
1832 style.font(),
1833 font_size,
1834 None,
1835 FILE_HEADER_HEIGHT,
1836 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1837 fold_placeholder,
1838 diagnostics_max_severity,
1839 cx,
1840 )
1841 })
1842 });
1843
1844 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1845
1846 let blink_manager = cx.new(|cx| {
1847 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1848 if is_minimap {
1849 blink_manager.disable(cx);
1850 }
1851 blink_manager
1852 });
1853
1854 let soft_wrap_mode_override =
1855 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1856
1857 let mut project_subscriptions = Vec::new();
1858 if full_mode && let Some(project) = project.as_ref() {
1859 project_subscriptions.push(cx.subscribe_in(
1860 project,
1861 window,
1862 |editor, _, event, window, cx| match event {
1863 project::Event::RefreshCodeLens => {
1864 // we always query lens with actions, without storing them, always refreshing them
1865 }
1866 project::Event::RefreshInlayHints => {
1867 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1868 }
1869 project::Event::LanguageServerAdded(..)
1870 | project::Event::LanguageServerRemoved(..) => {
1871 if editor.tasks_update_task.is_none() {
1872 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1873 }
1874 }
1875 project::Event::SnippetEdit(id, snippet_edits) => {
1876 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1877 let focus_handle = editor.focus_handle(cx);
1878 if focus_handle.is_focused(window) {
1879 let snapshot = buffer.read(cx).snapshot();
1880 for (range, snippet) in snippet_edits {
1881 let editor_range =
1882 language::range_from_lsp(*range).to_offset(&snapshot);
1883 editor
1884 .insert_snippet(
1885 &[editor_range],
1886 snippet.clone(),
1887 window,
1888 cx,
1889 )
1890 .ok();
1891 }
1892 }
1893 }
1894 }
1895 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1896 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1897 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1898 }
1899 }
1900
1901 project::Event::EntryRenamed(transaction) => {
1902 let Some(workspace) = editor.workspace() else {
1903 return;
1904 };
1905 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1906 else {
1907 return;
1908 };
1909 if active_editor.entity_id() == cx.entity_id() {
1910 let edited_buffers_already_open = {
1911 let other_editors: Vec<Entity<Editor>> = workspace
1912 .read(cx)
1913 .panes()
1914 .iter()
1915 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1916 .filter(|editor| editor.entity_id() != cx.entity_id())
1917 .collect();
1918
1919 transaction.0.keys().all(|buffer| {
1920 other_editors.iter().any(|editor| {
1921 let multi_buffer = editor.read(cx).buffer();
1922 multi_buffer.read(cx).is_singleton()
1923 && multi_buffer.read(cx).as_singleton().map_or(
1924 false,
1925 |singleton| {
1926 singleton.entity_id() == buffer.entity_id()
1927 },
1928 )
1929 })
1930 })
1931 };
1932
1933 if !edited_buffers_already_open {
1934 let workspace = workspace.downgrade();
1935 let transaction = transaction.clone();
1936 cx.defer_in(window, move |_, window, cx| {
1937 cx.spawn_in(window, async move |editor, cx| {
1938 Self::open_project_transaction(
1939 &editor,
1940 workspace,
1941 transaction,
1942 "Rename".to_string(),
1943 cx,
1944 )
1945 .await
1946 .ok()
1947 })
1948 .detach();
1949 });
1950 }
1951 }
1952 }
1953
1954 _ => {}
1955 },
1956 ));
1957 if let Some(task_inventory) = project
1958 .read(cx)
1959 .task_store()
1960 .read(cx)
1961 .task_inventory()
1962 .cloned()
1963 {
1964 project_subscriptions.push(cx.observe_in(
1965 &task_inventory,
1966 window,
1967 |editor, _, window, cx| {
1968 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1969 },
1970 ));
1971 };
1972
1973 project_subscriptions.push(cx.subscribe_in(
1974 &project.read(cx).breakpoint_store(),
1975 window,
1976 |editor, _, event, window, cx| match event {
1977 BreakpointStoreEvent::ClearDebugLines => {
1978 editor.clear_row_highlights::<ActiveDebugLine>();
1979 editor.refresh_inline_values(cx);
1980 }
1981 BreakpointStoreEvent::SetDebugLine => {
1982 if editor.go_to_active_debug_line(window, cx) {
1983 cx.stop_propagation();
1984 }
1985
1986 editor.refresh_inline_values(cx);
1987 }
1988 _ => {}
1989 },
1990 ));
1991 let git_store = project.read(cx).git_store().clone();
1992 let project = project.clone();
1993 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1994 if let GitStoreEvent::RepositoryUpdated(
1995 _,
1996 RepositoryEvent::Updated {
1997 new_instance: true, ..
1998 },
1999 _,
2000 ) = event
2001 {
2002 this.load_diff_task = Some(
2003 update_uncommitted_diff_for_buffer(
2004 cx.entity(),
2005 &project,
2006 this.buffer.read(cx).all_buffers(),
2007 this.buffer.clone(),
2008 cx,
2009 )
2010 .shared(),
2011 );
2012 }
2013 }));
2014 }
2015
2016 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2017
2018 let inlay_hint_settings =
2019 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2020 let focus_handle = cx.focus_handle();
2021 if !is_minimap {
2022 cx.on_focus(&focus_handle, window, Self::handle_focus)
2023 .detach();
2024 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2025 .detach();
2026 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2027 .detach();
2028 cx.on_blur(&focus_handle, window, Self::handle_blur)
2029 .detach();
2030 cx.observe_pending_input(window, Self::observe_pending_input)
2031 .detach();
2032 }
2033
2034 let show_indent_guides =
2035 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2036 Some(false)
2037 } else {
2038 None
2039 };
2040
2041 let breakpoint_store = match (&mode, project.as_ref()) {
2042 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2043 _ => None,
2044 };
2045
2046 let mut code_action_providers = Vec::new();
2047 let mut load_uncommitted_diff = None;
2048 if let Some(project) = project.clone() {
2049 load_uncommitted_diff = Some(
2050 update_uncommitted_diff_for_buffer(
2051 cx.entity(),
2052 &project,
2053 buffer.read(cx).all_buffers(),
2054 buffer.clone(),
2055 cx,
2056 )
2057 .shared(),
2058 );
2059 code_action_providers.push(Rc::new(project) as Rc<_>);
2060 }
2061
2062 let mut editor = Self {
2063 focus_handle,
2064 show_cursor_when_unfocused: false,
2065 last_focused_descendant: None,
2066 buffer: buffer.clone(),
2067 display_map: display_map.clone(),
2068 placeholder_display_map: None,
2069 selections,
2070 scroll_manager: ScrollManager::new(cx),
2071 columnar_selection_state: None,
2072 add_selections_state: None,
2073 select_next_state: None,
2074 select_prev_state: None,
2075 selection_history: SelectionHistory::default(),
2076 defer_selection_effects: false,
2077 deferred_selection_effects_state: None,
2078 autoclose_regions: Vec::new(),
2079 snippet_stack: InvalidationStack::default(),
2080 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2081 ime_transaction: None,
2082 active_diagnostics: ActiveDiagnostic::None,
2083 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2084 inline_diagnostics_update: Task::ready(()),
2085 inline_diagnostics: Vec::new(),
2086 soft_wrap_mode_override,
2087 diagnostics_max_severity,
2088 hard_wrap: None,
2089 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2090 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2091 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2092 project,
2093 blink_manager: blink_manager.clone(),
2094 show_local_selections: true,
2095 show_scrollbars: ScrollbarAxes {
2096 horizontal: full_mode,
2097 vertical: full_mode,
2098 },
2099 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2100 offset_content: !matches!(mode, EditorMode::SingleLine),
2101 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2102 show_gutter: full_mode,
2103 show_line_numbers: (!full_mode).then_some(false),
2104 use_relative_line_numbers: None,
2105 disable_expand_excerpt_buttons: !full_mode,
2106 show_git_diff_gutter: None,
2107 show_code_actions: None,
2108 show_runnables: None,
2109 show_breakpoints: None,
2110 show_wrap_guides: None,
2111 show_indent_guides,
2112 highlight_order: 0,
2113 highlighted_rows: HashMap::default(),
2114 background_highlights: HashMap::default(),
2115 gutter_highlights: HashMap::default(),
2116 scrollbar_marker_state: ScrollbarMarkerState::default(),
2117 active_indent_guides_state: ActiveIndentGuidesState::default(),
2118 nav_history: None,
2119 context_menu: RefCell::new(None),
2120 context_menu_options: None,
2121 mouse_context_menu: None,
2122 completion_tasks: Vec::new(),
2123 inline_blame_popover: None,
2124 inline_blame_popover_show_task: None,
2125 signature_help_state: SignatureHelpState::default(),
2126 auto_signature_help: None,
2127 find_all_references_task_sources: Vec::new(),
2128 next_completion_id: 0,
2129 next_inlay_id: 0,
2130 code_action_providers,
2131 available_code_actions: None,
2132 code_actions_task: None,
2133 quick_selection_highlight_task: None,
2134 debounced_selection_highlight_task: None,
2135 document_highlights_task: None,
2136 linked_editing_range_task: None,
2137 pending_rename: None,
2138 searchable: !is_minimap,
2139 cursor_shape: EditorSettings::get_global(cx)
2140 .cursor_shape
2141 .unwrap_or_default(),
2142 current_line_highlight: None,
2143 autoindent_mode: Some(AutoindentMode::EachLine),
2144 collapse_matches: false,
2145 workspace: None,
2146 input_enabled: !is_minimap,
2147 use_modal_editing: full_mode,
2148 read_only: is_minimap,
2149 use_autoclose: true,
2150 use_auto_surround: true,
2151 auto_replace_emoji_shortcode: false,
2152 jsx_tag_auto_close_enabled_in_any_buffer: false,
2153 leader_id: None,
2154 remote_id: None,
2155 hover_state: HoverState::default(),
2156 pending_mouse_down: None,
2157 hovered_link_state: None,
2158 edit_prediction_provider: None,
2159 active_edit_prediction: None,
2160 stale_edit_prediction_in_menu: None,
2161 edit_prediction_preview: EditPredictionPreview::Inactive {
2162 released_too_fast: false,
2163 },
2164 inline_diagnostics_enabled: full_mode,
2165 diagnostics_enabled: full_mode,
2166 word_completions_enabled: full_mode,
2167 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2168 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2169 gutter_hovered: false,
2170 pixel_position_of_newest_cursor: None,
2171 last_bounds: None,
2172 last_position_map: None,
2173 expect_bounds_change: None,
2174 gutter_dimensions: GutterDimensions::default(),
2175 style: None,
2176 show_cursor_names: false,
2177 hovered_cursors: HashMap::default(),
2178 next_editor_action_id: EditorActionId::default(),
2179 editor_actions: Rc::default(),
2180 edit_predictions_hidden_for_vim_mode: false,
2181 show_edit_predictions_override: None,
2182 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2183 edit_prediction_settings: EditPredictionSettings::Disabled,
2184 edit_prediction_indent_conflict: false,
2185 edit_prediction_requires_modifier_in_indent_conflict: true,
2186 custom_context_menu: None,
2187 show_git_blame_gutter: false,
2188 show_git_blame_inline: false,
2189 show_selection_menu: None,
2190 show_git_blame_inline_delay_task: None,
2191 git_blame_inline_enabled: full_mode
2192 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2193 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2194 serialize_dirty_buffers: !is_minimap
2195 && ProjectSettings::get_global(cx)
2196 .session
2197 .restore_unsaved_buffers,
2198 blame: None,
2199 blame_subscription: None,
2200 tasks: BTreeMap::default(),
2201
2202 breakpoint_store,
2203 gutter_breakpoint_indicator: (None, None),
2204 hovered_diff_hunk_row: None,
2205 _subscriptions: (!is_minimap)
2206 .then(|| {
2207 vec![
2208 cx.observe(&buffer, Self::on_buffer_changed),
2209 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2210 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2211 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2212 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2213 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2214 cx.observe_window_activation(window, |editor, window, cx| {
2215 let active = window.is_window_active();
2216 editor.blink_manager.update(cx, |blink_manager, cx| {
2217 if active {
2218 blink_manager.enable(cx);
2219 } else {
2220 blink_manager.disable(cx);
2221 }
2222 });
2223 if active {
2224 editor.show_mouse_cursor(cx);
2225 }
2226 }),
2227 ]
2228 })
2229 .unwrap_or_default(),
2230 tasks_update_task: None,
2231 pull_diagnostics_task: Task::ready(()),
2232 colors: None,
2233 next_color_inlay_id: 0,
2234 linked_edit_ranges: Default::default(),
2235 in_project_search: false,
2236 previous_search_ranges: None,
2237 breadcrumb_header: None,
2238 focused_block: None,
2239 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2240 addons: HashMap::default(),
2241 registered_buffers: HashMap::default(),
2242 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2243 selection_mark_mode: false,
2244 toggle_fold_multiple_buffers: Task::ready(()),
2245 serialize_selections: Task::ready(()),
2246 serialize_folds: Task::ready(()),
2247 text_style_refinement: None,
2248 load_diff_task: load_uncommitted_diff,
2249 temporary_diff_override: false,
2250 mouse_cursor_hidden: false,
2251 minimap: None,
2252 hide_mouse_mode: EditorSettings::get_global(cx)
2253 .hide_mouse
2254 .unwrap_or_default(),
2255 change_list: ChangeList::new(),
2256 mode,
2257 selection_drag_state: SelectionDragState::None,
2258 folding_newlines: Task::ready(()),
2259 lookup_key: None,
2260 };
2261
2262 if is_minimap {
2263 return editor;
2264 }
2265
2266 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2267 editor
2268 ._subscriptions
2269 .push(cx.observe(breakpoints, |_, _, cx| {
2270 cx.notify();
2271 }));
2272 }
2273 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2274 editor._subscriptions.extend(project_subscriptions);
2275
2276 editor._subscriptions.push(cx.subscribe_in(
2277 &cx.entity(),
2278 window,
2279 |editor, _, e: &EditorEvent, window, cx| match e {
2280 EditorEvent::ScrollPositionChanged { local, .. } => {
2281 if *local {
2282 let new_anchor = editor.scroll_manager.anchor();
2283 let snapshot = editor.snapshot(window, cx);
2284 editor.update_restoration_data(cx, move |data| {
2285 data.scroll_position = (
2286 new_anchor.top_row(&snapshot.buffer_snapshot),
2287 new_anchor.offset,
2288 );
2289 });
2290 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2291 editor.inline_blame_popover.take();
2292 }
2293 }
2294 EditorEvent::Edited { .. } => {
2295 if !vim_enabled(cx) {
2296 let (map, selections) = editor.selections.all_adjusted_display(cx);
2297 let pop_state = editor
2298 .change_list
2299 .last()
2300 .map(|previous| {
2301 previous.len() == selections.len()
2302 && previous.iter().enumerate().all(|(ix, p)| {
2303 p.to_display_point(&map).row()
2304 == selections[ix].head().row()
2305 })
2306 })
2307 .unwrap_or(false);
2308 let new_positions = selections
2309 .into_iter()
2310 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2311 .collect();
2312 editor
2313 .change_list
2314 .push_to_change_list(pop_state, new_positions);
2315 }
2316 }
2317 _ => (),
2318 },
2319 ));
2320
2321 if let Some(dap_store) = editor
2322 .project
2323 .as_ref()
2324 .map(|project| project.read(cx).dap_store())
2325 {
2326 let weak_editor = cx.weak_entity();
2327
2328 editor
2329 ._subscriptions
2330 .push(
2331 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2332 let session_entity = cx.entity();
2333 weak_editor
2334 .update(cx, |editor, cx| {
2335 editor._subscriptions.push(
2336 cx.subscribe(&session_entity, Self::on_debug_session_event),
2337 );
2338 })
2339 .ok();
2340 }),
2341 );
2342
2343 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2344 editor
2345 ._subscriptions
2346 .push(cx.subscribe(&session, Self::on_debug_session_event));
2347 }
2348 }
2349
2350 // skip adding the initial selection to selection history
2351 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2352 editor.end_selection(window, cx);
2353 editor.selection_history.mode = SelectionHistoryMode::Normal;
2354
2355 editor.scroll_manager.show_scrollbars(window, cx);
2356 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2357
2358 if full_mode {
2359 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2360 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2361
2362 if editor.git_blame_inline_enabled {
2363 editor.start_git_blame_inline(false, window, cx);
2364 }
2365
2366 editor.go_to_active_debug_line(window, cx);
2367
2368 if let Some(buffer) = buffer.read(cx).as_singleton()
2369 && let Some(project) = editor.project()
2370 {
2371 let handle = project.update(cx, |project, cx| {
2372 project.register_buffer_with_language_servers(&buffer, cx)
2373 });
2374 editor
2375 .registered_buffers
2376 .insert(buffer.read(cx).remote_id(), handle);
2377 }
2378
2379 editor.minimap =
2380 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2381 editor.colors = Some(LspColorData::new(cx));
2382 editor.update_lsp_data(false, None, window, cx);
2383 }
2384
2385 if editor.mode.is_full() {
2386 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2387 }
2388
2389 editor
2390 }
2391
2392 pub fn deploy_mouse_context_menu(
2393 &mut self,
2394 position: gpui::Point<Pixels>,
2395 context_menu: Entity<ContextMenu>,
2396 window: &mut Window,
2397 cx: &mut Context<Self>,
2398 ) {
2399 self.mouse_context_menu = Some(MouseContextMenu::new(
2400 self,
2401 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2402 context_menu,
2403 window,
2404 cx,
2405 ));
2406 }
2407
2408 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2409 self.mouse_context_menu
2410 .as_ref()
2411 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2412 }
2413
2414 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2415 if self
2416 .selections
2417 .pending
2418 .as_ref()
2419 .is_some_and(|pending_selection| {
2420 let snapshot = self.buffer().read(cx).snapshot(cx);
2421 pending_selection
2422 .selection
2423 .range()
2424 .includes(range, &snapshot)
2425 })
2426 {
2427 return true;
2428 }
2429
2430 self.selections
2431 .disjoint_in_range::<usize>(range.clone(), cx)
2432 .into_iter()
2433 .any(|selection| {
2434 // This is needed to cover a corner case, if we just check for an existing
2435 // selection in the fold range, having a cursor at the start of the fold
2436 // marks it as selected. Non-empty selections don't cause this.
2437 let length = selection.end - selection.start;
2438 length > 0
2439 })
2440 }
2441
2442 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2443 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2444 }
2445
2446 fn key_context_internal(
2447 &self,
2448 has_active_edit_prediction: bool,
2449 window: &Window,
2450 cx: &App,
2451 ) -> KeyContext {
2452 let mut key_context = KeyContext::new_with_defaults();
2453 key_context.add("Editor");
2454 let mode = match self.mode {
2455 EditorMode::SingleLine => "single_line",
2456 EditorMode::AutoHeight { .. } => "auto_height",
2457 EditorMode::Minimap { .. } => "minimap",
2458 EditorMode::Full { .. } => "full",
2459 };
2460
2461 if EditorSettings::jupyter_enabled(cx) {
2462 key_context.add("jupyter");
2463 }
2464
2465 key_context.set("mode", mode);
2466 if self.pending_rename.is_some() {
2467 key_context.add("renaming");
2468 }
2469
2470 match self.context_menu.borrow().as_ref() {
2471 Some(CodeContextMenu::Completions(menu)) => {
2472 if menu.visible() {
2473 key_context.add("menu");
2474 key_context.add("showing_completions");
2475 }
2476 }
2477 Some(CodeContextMenu::CodeActions(menu)) => {
2478 if menu.visible() {
2479 key_context.add("menu");
2480 key_context.add("showing_code_actions")
2481 }
2482 }
2483 None => {}
2484 }
2485
2486 if self.signature_help_state.has_multiple_signatures() {
2487 key_context.add("showing_signature_help");
2488 }
2489
2490 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2491 if !self.focus_handle(cx).contains_focused(window, cx)
2492 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2493 {
2494 for addon in self.addons.values() {
2495 addon.extend_key_context(&mut key_context, cx)
2496 }
2497 }
2498
2499 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2500 if let Some(extension) = singleton_buffer
2501 .read(cx)
2502 .file()
2503 .and_then(|file| file.path().extension()?.to_str())
2504 {
2505 key_context.set("extension", extension.to_string());
2506 }
2507 } else {
2508 key_context.add("multibuffer");
2509 }
2510
2511 if has_active_edit_prediction {
2512 if self.edit_prediction_in_conflict() {
2513 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2514 } else {
2515 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2516 key_context.add("copilot_suggestion");
2517 }
2518 }
2519
2520 if self.selection_mark_mode {
2521 key_context.add("selection_mode");
2522 }
2523
2524 key_context
2525 }
2526
2527 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2528 if self.mouse_cursor_hidden {
2529 self.mouse_cursor_hidden = false;
2530 cx.notify();
2531 }
2532 }
2533
2534 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2535 let hide_mouse_cursor = match origin {
2536 HideMouseCursorOrigin::TypingAction => {
2537 matches!(
2538 self.hide_mouse_mode,
2539 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2540 )
2541 }
2542 HideMouseCursorOrigin::MovementAction => {
2543 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2544 }
2545 };
2546 if self.mouse_cursor_hidden != hide_mouse_cursor {
2547 self.mouse_cursor_hidden = hide_mouse_cursor;
2548 cx.notify();
2549 }
2550 }
2551
2552 pub fn edit_prediction_in_conflict(&self) -> bool {
2553 if !self.show_edit_predictions_in_menu() {
2554 return false;
2555 }
2556
2557 let showing_completions = self
2558 .context_menu
2559 .borrow()
2560 .as_ref()
2561 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2562
2563 showing_completions
2564 || self.edit_prediction_requires_modifier()
2565 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2566 // bindings to insert tab characters.
2567 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2568 }
2569
2570 pub fn accept_edit_prediction_keybind(
2571 &self,
2572 accept_partial: bool,
2573 window: &Window,
2574 cx: &App,
2575 ) -> AcceptEditPredictionBinding {
2576 let key_context = self.key_context_internal(true, window, cx);
2577 let in_conflict = self.edit_prediction_in_conflict();
2578
2579 let bindings = if accept_partial {
2580 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2581 } else {
2582 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2583 };
2584
2585 // TODO: if the binding contains multiple keystrokes, display all of them, not
2586 // just the first one.
2587 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2588 !in_conflict
2589 || binding
2590 .keystrokes()
2591 .first()
2592 .is_some_and(|keystroke| keystroke.modifiers().modified())
2593 }))
2594 }
2595
2596 pub fn new_file(
2597 workspace: &mut Workspace,
2598 _: &workspace::NewFile,
2599 window: &mut Window,
2600 cx: &mut Context<Workspace>,
2601 ) {
2602 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2603 "Failed to create buffer",
2604 window,
2605 cx,
2606 |e, _, _| match e.error_code() {
2607 ErrorCode::RemoteUpgradeRequired => Some(format!(
2608 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2609 e.error_tag("required").unwrap_or("the latest version")
2610 )),
2611 _ => None,
2612 },
2613 );
2614 }
2615
2616 pub fn new_in_workspace(
2617 workspace: &mut Workspace,
2618 window: &mut Window,
2619 cx: &mut Context<Workspace>,
2620 ) -> Task<Result<Entity<Editor>>> {
2621 let project = workspace.project().clone();
2622 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2623
2624 cx.spawn_in(window, async move |workspace, cx| {
2625 let buffer = create.await?;
2626 workspace.update_in(cx, |workspace, window, cx| {
2627 let editor =
2628 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2629 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2630 editor
2631 })
2632 })
2633 }
2634
2635 fn new_file_vertical(
2636 workspace: &mut Workspace,
2637 _: &workspace::NewFileSplitVertical,
2638 window: &mut Window,
2639 cx: &mut Context<Workspace>,
2640 ) {
2641 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2642 }
2643
2644 fn new_file_horizontal(
2645 workspace: &mut Workspace,
2646 _: &workspace::NewFileSplitHorizontal,
2647 window: &mut Window,
2648 cx: &mut Context<Workspace>,
2649 ) {
2650 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2651 }
2652
2653 fn new_file_in_direction(
2654 workspace: &mut Workspace,
2655 direction: SplitDirection,
2656 window: &mut Window,
2657 cx: &mut Context<Workspace>,
2658 ) {
2659 let project = workspace.project().clone();
2660 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2661
2662 cx.spawn_in(window, async move |workspace, cx| {
2663 let buffer = create.await?;
2664 workspace.update_in(cx, move |workspace, window, cx| {
2665 workspace.split_item(
2666 direction,
2667 Box::new(
2668 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2669 ),
2670 window,
2671 cx,
2672 )
2673 })?;
2674 anyhow::Ok(())
2675 })
2676 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2677 match e.error_code() {
2678 ErrorCode::RemoteUpgradeRequired => Some(format!(
2679 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2680 e.error_tag("required").unwrap_or("the latest version")
2681 )),
2682 _ => None,
2683 }
2684 });
2685 }
2686
2687 pub fn leader_id(&self) -> Option<CollaboratorId> {
2688 self.leader_id
2689 }
2690
2691 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2692 &self.buffer
2693 }
2694
2695 pub fn project(&self) -> Option<&Entity<Project>> {
2696 self.project.as_ref()
2697 }
2698
2699 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2700 self.workspace.as_ref()?.0.upgrade()
2701 }
2702
2703 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2704 self.buffer().read(cx).title(cx)
2705 }
2706
2707 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2708 let git_blame_gutter_max_author_length = self
2709 .render_git_blame_gutter(cx)
2710 .then(|| {
2711 if let Some(blame) = self.blame.as_ref() {
2712 let max_author_length =
2713 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2714 Some(max_author_length)
2715 } else {
2716 None
2717 }
2718 })
2719 .flatten();
2720
2721 EditorSnapshot {
2722 mode: self.mode.clone(),
2723 show_gutter: self.show_gutter,
2724 show_line_numbers: self.show_line_numbers,
2725 show_git_diff_gutter: self.show_git_diff_gutter,
2726 show_code_actions: self.show_code_actions,
2727 show_runnables: self.show_runnables,
2728 show_breakpoints: self.show_breakpoints,
2729 git_blame_gutter_max_author_length,
2730 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2731 placeholder_display_snapshot: self
2732 .placeholder_display_map
2733 .as_ref()
2734 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2735 scroll_anchor: self.scroll_manager.anchor(),
2736 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2737 is_focused: self.focus_handle.is_focused(window),
2738 current_line_highlight: self
2739 .current_line_highlight
2740 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2741 gutter_hovered: self.gutter_hovered,
2742 }
2743 }
2744
2745 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2746 self.buffer.read(cx).language_at(point, cx)
2747 }
2748
2749 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2750 self.buffer.read(cx).read(cx).file_at(point).cloned()
2751 }
2752
2753 pub fn active_excerpt(
2754 &self,
2755 cx: &App,
2756 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2757 self.buffer
2758 .read(cx)
2759 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2760 }
2761
2762 pub fn mode(&self) -> &EditorMode {
2763 &self.mode
2764 }
2765
2766 pub fn set_mode(&mut self, mode: EditorMode) {
2767 self.mode = mode;
2768 }
2769
2770 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2771 self.collaboration_hub.as_deref()
2772 }
2773
2774 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2775 self.collaboration_hub = Some(hub);
2776 }
2777
2778 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2779 self.in_project_search = in_project_search;
2780 }
2781
2782 pub fn set_custom_context_menu(
2783 &mut self,
2784 f: impl 'static
2785 + Fn(
2786 &mut Self,
2787 DisplayPoint,
2788 &mut Window,
2789 &mut Context<Self>,
2790 ) -> Option<Entity<ui::ContextMenu>>,
2791 ) {
2792 self.custom_context_menu = Some(Box::new(f))
2793 }
2794
2795 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2796 self.completion_provider = provider;
2797 }
2798
2799 #[cfg(any(test, feature = "test-support"))]
2800 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2801 self.completion_provider.clone()
2802 }
2803
2804 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2805 self.semantics_provider.clone()
2806 }
2807
2808 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2809 self.semantics_provider = provider;
2810 }
2811
2812 pub fn set_edit_prediction_provider<T>(
2813 &mut self,
2814 provider: Option<Entity<T>>,
2815 window: &mut Window,
2816 cx: &mut Context<Self>,
2817 ) where
2818 T: EditPredictionProvider,
2819 {
2820 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2821 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2822 if this.focus_handle.is_focused(window) {
2823 this.update_visible_edit_prediction(window, cx);
2824 }
2825 }),
2826 provider: Arc::new(provider),
2827 });
2828 self.update_edit_prediction_settings(cx);
2829 self.refresh_edit_prediction(false, false, window, cx);
2830 }
2831
2832 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2833 self.placeholder_display_map
2834 .as_ref()
2835 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2836 }
2837
2838 pub fn set_placeholder_text(
2839 &mut self,
2840 placeholder_text: &str,
2841 window: &mut Window,
2842 cx: &mut Context<Self>,
2843 ) {
2844 let multibuffer = cx
2845 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2846
2847 let style = window.text_style();
2848
2849 self.placeholder_display_map = Some(cx.new(|cx| {
2850 DisplayMap::new(
2851 multibuffer,
2852 style.font(),
2853 style.font_size.to_pixels(window.rem_size()),
2854 None,
2855 FILE_HEADER_HEIGHT,
2856 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2857 Default::default(),
2858 DiagnosticSeverity::Off,
2859 cx,
2860 )
2861 }));
2862 cx.notify();
2863 }
2864
2865 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2866 self.cursor_shape = cursor_shape;
2867
2868 // Disrupt blink for immediate user feedback that the cursor shape has changed
2869 self.blink_manager.update(cx, BlinkManager::show_cursor);
2870
2871 cx.notify();
2872 }
2873
2874 pub fn set_current_line_highlight(
2875 &mut self,
2876 current_line_highlight: Option<CurrentLineHighlight>,
2877 ) {
2878 self.current_line_highlight = current_line_highlight;
2879 }
2880
2881 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2882 self.collapse_matches = collapse_matches;
2883 }
2884
2885 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2886 let buffers = self.buffer.read(cx).all_buffers();
2887 let Some(project) = self.project.as_ref() else {
2888 return;
2889 };
2890 project.update(cx, |project, cx| {
2891 for buffer in buffers {
2892 self.registered_buffers
2893 .entry(buffer.read(cx).remote_id())
2894 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2895 }
2896 })
2897 }
2898
2899 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2900 if self.collapse_matches {
2901 return range.start..range.start;
2902 }
2903 range.clone()
2904 }
2905
2906 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2907 if self.display_map.read(cx).clip_at_line_ends != clip {
2908 self.display_map
2909 .update(cx, |map, _| map.clip_at_line_ends = clip);
2910 }
2911 }
2912
2913 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2914 self.input_enabled = input_enabled;
2915 }
2916
2917 pub fn set_edit_predictions_hidden_for_vim_mode(
2918 &mut self,
2919 hidden: bool,
2920 window: &mut Window,
2921 cx: &mut Context<Self>,
2922 ) {
2923 if hidden != self.edit_predictions_hidden_for_vim_mode {
2924 self.edit_predictions_hidden_for_vim_mode = hidden;
2925 if hidden {
2926 self.update_visible_edit_prediction(window, cx);
2927 } else {
2928 self.refresh_edit_prediction(true, false, window, cx);
2929 }
2930 }
2931 }
2932
2933 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2934 self.menu_edit_predictions_policy = value;
2935 }
2936
2937 pub fn set_autoindent(&mut self, autoindent: bool) {
2938 if autoindent {
2939 self.autoindent_mode = Some(AutoindentMode::EachLine);
2940 } else {
2941 self.autoindent_mode = None;
2942 }
2943 }
2944
2945 pub fn read_only(&self, cx: &App) -> bool {
2946 self.read_only || self.buffer.read(cx).read_only()
2947 }
2948
2949 pub fn set_read_only(&mut self, read_only: bool) {
2950 self.read_only = read_only;
2951 }
2952
2953 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2954 self.use_autoclose = autoclose;
2955 }
2956
2957 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2958 self.use_auto_surround = auto_surround;
2959 }
2960
2961 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2962 self.auto_replace_emoji_shortcode = auto_replace;
2963 }
2964
2965 pub fn toggle_edit_predictions(
2966 &mut self,
2967 _: &ToggleEditPrediction,
2968 window: &mut Window,
2969 cx: &mut Context<Self>,
2970 ) {
2971 if self.show_edit_predictions_override.is_some() {
2972 self.set_show_edit_predictions(None, window, cx);
2973 } else {
2974 let show_edit_predictions = !self.edit_predictions_enabled();
2975 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2976 }
2977 }
2978
2979 pub fn set_show_edit_predictions(
2980 &mut self,
2981 show_edit_predictions: Option<bool>,
2982 window: &mut Window,
2983 cx: &mut Context<Self>,
2984 ) {
2985 self.show_edit_predictions_override = show_edit_predictions;
2986 self.update_edit_prediction_settings(cx);
2987
2988 if let Some(false) = show_edit_predictions {
2989 self.discard_edit_prediction(false, cx);
2990 } else {
2991 self.refresh_edit_prediction(false, true, window, cx);
2992 }
2993 }
2994
2995 fn edit_predictions_disabled_in_scope(
2996 &self,
2997 buffer: &Entity<Buffer>,
2998 buffer_position: language::Anchor,
2999 cx: &App,
3000 ) -> bool {
3001 let snapshot = buffer.read(cx).snapshot();
3002 let settings = snapshot.settings_at(buffer_position, cx);
3003
3004 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3005 return false;
3006 };
3007
3008 scope.override_name().is_some_and(|scope_name| {
3009 settings
3010 .edit_predictions_disabled_in
3011 .iter()
3012 .any(|s| s == scope_name)
3013 })
3014 }
3015
3016 pub fn set_use_modal_editing(&mut self, to: bool) {
3017 self.use_modal_editing = to;
3018 }
3019
3020 pub fn use_modal_editing(&self) -> bool {
3021 self.use_modal_editing
3022 }
3023
3024 fn selections_did_change(
3025 &mut self,
3026 local: bool,
3027 old_cursor_position: &Anchor,
3028 effects: SelectionEffects,
3029 window: &mut Window,
3030 cx: &mut Context<Self>,
3031 ) {
3032 window.invalidate_character_coordinates();
3033
3034 // Copy selections to primary selection buffer
3035 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3036 if local {
3037 let selections = self.selections.all::<usize>(cx);
3038 let buffer_handle = self.buffer.read(cx).read(cx);
3039
3040 let mut text = String::new();
3041 for (index, selection) in selections.iter().enumerate() {
3042 let text_for_selection = buffer_handle
3043 .text_for_range(selection.start..selection.end)
3044 .collect::<String>();
3045
3046 text.push_str(&text_for_selection);
3047 if index != selections.len() - 1 {
3048 text.push('\n');
3049 }
3050 }
3051
3052 if !text.is_empty() {
3053 cx.write_to_primary(ClipboardItem::new_string(text));
3054 }
3055 }
3056
3057 let selection_anchors = self.selections.disjoint_anchors();
3058
3059 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3060 self.buffer.update(cx, |buffer, cx| {
3061 buffer.set_active_selections(
3062 &selection_anchors,
3063 self.selections.line_mode,
3064 self.cursor_shape,
3065 cx,
3066 )
3067 });
3068 }
3069 let display_map = self
3070 .display_map
3071 .update(cx, |display_map, cx| display_map.snapshot(cx));
3072 let buffer = &display_map.buffer_snapshot;
3073 if self.selections.count() == 1 {
3074 self.add_selections_state = None;
3075 }
3076 self.select_next_state = None;
3077 self.select_prev_state = None;
3078 self.select_syntax_node_history.try_clear();
3079 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3080 self.snippet_stack.invalidate(&selection_anchors, buffer);
3081 self.take_rename(false, window, cx);
3082
3083 let newest_selection = self.selections.newest_anchor();
3084 let new_cursor_position = newest_selection.head();
3085 let selection_start = newest_selection.start;
3086
3087 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3088 self.push_to_nav_history(
3089 *old_cursor_position,
3090 Some(new_cursor_position.to_point(buffer)),
3091 false,
3092 effects.nav_history == Some(true),
3093 cx,
3094 );
3095 }
3096
3097 if local {
3098 if let Some(buffer_id) = new_cursor_position.buffer_id
3099 && !self.registered_buffers.contains_key(&buffer_id)
3100 && let Some(project) = self.project.as_ref()
3101 {
3102 project.update(cx, |project, cx| {
3103 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3104 return;
3105 };
3106 self.registered_buffers.insert(
3107 buffer_id,
3108 project.register_buffer_with_language_servers(&buffer, cx),
3109 );
3110 })
3111 }
3112
3113 let mut context_menu = self.context_menu.borrow_mut();
3114 let completion_menu = match context_menu.as_ref() {
3115 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3116 Some(CodeContextMenu::CodeActions(_)) => {
3117 *context_menu = None;
3118 None
3119 }
3120 None => None,
3121 };
3122 let completion_position = completion_menu.map(|menu| menu.initial_position);
3123 drop(context_menu);
3124
3125 if effects.completions
3126 && let Some(completion_position) = completion_position
3127 {
3128 let start_offset = selection_start.to_offset(buffer);
3129 let position_matches = start_offset == completion_position.to_offset(buffer);
3130 let continue_showing = if position_matches {
3131 if self.snippet_stack.is_empty() {
3132 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3133 } else {
3134 // Snippet choices can be shown even when the cursor is in whitespace.
3135 // Dismissing the menu with actions like backspace is handled by
3136 // invalidation regions.
3137 true
3138 }
3139 } else {
3140 false
3141 };
3142
3143 if continue_showing {
3144 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3145 } else {
3146 self.hide_context_menu(window, cx);
3147 }
3148 }
3149
3150 hide_hover(self, cx);
3151
3152 if old_cursor_position.to_display_point(&display_map).row()
3153 != new_cursor_position.to_display_point(&display_map).row()
3154 {
3155 self.available_code_actions.take();
3156 }
3157 self.refresh_code_actions(window, cx);
3158 self.refresh_document_highlights(cx);
3159 self.refresh_selected_text_highlights(false, window, cx);
3160 refresh_matching_bracket_highlights(self, window, cx);
3161 self.update_visible_edit_prediction(window, cx);
3162 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3163 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3164 self.inline_blame_popover.take();
3165 if self.git_blame_inline_enabled {
3166 self.start_inline_blame_timer(window, cx);
3167 }
3168 }
3169
3170 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3171 cx.emit(EditorEvent::SelectionsChanged { local });
3172
3173 let selections = &self.selections.disjoint;
3174 if selections.len() == 1 {
3175 cx.emit(SearchEvent::ActiveMatchChanged)
3176 }
3177 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3178 let inmemory_selections = selections
3179 .iter()
3180 .map(|s| {
3181 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3182 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3183 })
3184 .collect();
3185 self.update_restoration_data(cx, |data| {
3186 data.selections = inmemory_selections;
3187 });
3188
3189 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3190 && let Some(workspace_id) =
3191 self.workspace.as_ref().and_then(|workspace| workspace.1)
3192 {
3193 let snapshot = self.buffer().read(cx).snapshot(cx);
3194 let selections = selections.clone();
3195 let background_executor = cx.background_executor().clone();
3196 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3197 self.serialize_selections = cx.background_spawn(async move {
3198 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3199 let db_selections = selections
3200 .iter()
3201 .map(|selection| {
3202 (
3203 selection.start.to_offset(&snapshot),
3204 selection.end.to_offset(&snapshot),
3205 )
3206 })
3207 .collect();
3208
3209 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3210 .await
3211 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3212 .log_err();
3213 });
3214 }
3215 }
3216
3217 cx.notify();
3218 }
3219
3220 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3221 use text::ToOffset as _;
3222 use text::ToPoint as _;
3223
3224 if self.mode.is_minimap()
3225 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3226 {
3227 return;
3228 }
3229
3230 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3231 return;
3232 };
3233
3234 let snapshot = singleton.read(cx).snapshot();
3235 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3236 let display_snapshot = display_map.snapshot(cx);
3237
3238 display_snapshot
3239 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3240 .map(|fold| {
3241 fold.range.start.text_anchor.to_point(&snapshot)
3242 ..fold.range.end.text_anchor.to_point(&snapshot)
3243 })
3244 .collect()
3245 });
3246 self.update_restoration_data(cx, |data| {
3247 data.folds = inmemory_folds;
3248 });
3249
3250 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3251 return;
3252 };
3253 let background_executor = cx.background_executor().clone();
3254 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3255 let db_folds = self.display_map.update(cx, |display_map, cx| {
3256 display_map
3257 .snapshot(cx)
3258 .folds_in_range(0..snapshot.len())
3259 .map(|fold| {
3260 (
3261 fold.range.start.text_anchor.to_offset(&snapshot),
3262 fold.range.end.text_anchor.to_offset(&snapshot),
3263 )
3264 })
3265 .collect()
3266 });
3267 self.serialize_folds = cx.background_spawn(async move {
3268 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3269 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3270 .await
3271 .with_context(|| {
3272 format!(
3273 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3274 )
3275 })
3276 .log_err();
3277 });
3278 }
3279
3280 pub fn sync_selections(
3281 &mut self,
3282 other: Entity<Editor>,
3283 cx: &mut Context<Self>,
3284 ) -> gpui::Subscription {
3285 let other_selections = other.read(cx).selections.disjoint.to_vec();
3286 self.selections.change_with(cx, |selections| {
3287 selections.select_anchors(other_selections);
3288 });
3289
3290 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3291 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3292 let other_selections = other.read(cx).selections.disjoint.to_vec();
3293 if other_selections.is_empty() {
3294 return;
3295 }
3296 this.selections.change_with(cx, |selections| {
3297 selections.select_anchors(other_selections);
3298 });
3299 }
3300 });
3301
3302 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3303 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3304 let these_selections = this.selections.disjoint.to_vec();
3305 if these_selections.is_empty() {
3306 return;
3307 }
3308 other.update(cx, |other_editor, cx| {
3309 other_editor.selections.change_with(cx, |selections| {
3310 selections.select_anchors(these_selections);
3311 })
3312 });
3313 }
3314 });
3315
3316 Subscription::join(other_subscription, this_subscription)
3317 }
3318
3319 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3320 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3321 /// effects of selection change occur at the end of the transaction.
3322 pub fn change_selections<R>(
3323 &mut self,
3324 effects: SelectionEffects,
3325 window: &mut Window,
3326 cx: &mut Context<Self>,
3327 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3328 ) -> R {
3329 if let Some(state) = &mut self.deferred_selection_effects_state {
3330 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3331 state.effects.completions = effects.completions;
3332 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3333 let (changed, result) = self.selections.change_with(cx, change);
3334 state.changed |= changed;
3335 return result;
3336 }
3337 let mut state = DeferredSelectionEffectsState {
3338 changed: false,
3339 effects,
3340 old_cursor_position: self.selections.newest_anchor().head(),
3341 history_entry: SelectionHistoryEntry {
3342 selections: self.selections.disjoint_anchors(),
3343 select_next_state: self.select_next_state.clone(),
3344 select_prev_state: self.select_prev_state.clone(),
3345 add_selections_state: self.add_selections_state.clone(),
3346 },
3347 };
3348 let (changed, result) = self.selections.change_with(cx, change);
3349 state.changed = state.changed || changed;
3350 if self.defer_selection_effects {
3351 self.deferred_selection_effects_state = Some(state);
3352 } else {
3353 self.apply_selection_effects(state, window, cx);
3354 }
3355 result
3356 }
3357
3358 /// Defers the effects of selection change, so that the effects of multiple calls to
3359 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3360 /// to selection history and the state of popovers based on selection position aren't
3361 /// erroneously updated.
3362 pub fn with_selection_effects_deferred<R>(
3363 &mut self,
3364 window: &mut Window,
3365 cx: &mut Context<Self>,
3366 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3367 ) -> R {
3368 let already_deferred = self.defer_selection_effects;
3369 self.defer_selection_effects = true;
3370 let result = update(self, window, cx);
3371 if !already_deferred {
3372 self.defer_selection_effects = false;
3373 if let Some(state) = self.deferred_selection_effects_state.take() {
3374 self.apply_selection_effects(state, window, cx);
3375 }
3376 }
3377 result
3378 }
3379
3380 fn apply_selection_effects(
3381 &mut self,
3382 state: DeferredSelectionEffectsState,
3383 window: &mut Window,
3384 cx: &mut Context<Self>,
3385 ) {
3386 if state.changed {
3387 self.selection_history.push(state.history_entry);
3388
3389 if let Some(autoscroll) = state.effects.scroll {
3390 self.request_autoscroll(autoscroll, cx);
3391 }
3392
3393 let old_cursor_position = &state.old_cursor_position;
3394
3395 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3396
3397 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3398 self.show_signature_help(&ShowSignatureHelp, window, cx);
3399 }
3400 }
3401 }
3402
3403 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3404 where
3405 I: IntoIterator<Item = (Range<S>, T)>,
3406 S: ToOffset,
3407 T: Into<Arc<str>>,
3408 {
3409 if self.read_only(cx) {
3410 return;
3411 }
3412
3413 self.buffer
3414 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3415 }
3416
3417 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3418 where
3419 I: IntoIterator<Item = (Range<S>, T)>,
3420 S: ToOffset,
3421 T: Into<Arc<str>>,
3422 {
3423 if self.read_only(cx) {
3424 return;
3425 }
3426
3427 self.buffer.update(cx, |buffer, cx| {
3428 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3429 });
3430 }
3431
3432 pub fn edit_with_block_indent<I, S, T>(
3433 &mut self,
3434 edits: I,
3435 original_indent_columns: Vec<Option<u32>>,
3436 cx: &mut Context<Self>,
3437 ) where
3438 I: IntoIterator<Item = (Range<S>, T)>,
3439 S: ToOffset,
3440 T: Into<Arc<str>>,
3441 {
3442 if self.read_only(cx) {
3443 return;
3444 }
3445
3446 self.buffer.update(cx, |buffer, cx| {
3447 buffer.edit(
3448 edits,
3449 Some(AutoindentMode::Block {
3450 original_indent_columns,
3451 }),
3452 cx,
3453 )
3454 });
3455 }
3456
3457 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3458 self.hide_context_menu(window, cx);
3459
3460 match phase {
3461 SelectPhase::Begin {
3462 position,
3463 add,
3464 click_count,
3465 } => self.begin_selection(position, add, click_count, window, cx),
3466 SelectPhase::BeginColumnar {
3467 position,
3468 goal_column,
3469 reset,
3470 mode,
3471 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3472 SelectPhase::Extend {
3473 position,
3474 click_count,
3475 } => self.extend_selection(position, click_count, window, cx),
3476 SelectPhase::Update {
3477 position,
3478 goal_column,
3479 scroll_delta,
3480 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3481 SelectPhase::End => self.end_selection(window, cx),
3482 }
3483 }
3484
3485 fn extend_selection(
3486 &mut self,
3487 position: DisplayPoint,
3488 click_count: usize,
3489 window: &mut Window,
3490 cx: &mut Context<Self>,
3491 ) {
3492 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3493 let tail = self.selections.newest::<usize>(cx).tail();
3494 self.begin_selection(position, false, click_count, window, cx);
3495
3496 let position = position.to_offset(&display_map, Bias::Left);
3497 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3498
3499 let mut pending_selection = self
3500 .selections
3501 .pending_anchor()
3502 .expect("extend_selection not called with pending selection");
3503 if position >= tail {
3504 pending_selection.start = tail_anchor;
3505 } else {
3506 pending_selection.end = tail_anchor;
3507 pending_selection.reversed = true;
3508 }
3509
3510 let mut pending_mode = self.selections.pending_mode().unwrap();
3511 match &mut pending_mode {
3512 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3513 _ => {}
3514 }
3515
3516 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3517 SelectionEffects::scroll(Autoscroll::fit())
3518 } else {
3519 SelectionEffects::no_scroll()
3520 };
3521
3522 self.change_selections(effects, window, cx, |s| {
3523 s.set_pending(pending_selection, pending_mode)
3524 });
3525 }
3526
3527 fn begin_selection(
3528 &mut self,
3529 position: DisplayPoint,
3530 add: bool,
3531 click_count: usize,
3532 window: &mut Window,
3533 cx: &mut Context<Self>,
3534 ) {
3535 if !self.focus_handle.is_focused(window) {
3536 self.last_focused_descendant = None;
3537 window.focus(&self.focus_handle);
3538 }
3539
3540 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3541 let buffer = &display_map.buffer_snapshot;
3542 let position = display_map.clip_point(position, Bias::Left);
3543
3544 let start;
3545 let end;
3546 let mode;
3547 let mut auto_scroll;
3548 match click_count {
3549 1 => {
3550 start = buffer.anchor_before(position.to_point(&display_map));
3551 end = start;
3552 mode = SelectMode::Character;
3553 auto_scroll = true;
3554 }
3555 2 => {
3556 let position = display_map
3557 .clip_point(position, Bias::Left)
3558 .to_offset(&display_map, Bias::Left);
3559 let (range, _) = buffer.surrounding_word(position, false);
3560 start = buffer.anchor_before(range.start);
3561 end = buffer.anchor_before(range.end);
3562 mode = SelectMode::Word(start..end);
3563 auto_scroll = true;
3564 }
3565 3 => {
3566 let position = display_map
3567 .clip_point(position, Bias::Left)
3568 .to_point(&display_map);
3569 let line_start = display_map.prev_line_boundary(position).0;
3570 let next_line_start = buffer.clip_point(
3571 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3572 Bias::Left,
3573 );
3574 start = buffer.anchor_before(line_start);
3575 end = buffer.anchor_before(next_line_start);
3576 mode = SelectMode::Line(start..end);
3577 auto_scroll = true;
3578 }
3579 _ => {
3580 start = buffer.anchor_before(0);
3581 end = buffer.anchor_before(buffer.len());
3582 mode = SelectMode::All;
3583 auto_scroll = false;
3584 }
3585 }
3586 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3587
3588 let point_to_delete: Option<usize> = {
3589 let selected_points: Vec<Selection<Point>> =
3590 self.selections.disjoint_in_range(start..end, cx);
3591
3592 if !add || click_count > 1 {
3593 None
3594 } else if !selected_points.is_empty() {
3595 Some(selected_points[0].id)
3596 } else {
3597 let clicked_point_already_selected =
3598 self.selections.disjoint.iter().find(|selection| {
3599 selection.start.to_point(buffer) == start.to_point(buffer)
3600 || selection.end.to_point(buffer) == end.to_point(buffer)
3601 });
3602
3603 clicked_point_already_selected.map(|selection| selection.id)
3604 }
3605 };
3606
3607 let selections_count = self.selections.count();
3608 let effects = if auto_scroll {
3609 SelectionEffects::default()
3610 } else {
3611 SelectionEffects::no_scroll()
3612 };
3613
3614 self.change_selections(effects, window, cx, |s| {
3615 if let Some(point_to_delete) = point_to_delete {
3616 s.delete(point_to_delete);
3617
3618 if selections_count == 1 {
3619 s.set_pending_anchor_range(start..end, mode);
3620 }
3621 } else {
3622 if !add {
3623 s.clear_disjoint();
3624 }
3625
3626 s.set_pending_anchor_range(start..end, mode);
3627 }
3628 });
3629 }
3630
3631 fn begin_columnar_selection(
3632 &mut self,
3633 position: DisplayPoint,
3634 goal_column: u32,
3635 reset: bool,
3636 mode: ColumnarMode,
3637 window: &mut Window,
3638 cx: &mut Context<Self>,
3639 ) {
3640 if !self.focus_handle.is_focused(window) {
3641 self.last_focused_descendant = None;
3642 window.focus(&self.focus_handle);
3643 }
3644
3645 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3646
3647 if reset {
3648 let pointer_position = display_map
3649 .buffer_snapshot
3650 .anchor_before(position.to_point(&display_map));
3651
3652 self.change_selections(
3653 SelectionEffects::scroll(Autoscroll::newest()),
3654 window,
3655 cx,
3656 |s| {
3657 s.clear_disjoint();
3658 s.set_pending_anchor_range(
3659 pointer_position..pointer_position,
3660 SelectMode::Character,
3661 );
3662 },
3663 );
3664 };
3665
3666 let tail = self.selections.newest::<Point>(cx).tail();
3667 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3668 self.columnar_selection_state = match mode {
3669 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3670 selection_tail: selection_anchor,
3671 display_point: if reset {
3672 if position.column() != goal_column {
3673 Some(DisplayPoint::new(position.row(), goal_column))
3674 } else {
3675 None
3676 }
3677 } else {
3678 None
3679 },
3680 }),
3681 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3682 selection_tail: selection_anchor,
3683 }),
3684 };
3685
3686 if !reset {
3687 self.select_columns(position, goal_column, &display_map, window, cx);
3688 }
3689 }
3690
3691 fn update_selection(
3692 &mut self,
3693 position: DisplayPoint,
3694 goal_column: u32,
3695 scroll_delta: gpui::Point<f32>,
3696 window: &mut Window,
3697 cx: &mut Context<Self>,
3698 ) {
3699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3700
3701 if self.columnar_selection_state.is_some() {
3702 self.select_columns(position, goal_column, &display_map, window, cx);
3703 } else if let Some(mut pending) = self.selections.pending_anchor() {
3704 let buffer = &display_map.buffer_snapshot;
3705 let head;
3706 let tail;
3707 let mode = self.selections.pending_mode().unwrap();
3708 match &mode {
3709 SelectMode::Character => {
3710 head = position.to_point(&display_map);
3711 tail = pending.tail().to_point(buffer);
3712 }
3713 SelectMode::Word(original_range) => {
3714 let offset = display_map
3715 .clip_point(position, Bias::Left)
3716 .to_offset(&display_map, Bias::Left);
3717 let original_range = original_range.to_offset(buffer);
3718
3719 let head_offset = if buffer.is_inside_word(offset, false)
3720 || original_range.contains(&offset)
3721 {
3722 let (word_range, _) = buffer.surrounding_word(offset, false);
3723 if word_range.start < original_range.start {
3724 word_range.start
3725 } else {
3726 word_range.end
3727 }
3728 } else {
3729 offset
3730 };
3731
3732 head = head_offset.to_point(buffer);
3733 if head_offset <= original_range.start {
3734 tail = original_range.end.to_point(buffer);
3735 } else {
3736 tail = original_range.start.to_point(buffer);
3737 }
3738 }
3739 SelectMode::Line(original_range) => {
3740 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3741
3742 let position = display_map
3743 .clip_point(position, Bias::Left)
3744 .to_point(&display_map);
3745 let line_start = display_map.prev_line_boundary(position).0;
3746 let next_line_start = buffer.clip_point(
3747 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3748 Bias::Left,
3749 );
3750
3751 if line_start < original_range.start {
3752 head = line_start
3753 } else {
3754 head = next_line_start
3755 }
3756
3757 if head <= original_range.start {
3758 tail = original_range.end;
3759 } else {
3760 tail = original_range.start;
3761 }
3762 }
3763 SelectMode::All => {
3764 return;
3765 }
3766 };
3767
3768 if head < tail {
3769 pending.start = buffer.anchor_before(head);
3770 pending.end = buffer.anchor_before(tail);
3771 pending.reversed = true;
3772 } else {
3773 pending.start = buffer.anchor_before(tail);
3774 pending.end = buffer.anchor_before(head);
3775 pending.reversed = false;
3776 }
3777
3778 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3779 s.set_pending(pending, mode);
3780 });
3781 } else {
3782 log::error!("update_selection dispatched with no pending selection");
3783 return;
3784 }
3785
3786 self.apply_scroll_delta(scroll_delta, window, cx);
3787 cx.notify();
3788 }
3789
3790 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3791 self.columnar_selection_state.take();
3792 if self.selections.pending_anchor().is_some() {
3793 let selections = self.selections.all::<usize>(cx);
3794 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3795 s.select(selections);
3796 s.clear_pending();
3797 });
3798 }
3799 }
3800
3801 fn select_columns(
3802 &mut self,
3803 head: DisplayPoint,
3804 goal_column: u32,
3805 display_map: &DisplaySnapshot,
3806 window: &mut Window,
3807 cx: &mut Context<Self>,
3808 ) {
3809 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3810 return;
3811 };
3812
3813 let tail = match columnar_state {
3814 ColumnarSelectionState::FromMouse {
3815 selection_tail,
3816 display_point,
3817 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3818 ColumnarSelectionState::FromSelection { selection_tail } => {
3819 selection_tail.to_display_point(display_map)
3820 }
3821 };
3822
3823 let start_row = cmp::min(tail.row(), head.row());
3824 let end_row = cmp::max(tail.row(), head.row());
3825 let start_column = cmp::min(tail.column(), goal_column);
3826 let end_column = cmp::max(tail.column(), goal_column);
3827 let reversed = start_column < tail.column();
3828
3829 let selection_ranges = (start_row.0..=end_row.0)
3830 .map(DisplayRow)
3831 .filter_map(|row| {
3832 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3833 || start_column <= display_map.line_len(row))
3834 && !display_map.is_block_line(row)
3835 {
3836 let start = display_map
3837 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3838 .to_point(display_map);
3839 let end = display_map
3840 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3841 .to_point(display_map);
3842 if reversed {
3843 Some(end..start)
3844 } else {
3845 Some(start..end)
3846 }
3847 } else {
3848 None
3849 }
3850 })
3851 .collect::<Vec<_>>();
3852
3853 let ranges = match columnar_state {
3854 ColumnarSelectionState::FromMouse { .. } => {
3855 let mut non_empty_ranges = selection_ranges
3856 .iter()
3857 .filter(|selection_range| selection_range.start != selection_range.end)
3858 .peekable();
3859 if non_empty_ranges.peek().is_some() {
3860 non_empty_ranges.cloned().collect()
3861 } else {
3862 selection_ranges
3863 }
3864 }
3865 _ => selection_ranges,
3866 };
3867
3868 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3869 s.select_ranges(ranges);
3870 });
3871 cx.notify();
3872 }
3873
3874 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3875 self.selections
3876 .all_adjusted(cx)
3877 .iter()
3878 .any(|selection| !selection.is_empty())
3879 }
3880
3881 pub fn has_pending_nonempty_selection(&self) -> bool {
3882 let pending_nonempty_selection = match self.selections.pending_anchor() {
3883 Some(Selection { start, end, .. }) => start != end,
3884 None => false,
3885 };
3886
3887 pending_nonempty_selection
3888 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3889 }
3890
3891 pub fn has_pending_selection(&self) -> bool {
3892 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3893 }
3894
3895 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3896 self.selection_mark_mode = false;
3897 self.selection_drag_state = SelectionDragState::None;
3898
3899 if self.clear_expanded_diff_hunks(cx) {
3900 cx.notify();
3901 return;
3902 }
3903 if self.dismiss_menus_and_popups(true, window, cx) {
3904 return;
3905 }
3906
3907 if self.mode.is_full()
3908 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3909 {
3910 return;
3911 }
3912
3913 cx.propagate();
3914 }
3915
3916 pub fn dismiss_menus_and_popups(
3917 &mut self,
3918 is_user_requested: bool,
3919 window: &mut Window,
3920 cx: &mut Context<Self>,
3921 ) -> bool {
3922 if self.take_rename(false, window, cx).is_some() {
3923 return true;
3924 }
3925
3926 if hide_hover(self, cx) {
3927 return true;
3928 }
3929
3930 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3931 return true;
3932 }
3933
3934 if self.hide_context_menu(window, cx).is_some() {
3935 return true;
3936 }
3937
3938 if self.mouse_context_menu.take().is_some() {
3939 return true;
3940 }
3941
3942 if is_user_requested && self.discard_edit_prediction(true, cx) {
3943 return true;
3944 }
3945
3946 if self.snippet_stack.pop().is_some() {
3947 return true;
3948 }
3949
3950 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3951 self.dismiss_diagnostics(cx);
3952 return true;
3953 }
3954
3955 false
3956 }
3957
3958 fn linked_editing_ranges_for(
3959 &self,
3960 selection: Range<text::Anchor>,
3961 cx: &App,
3962 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3963 if self.linked_edit_ranges.is_empty() {
3964 return None;
3965 }
3966 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3967 selection.end.buffer_id.and_then(|end_buffer_id| {
3968 if selection.start.buffer_id != Some(end_buffer_id) {
3969 return None;
3970 }
3971 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3972 let snapshot = buffer.read(cx).snapshot();
3973 self.linked_edit_ranges
3974 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3975 .map(|ranges| (ranges, snapshot, buffer))
3976 })?;
3977 use text::ToOffset as TO;
3978 // find offset from the start of current range to current cursor position
3979 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3980
3981 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3982 let start_difference = start_offset - start_byte_offset;
3983 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3984 let end_difference = end_offset - start_byte_offset;
3985 // Current range has associated linked ranges.
3986 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3987 for range in linked_ranges.iter() {
3988 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3989 let end_offset = start_offset + end_difference;
3990 let start_offset = start_offset + start_difference;
3991 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3992 continue;
3993 }
3994 if self.selections.disjoint_anchor_ranges().any(|s| {
3995 if s.start.buffer_id != selection.start.buffer_id
3996 || s.end.buffer_id != selection.end.buffer_id
3997 {
3998 return false;
3999 }
4000 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4001 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4002 }) {
4003 continue;
4004 }
4005 let start = buffer_snapshot.anchor_after(start_offset);
4006 let end = buffer_snapshot.anchor_after(end_offset);
4007 linked_edits
4008 .entry(buffer.clone())
4009 .or_default()
4010 .push(start..end);
4011 }
4012 Some(linked_edits)
4013 }
4014
4015 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4016 let text: Arc<str> = text.into();
4017
4018 if self.read_only(cx) {
4019 return;
4020 }
4021
4022 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4023
4024 let selections = self.selections.all_adjusted(cx);
4025 let mut bracket_inserted = false;
4026 let mut edits = Vec::new();
4027 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4028 let mut new_selections = Vec::with_capacity(selections.len());
4029 let mut new_autoclose_regions = Vec::new();
4030 let snapshot = self.buffer.read(cx).read(cx);
4031 let mut clear_linked_edit_ranges = false;
4032
4033 for (selection, autoclose_region) in
4034 self.selections_with_autoclose_regions(selections, &snapshot)
4035 {
4036 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4037 // Determine if the inserted text matches the opening or closing
4038 // bracket of any of this language's bracket pairs.
4039 let mut bracket_pair = None;
4040 let mut is_bracket_pair_start = false;
4041 let mut is_bracket_pair_end = false;
4042 if !text.is_empty() {
4043 let mut bracket_pair_matching_end = None;
4044 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4045 // and they are removing the character that triggered IME popup.
4046 for (pair, enabled) in scope.brackets() {
4047 if !pair.close && !pair.surround {
4048 continue;
4049 }
4050
4051 if enabled && pair.start.ends_with(text.as_ref()) {
4052 let prefix_len = pair.start.len() - text.len();
4053 let preceding_text_matches_prefix = prefix_len == 0
4054 || (selection.start.column >= (prefix_len as u32)
4055 && snapshot.contains_str_at(
4056 Point::new(
4057 selection.start.row,
4058 selection.start.column - (prefix_len as u32),
4059 ),
4060 &pair.start[..prefix_len],
4061 ));
4062 if preceding_text_matches_prefix {
4063 bracket_pair = Some(pair.clone());
4064 is_bracket_pair_start = true;
4065 break;
4066 }
4067 }
4068 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4069 {
4070 // take first bracket pair matching end, but don't break in case a later bracket
4071 // pair matches start
4072 bracket_pair_matching_end = Some(pair.clone());
4073 }
4074 }
4075 if let Some(end) = bracket_pair_matching_end
4076 && bracket_pair.is_none()
4077 {
4078 bracket_pair = Some(end);
4079 is_bracket_pair_end = true;
4080 }
4081 }
4082
4083 if let Some(bracket_pair) = bracket_pair {
4084 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4085 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4086 let auto_surround =
4087 self.use_auto_surround && snapshot_settings.use_auto_surround;
4088 if selection.is_empty() {
4089 if is_bracket_pair_start {
4090 // If the inserted text is a suffix of an opening bracket and the
4091 // selection is preceded by the rest of the opening bracket, then
4092 // insert the closing bracket.
4093 let following_text_allows_autoclose = snapshot
4094 .chars_at(selection.start)
4095 .next()
4096 .is_none_or(|c| scope.should_autoclose_before(c));
4097
4098 let preceding_text_allows_autoclose = selection.start.column == 0
4099 || snapshot
4100 .reversed_chars_at(selection.start)
4101 .next()
4102 .is_none_or(|c| {
4103 bracket_pair.start != bracket_pair.end
4104 || !snapshot
4105 .char_classifier_at(selection.start)
4106 .is_word(c)
4107 });
4108
4109 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4110 && bracket_pair.start.len() == 1
4111 {
4112 let target = bracket_pair.start.chars().next().unwrap();
4113 let current_line_count = snapshot
4114 .reversed_chars_at(selection.start)
4115 .take_while(|&c| c != '\n')
4116 .filter(|&c| c == target)
4117 .count();
4118 current_line_count % 2 == 1
4119 } else {
4120 false
4121 };
4122
4123 if autoclose
4124 && bracket_pair.close
4125 && following_text_allows_autoclose
4126 && preceding_text_allows_autoclose
4127 && !is_closing_quote
4128 {
4129 let anchor = snapshot.anchor_before(selection.end);
4130 new_selections.push((selection.map(|_| anchor), text.len()));
4131 new_autoclose_regions.push((
4132 anchor,
4133 text.len(),
4134 selection.id,
4135 bracket_pair.clone(),
4136 ));
4137 edits.push((
4138 selection.range(),
4139 format!("{}{}", text, bracket_pair.end).into(),
4140 ));
4141 bracket_inserted = true;
4142 continue;
4143 }
4144 }
4145
4146 if let Some(region) = autoclose_region {
4147 // If the selection is followed by an auto-inserted closing bracket,
4148 // then don't insert that closing bracket again; just move the selection
4149 // past the closing bracket.
4150 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4151 && text.as_ref() == region.pair.end.as_str()
4152 && snapshot.contains_str_at(region.range.end, text.as_ref());
4153 if should_skip {
4154 let anchor = snapshot.anchor_after(selection.end);
4155 new_selections
4156 .push((selection.map(|_| anchor), region.pair.end.len()));
4157 continue;
4158 }
4159 }
4160
4161 let always_treat_brackets_as_autoclosed = snapshot
4162 .language_settings_at(selection.start, cx)
4163 .always_treat_brackets_as_autoclosed;
4164 if always_treat_brackets_as_autoclosed
4165 && is_bracket_pair_end
4166 && snapshot.contains_str_at(selection.end, text.as_ref())
4167 {
4168 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4169 // and the inserted text is a closing bracket and the selection is followed
4170 // by the closing bracket then move the selection past the closing bracket.
4171 let anchor = snapshot.anchor_after(selection.end);
4172 new_selections.push((selection.map(|_| anchor), text.len()));
4173 continue;
4174 }
4175 }
4176 // If an opening bracket is 1 character long and is typed while
4177 // text is selected, then surround that text with the bracket pair.
4178 else if auto_surround
4179 && bracket_pair.surround
4180 && is_bracket_pair_start
4181 && bracket_pair.start.chars().count() == 1
4182 {
4183 edits.push((selection.start..selection.start, text.clone()));
4184 edits.push((
4185 selection.end..selection.end,
4186 bracket_pair.end.as_str().into(),
4187 ));
4188 bracket_inserted = true;
4189 new_selections.push((
4190 Selection {
4191 id: selection.id,
4192 start: snapshot.anchor_after(selection.start),
4193 end: snapshot.anchor_before(selection.end),
4194 reversed: selection.reversed,
4195 goal: selection.goal,
4196 },
4197 0,
4198 ));
4199 continue;
4200 }
4201 }
4202 }
4203
4204 if self.auto_replace_emoji_shortcode
4205 && selection.is_empty()
4206 && text.as_ref().ends_with(':')
4207 && let Some(possible_emoji_short_code) =
4208 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4209 && !possible_emoji_short_code.is_empty()
4210 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4211 {
4212 let emoji_shortcode_start = Point::new(
4213 selection.start.row,
4214 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4215 );
4216
4217 // Remove shortcode from buffer
4218 edits.push((
4219 emoji_shortcode_start..selection.start,
4220 "".to_string().into(),
4221 ));
4222 new_selections.push((
4223 Selection {
4224 id: selection.id,
4225 start: snapshot.anchor_after(emoji_shortcode_start),
4226 end: snapshot.anchor_before(selection.start),
4227 reversed: selection.reversed,
4228 goal: selection.goal,
4229 },
4230 0,
4231 ));
4232
4233 // Insert emoji
4234 let selection_start_anchor = snapshot.anchor_after(selection.start);
4235 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4236 edits.push((selection.start..selection.end, emoji.to_string().into()));
4237
4238 continue;
4239 }
4240
4241 // If not handling any auto-close operation, then just replace the selected
4242 // text with the given input and move the selection to the end of the
4243 // newly inserted text.
4244 let anchor = snapshot.anchor_after(selection.end);
4245 if !self.linked_edit_ranges.is_empty() {
4246 let start_anchor = snapshot.anchor_before(selection.start);
4247
4248 let is_word_char = text.chars().next().is_none_or(|char| {
4249 let classifier = snapshot
4250 .char_classifier_at(start_anchor.to_offset(&snapshot))
4251 .ignore_punctuation(true);
4252 classifier.is_word(char)
4253 });
4254
4255 if is_word_char {
4256 if let Some(ranges) = self
4257 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4258 {
4259 for (buffer, edits) in ranges {
4260 linked_edits
4261 .entry(buffer.clone())
4262 .or_default()
4263 .extend(edits.into_iter().map(|range| (range, text.clone())));
4264 }
4265 }
4266 } else {
4267 clear_linked_edit_ranges = true;
4268 }
4269 }
4270
4271 new_selections.push((selection.map(|_| anchor), 0));
4272 edits.push((selection.start..selection.end, text.clone()));
4273 }
4274
4275 drop(snapshot);
4276
4277 self.transact(window, cx, |this, window, cx| {
4278 if clear_linked_edit_ranges {
4279 this.linked_edit_ranges.clear();
4280 }
4281 let initial_buffer_versions =
4282 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4283
4284 this.buffer.update(cx, |buffer, cx| {
4285 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4286 });
4287 for (buffer, edits) in linked_edits {
4288 buffer.update(cx, |buffer, cx| {
4289 let snapshot = buffer.snapshot();
4290 let edits = edits
4291 .into_iter()
4292 .map(|(range, text)| {
4293 use text::ToPoint as TP;
4294 let end_point = TP::to_point(&range.end, &snapshot);
4295 let start_point = TP::to_point(&range.start, &snapshot);
4296 (start_point..end_point, text)
4297 })
4298 .sorted_by_key(|(range, _)| range.start);
4299 buffer.edit(edits, None, cx);
4300 })
4301 }
4302 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4303 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4304 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4305 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4306 .zip(new_selection_deltas)
4307 .map(|(selection, delta)| Selection {
4308 id: selection.id,
4309 start: selection.start + delta,
4310 end: selection.end + delta,
4311 reversed: selection.reversed,
4312 goal: SelectionGoal::None,
4313 })
4314 .collect::<Vec<_>>();
4315
4316 let mut i = 0;
4317 for (position, delta, selection_id, pair) in new_autoclose_regions {
4318 let position = position.to_offset(&map.buffer_snapshot) + delta;
4319 let start = map.buffer_snapshot.anchor_before(position);
4320 let end = map.buffer_snapshot.anchor_after(position);
4321 while let Some(existing_state) = this.autoclose_regions.get(i) {
4322 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4323 Ordering::Less => i += 1,
4324 Ordering::Greater => break,
4325 Ordering::Equal => {
4326 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4327 Ordering::Less => i += 1,
4328 Ordering::Equal => break,
4329 Ordering::Greater => break,
4330 }
4331 }
4332 }
4333 }
4334 this.autoclose_regions.insert(
4335 i,
4336 AutocloseRegion {
4337 selection_id,
4338 range: start..end,
4339 pair,
4340 },
4341 );
4342 }
4343
4344 let had_active_edit_prediction = this.has_active_edit_prediction();
4345 this.change_selections(
4346 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4347 window,
4348 cx,
4349 |s| s.select(new_selections),
4350 );
4351
4352 if !bracket_inserted
4353 && let Some(on_type_format_task) =
4354 this.trigger_on_type_formatting(text.to_string(), window, cx)
4355 {
4356 on_type_format_task.detach_and_log_err(cx);
4357 }
4358
4359 let editor_settings = EditorSettings::get_global(cx);
4360 if bracket_inserted
4361 && (editor_settings.auto_signature_help
4362 || editor_settings.show_signature_help_after_edits)
4363 {
4364 this.show_signature_help(&ShowSignatureHelp, window, cx);
4365 }
4366
4367 let trigger_in_words =
4368 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4369 if this.hard_wrap.is_some() {
4370 let latest: Range<Point> = this.selections.newest(cx).range();
4371 if latest.is_empty()
4372 && this
4373 .buffer()
4374 .read(cx)
4375 .snapshot(cx)
4376 .line_len(MultiBufferRow(latest.start.row))
4377 == latest.start.column
4378 {
4379 this.rewrap_impl(
4380 RewrapOptions {
4381 override_language_settings: true,
4382 preserve_existing_whitespace: true,
4383 },
4384 cx,
4385 )
4386 }
4387 }
4388 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4389 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4390 this.refresh_edit_prediction(true, false, window, cx);
4391 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4392 });
4393 }
4394
4395 fn find_possible_emoji_shortcode_at_position(
4396 snapshot: &MultiBufferSnapshot,
4397 position: Point,
4398 ) -> Option<String> {
4399 let mut chars = Vec::new();
4400 let mut found_colon = false;
4401 for char in snapshot.reversed_chars_at(position).take(100) {
4402 // Found a possible emoji shortcode in the middle of the buffer
4403 if found_colon {
4404 if char.is_whitespace() {
4405 chars.reverse();
4406 return Some(chars.iter().collect());
4407 }
4408 // If the previous character is not a whitespace, we are in the middle of a word
4409 // and we only want to complete the shortcode if the word is made up of other emojis
4410 let mut containing_word = String::new();
4411 for ch in snapshot
4412 .reversed_chars_at(position)
4413 .skip(chars.len() + 1)
4414 .take(100)
4415 {
4416 if ch.is_whitespace() {
4417 break;
4418 }
4419 containing_word.push(ch);
4420 }
4421 let containing_word = containing_word.chars().rev().collect::<String>();
4422 if util::word_consists_of_emojis(containing_word.as_str()) {
4423 chars.reverse();
4424 return Some(chars.iter().collect());
4425 }
4426 }
4427
4428 if char.is_whitespace() || !char.is_ascii() {
4429 return None;
4430 }
4431 if char == ':' {
4432 found_colon = true;
4433 } else {
4434 chars.push(char);
4435 }
4436 }
4437 // Found a possible emoji shortcode at the beginning of the buffer
4438 chars.reverse();
4439 Some(chars.iter().collect())
4440 }
4441
4442 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4443 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4444 self.transact(window, cx, |this, window, cx| {
4445 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4446 let selections = this.selections.all::<usize>(cx);
4447 let multi_buffer = this.buffer.read(cx);
4448 let buffer = multi_buffer.snapshot(cx);
4449 selections
4450 .iter()
4451 .map(|selection| {
4452 let start_point = selection.start.to_point(&buffer);
4453 let mut existing_indent =
4454 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4455 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4456 let start = selection.start;
4457 let end = selection.end;
4458 let selection_is_empty = start == end;
4459 let language_scope = buffer.language_scope_at(start);
4460 let (
4461 comment_delimiter,
4462 doc_delimiter,
4463 insert_extra_newline,
4464 indent_on_newline,
4465 indent_on_extra_newline,
4466 ) = if let Some(language) = &language_scope {
4467 let mut insert_extra_newline =
4468 insert_extra_newline_brackets(&buffer, start..end, language)
4469 || insert_extra_newline_tree_sitter(&buffer, start..end);
4470
4471 // Comment extension on newline is allowed only for cursor selections
4472 let comment_delimiter = maybe!({
4473 if !selection_is_empty {
4474 return None;
4475 }
4476
4477 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4478 return None;
4479 }
4480
4481 let delimiters = language.line_comment_prefixes();
4482 let max_len_of_delimiter =
4483 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4484 let (snapshot, range) =
4485 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4486
4487 let num_of_whitespaces = snapshot
4488 .chars_for_range(range.clone())
4489 .take_while(|c| c.is_whitespace())
4490 .count();
4491 let comment_candidate = snapshot
4492 .chars_for_range(range.clone())
4493 .skip(num_of_whitespaces)
4494 .take(max_len_of_delimiter)
4495 .collect::<String>();
4496 let (delimiter, trimmed_len) = delimiters
4497 .iter()
4498 .filter_map(|delimiter| {
4499 let prefix = delimiter.trim_end();
4500 if comment_candidate.starts_with(prefix) {
4501 Some((delimiter, prefix.len()))
4502 } else {
4503 None
4504 }
4505 })
4506 .max_by_key(|(_, len)| *len)?;
4507
4508 if let Some(BlockCommentConfig {
4509 start: block_start, ..
4510 }) = language.block_comment()
4511 {
4512 let block_start_trimmed = block_start.trim_end();
4513 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4514 let line_content = snapshot
4515 .chars_for_range(range)
4516 .skip(num_of_whitespaces)
4517 .take(block_start_trimmed.len())
4518 .collect::<String>();
4519
4520 if line_content.starts_with(block_start_trimmed) {
4521 return None;
4522 }
4523 }
4524 }
4525
4526 let cursor_is_placed_after_comment_marker =
4527 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4528 if cursor_is_placed_after_comment_marker {
4529 Some(delimiter.clone())
4530 } else {
4531 None
4532 }
4533 });
4534
4535 let mut indent_on_newline = IndentSize::spaces(0);
4536 let mut indent_on_extra_newline = IndentSize::spaces(0);
4537
4538 let doc_delimiter = maybe!({
4539 if !selection_is_empty {
4540 return None;
4541 }
4542
4543 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4544 return None;
4545 }
4546
4547 let BlockCommentConfig {
4548 start: start_tag,
4549 end: end_tag,
4550 prefix: delimiter,
4551 tab_size: len,
4552 } = language.documentation_comment()?;
4553 let is_within_block_comment = buffer
4554 .language_scope_at(start_point)
4555 .is_some_and(|scope| scope.override_name() == Some("comment"));
4556 if !is_within_block_comment {
4557 return None;
4558 }
4559
4560 let (snapshot, range) =
4561 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4562
4563 let num_of_whitespaces = snapshot
4564 .chars_for_range(range.clone())
4565 .take_while(|c| c.is_whitespace())
4566 .count();
4567
4568 // 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.
4569 let column = start_point.column;
4570 let cursor_is_after_start_tag = {
4571 let start_tag_len = start_tag.len();
4572 let start_tag_line = snapshot
4573 .chars_for_range(range.clone())
4574 .skip(num_of_whitespaces)
4575 .take(start_tag_len)
4576 .collect::<String>();
4577 if start_tag_line.starts_with(start_tag.as_ref()) {
4578 num_of_whitespaces + start_tag_len <= column as usize
4579 } else {
4580 false
4581 }
4582 };
4583
4584 let cursor_is_after_delimiter = {
4585 let delimiter_trim = delimiter.trim_end();
4586 let delimiter_line = snapshot
4587 .chars_for_range(range.clone())
4588 .skip(num_of_whitespaces)
4589 .take(delimiter_trim.len())
4590 .collect::<String>();
4591 if delimiter_line.starts_with(delimiter_trim) {
4592 num_of_whitespaces + delimiter_trim.len() <= column as usize
4593 } else {
4594 false
4595 }
4596 };
4597
4598 let cursor_is_before_end_tag_if_exists = {
4599 let mut char_position = 0u32;
4600 let mut end_tag_offset = None;
4601
4602 'outer: for chunk in snapshot.text_for_range(range) {
4603 if let Some(byte_pos) = chunk.find(&**end_tag) {
4604 let chars_before_match =
4605 chunk[..byte_pos].chars().count() as u32;
4606 end_tag_offset =
4607 Some(char_position + chars_before_match);
4608 break 'outer;
4609 }
4610 char_position += chunk.chars().count() as u32;
4611 }
4612
4613 if let Some(end_tag_offset) = end_tag_offset {
4614 let cursor_is_before_end_tag = column <= end_tag_offset;
4615 if cursor_is_after_start_tag {
4616 if cursor_is_before_end_tag {
4617 insert_extra_newline = true;
4618 }
4619 let cursor_is_at_start_of_end_tag =
4620 column == end_tag_offset;
4621 if cursor_is_at_start_of_end_tag {
4622 indent_on_extra_newline.len = *len;
4623 }
4624 }
4625 cursor_is_before_end_tag
4626 } else {
4627 true
4628 }
4629 };
4630
4631 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4632 && cursor_is_before_end_tag_if_exists
4633 {
4634 if cursor_is_after_start_tag {
4635 indent_on_newline.len = *len;
4636 }
4637 Some(delimiter.clone())
4638 } else {
4639 None
4640 }
4641 });
4642
4643 (
4644 comment_delimiter,
4645 doc_delimiter,
4646 insert_extra_newline,
4647 indent_on_newline,
4648 indent_on_extra_newline,
4649 )
4650 } else {
4651 (
4652 None,
4653 None,
4654 false,
4655 IndentSize::default(),
4656 IndentSize::default(),
4657 )
4658 };
4659
4660 let prevent_auto_indent = doc_delimiter.is_some();
4661 let delimiter = comment_delimiter.or(doc_delimiter);
4662
4663 let capacity_for_delimiter =
4664 delimiter.as_deref().map(str::len).unwrap_or_default();
4665 let mut new_text = String::with_capacity(
4666 1 + capacity_for_delimiter
4667 + existing_indent.len as usize
4668 + indent_on_newline.len as usize
4669 + indent_on_extra_newline.len as usize,
4670 );
4671 new_text.push('\n');
4672 new_text.extend(existing_indent.chars());
4673 new_text.extend(indent_on_newline.chars());
4674
4675 if let Some(delimiter) = &delimiter {
4676 new_text.push_str(delimiter);
4677 }
4678
4679 if insert_extra_newline {
4680 new_text.push('\n');
4681 new_text.extend(existing_indent.chars());
4682 new_text.extend(indent_on_extra_newline.chars());
4683 }
4684
4685 let anchor = buffer.anchor_after(end);
4686 let new_selection = selection.map(|_| anchor);
4687 (
4688 ((start..end, new_text), prevent_auto_indent),
4689 (insert_extra_newline, new_selection),
4690 )
4691 })
4692 .unzip()
4693 };
4694
4695 let mut auto_indent_edits = Vec::new();
4696 let mut edits = Vec::new();
4697 for (edit, prevent_auto_indent) in edits_with_flags {
4698 if prevent_auto_indent {
4699 edits.push(edit);
4700 } else {
4701 auto_indent_edits.push(edit);
4702 }
4703 }
4704 if !edits.is_empty() {
4705 this.edit(edits, cx);
4706 }
4707 if !auto_indent_edits.is_empty() {
4708 this.edit_with_autoindent(auto_indent_edits, cx);
4709 }
4710
4711 let buffer = this.buffer.read(cx).snapshot(cx);
4712 let new_selections = selection_info
4713 .into_iter()
4714 .map(|(extra_newline_inserted, new_selection)| {
4715 let mut cursor = new_selection.end.to_point(&buffer);
4716 if extra_newline_inserted {
4717 cursor.row -= 1;
4718 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4719 }
4720 new_selection.map(|_| cursor)
4721 })
4722 .collect();
4723
4724 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4725 this.refresh_edit_prediction(true, false, window, cx);
4726 });
4727 }
4728
4729 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4731
4732 let buffer = self.buffer.read(cx);
4733 let snapshot = buffer.snapshot(cx);
4734
4735 let mut edits = Vec::new();
4736 let mut rows = Vec::new();
4737
4738 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4739 let cursor = selection.head();
4740 let row = cursor.row;
4741
4742 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4743
4744 let newline = "\n".to_string();
4745 edits.push((start_of_line..start_of_line, newline));
4746
4747 rows.push(row + rows_inserted as u32);
4748 }
4749
4750 self.transact(window, cx, |editor, window, cx| {
4751 editor.edit(edits, cx);
4752
4753 editor.change_selections(Default::default(), window, cx, |s| {
4754 let mut index = 0;
4755 s.move_cursors_with(|map, _, _| {
4756 let row = rows[index];
4757 index += 1;
4758
4759 let point = Point::new(row, 0);
4760 let boundary = map.next_line_boundary(point).1;
4761 let clipped = map.clip_point(boundary, Bias::Left);
4762
4763 (clipped, SelectionGoal::None)
4764 });
4765 });
4766
4767 let mut indent_edits = Vec::new();
4768 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4769 for row in rows {
4770 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4771 for (row, indent) in indents {
4772 if indent.len == 0 {
4773 continue;
4774 }
4775
4776 let text = match indent.kind {
4777 IndentKind::Space => " ".repeat(indent.len as usize),
4778 IndentKind::Tab => "\t".repeat(indent.len as usize),
4779 };
4780 let point = Point::new(row.0, 0);
4781 indent_edits.push((point..point, text));
4782 }
4783 }
4784 editor.edit(indent_edits, cx);
4785 });
4786 }
4787
4788 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4789 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4790
4791 let buffer = self.buffer.read(cx);
4792 let snapshot = buffer.snapshot(cx);
4793
4794 let mut edits = Vec::new();
4795 let mut rows = Vec::new();
4796 let mut rows_inserted = 0;
4797
4798 for selection in self.selections.all_adjusted(cx) {
4799 let cursor = selection.head();
4800 let row = cursor.row;
4801
4802 let point = Point::new(row + 1, 0);
4803 let start_of_line = snapshot.clip_point(point, Bias::Left);
4804
4805 let newline = "\n".to_string();
4806 edits.push((start_of_line..start_of_line, newline));
4807
4808 rows_inserted += 1;
4809 rows.push(row + rows_inserted);
4810 }
4811
4812 self.transact(window, cx, |editor, window, cx| {
4813 editor.edit(edits, cx);
4814
4815 editor.change_selections(Default::default(), window, cx, |s| {
4816 let mut index = 0;
4817 s.move_cursors_with(|map, _, _| {
4818 let row = rows[index];
4819 index += 1;
4820
4821 let point = Point::new(row, 0);
4822 let boundary = map.next_line_boundary(point).1;
4823 let clipped = map.clip_point(boundary, Bias::Left);
4824
4825 (clipped, SelectionGoal::None)
4826 });
4827 });
4828
4829 let mut indent_edits = Vec::new();
4830 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4831 for row in rows {
4832 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4833 for (row, indent) in indents {
4834 if indent.len == 0 {
4835 continue;
4836 }
4837
4838 let text = match indent.kind {
4839 IndentKind::Space => " ".repeat(indent.len as usize),
4840 IndentKind::Tab => "\t".repeat(indent.len as usize),
4841 };
4842 let point = Point::new(row.0, 0);
4843 indent_edits.push((point..point, text));
4844 }
4845 }
4846 editor.edit(indent_edits, cx);
4847 });
4848 }
4849
4850 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4851 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4852 original_indent_columns: Vec::new(),
4853 });
4854 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4855 }
4856
4857 fn insert_with_autoindent_mode(
4858 &mut self,
4859 text: &str,
4860 autoindent_mode: Option<AutoindentMode>,
4861 window: &mut Window,
4862 cx: &mut Context<Self>,
4863 ) {
4864 if self.read_only(cx) {
4865 return;
4866 }
4867
4868 let text: Arc<str> = text.into();
4869 self.transact(window, cx, |this, window, cx| {
4870 let old_selections = this.selections.all_adjusted(cx);
4871 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4872 let anchors = {
4873 let snapshot = buffer.read(cx);
4874 old_selections
4875 .iter()
4876 .map(|s| {
4877 let anchor = snapshot.anchor_after(s.head());
4878 s.map(|_| anchor)
4879 })
4880 .collect::<Vec<_>>()
4881 };
4882 buffer.edit(
4883 old_selections
4884 .iter()
4885 .map(|s| (s.start..s.end, text.clone())),
4886 autoindent_mode,
4887 cx,
4888 );
4889 anchors
4890 });
4891
4892 this.change_selections(Default::default(), window, cx, |s| {
4893 s.select_anchors(selection_anchors);
4894 });
4895
4896 cx.notify();
4897 });
4898 }
4899
4900 fn trigger_completion_on_input(
4901 &mut self,
4902 text: &str,
4903 trigger_in_words: bool,
4904 window: &mut Window,
4905 cx: &mut Context<Self>,
4906 ) {
4907 let completions_source = self
4908 .context_menu
4909 .borrow()
4910 .as_ref()
4911 .and_then(|menu| match menu {
4912 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4913 CodeContextMenu::CodeActions(_) => None,
4914 });
4915
4916 match completions_source {
4917 Some(CompletionsMenuSource::Words { .. }) => {
4918 self.open_or_update_completions_menu(
4919 Some(CompletionsMenuSource::Words {
4920 ignore_threshold: false,
4921 }),
4922 None,
4923 window,
4924 cx,
4925 );
4926 }
4927 Some(CompletionsMenuSource::Normal)
4928 | Some(CompletionsMenuSource::SnippetChoices)
4929 | None
4930 if self.is_completion_trigger(
4931 text,
4932 trigger_in_words,
4933 completions_source.is_some(),
4934 cx,
4935 ) =>
4936 {
4937 self.show_completions(
4938 &ShowCompletions {
4939 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4940 },
4941 window,
4942 cx,
4943 )
4944 }
4945 _ => {
4946 self.hide_context_menu(window, cx);
4947 }
4948 }
4949 }
4950
4951 fn is_completion_trigger(
4952 &self,
4953 text: &str,
4954 trigger_in_words: bool,
4955 menu_is_open: bool,
4956 cx: &mut Context<Self>,
4957 ) -> bool {
4958 let position = self.selections.newest_anchor().head();
4959 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4960 return false;
4961 };
4962
4963 if let Some(completion_provider) = &self.completion_provider {
4964 completion_provider.is_completion_trigger(
4965 &buffer,
4966 position.text_anchor,
4967 text,
4968 trigger_in_words,
4969 menu_is_open,
4970 cx,
4971 )
4972 } else {
4973 false
4974 }
4975 }
4976
4977 /// If any empty selections is touching the start of its innermost containing autoclose
4978 /// region, expand it to select the brackets.
4979 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4980 let selections = self.selections.all::<usize>(cx);
4981 let buffer = self.buffer.read(cx).read(cx);
4982 let new_selections = self
4983 .selections_with_autoclose_regions(selections, &buffer)
4984 .map(|(mut selection, region)| {
4985 if !selection.is_empty() {
4986 return selection;
4987 }
4988
4989 if let Some(region) = region {
4990 let mut range = region.range.to_offset(&buffer);
4991 if selection.start == range.start && range.start >= region.pair.start.len() {
4992 range.start -= region.pair.start.len();
4993 if buffer.contains_str_at(range.start, ®ion.pair.start)
4994 && buffer.contains_str_at(range.end, ®ion.pair.end)
4995 {
4996 range.end += region.pair.end.len();
4997 selection.start = range.start;
4998 selection.end = range.end;
4999
5000 return selection;
5001 }
5002 }
5003 }
5004
5005 let always_treat_brackets_as_autoclosed = buffer
5006 .language_settings_at(selection.start, cx)
5007 .always_treat_brackets_as_autoclosed;
5008
5009 if !always_treat_brackets_as_autoclosed {
5010 return selection;
5011 }
5012
5013 if let Some(scope) = buffer.language_scope_at(selection.start) {
5014 for (pair, enabled) in scope.brackets() {
5015 if !enabled || !pair.close {
5016 continue;
5017 }
5018
5019 if buffer.contains_str_at(selection.start, &pair.end) {
5020 let pair_start_len = pair.start.len();
5021 if buffer.contains_str_at(
5022 selection.start.saturating_sub(pair_start_len),
5023 &pair.start,
5024 ) {
5025 selection.start -= pair_start_len;
5026 selection.end += pair.end.len();
5027
5028 return selection;
5029 }
5030 }
5031 }
5032 }
5033
5034 selection
5035 })
5036 .collect();
5037
5038 drop(buffer);
5039 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5040 selections.select(new_selections)
5041 });
5042 }
5043
5044 /// Iterate the given selections, and for each one, find the smallest surrounding
5045 /// autoclose region. This uses the ordering of the selections and the autoclose
5046 /// regions to avoid repeated comparisons.
5047 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5048 &'a self,
5049 selections: impl IntoIterator<Item = Selection<D>>,
5050 buffer: &'a MultiBufferSnapshot,
5051 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5052 let mut i = 0;
5053 let mut regions = self.autoclose_regions.as_slice();
5054 selections.into_iter().map(move |selection| {
5055 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5056
5057 let mut enclosing = None;
5058 while let Some(pair_state) = regions.get(i) {
5059 if pair_state.range.end.to_offset(buffer) < range.start {
5060 regions = ®ions[i + 1..];
5061 i = 0;
5062 } else if pair_state.range.start.to_offset(buffer) > range.end {
5063 break;
5064 } else {
5065 if pair_state.selection_id == selection.id {
5066 enclosing = Some(pair_state);
5067 }
5068 i += 1;
5069 }
5070 }
5071
5072 (selection, enclosing)
5073 })
5074 }
5075
5076 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5077 fn invalidate_autoclose_regions(
5078 &mut self,
5079 mut selections: &[Selection<Anchor>],
5080 buffer: &MultiBufferSnapshot,
5081 ) {
5082 self.autoclose_regions.retain(|state| {
5083 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5084 return false;
5085 }
5086
5087 let mut i = 0;
5088 while let Some(selection) = selections.get(i) {
5089 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5090 selections = &selections[1..];
5091 continue;
5092 }
5093 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5094 break;
5095 }
5096 if selection.id == state.selection_id {
5097 return true;
5098 } else {
5099 i += 1;
5100 }
5101 }
5102 false
5103 });
5104 }
5105
5106 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5107 let offset = position.to_offset(buffer);
5108 let (word_range, kind) = buffer.surrounding_word(offset, true);
5109 if offset > word_range.start && kind == Some(CharKind::Word) {
5110 Some(
5111 buffer
5112 .text_for_range(word_range.start..offset)
5113 .collect::<String>(),
5114 )
5115 } else {
5116 None
5117 }
5118 }
5119
5120 pub fn toggle_inline_values(
5121 &mut self,
5122 _: &ToggleInlineValues,
5123 _: &mut Window,
5124 cx: &mut Context<Self>,
5125 ) {
5126 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5127
5128 self.refresh_inline_values(cx);
5129 }
5130
5131 pub fn toggle_inlay_hints(
5132 &mut self,
5133 _: &ToggleInlayHints,
5134 _: &mut Window,
5135 cx: &mut Context<Self>,
5136 ) {
5137 self.refresh_inlay_hints(
5138 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5139 cx,
5140 );
5141 }
5142
5143 pub fn inlay_hints_enabled(&self) -> bool {
5144 self.inlay_hint_cache.enabled
5145 }
5146
5147 pub fn inline_values_enabled(&self) -> bool {
5148 self.inline_value_cache.enabled
5149 }
5150
5151 #[cfg(any(test, feature = "test-support"))]
5152 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5153 self.display_map
5154 .read(cx)
5155 .current_inlays()
5156 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5157 .cloned()
5158 .collect()
5159 }
5160
5161 #[cfg(any(test, feature = "test-support"))]
5162 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5163 self.display_map
5164 .read(cx)
5165 .current_inlays()
5166 .cloned()
5167 .collect()
5168 }
5169
5170 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5171 if self.semantics_provider.is_none() || !self.mode.is_full() {
5172 return;
5173 }
5174
5175 let reason_description = reason.description();
5176 let ignore_debounce = matches!(
5177 reason,
5178 InlayHintRefreshReason::SettingsChange(_)
5179 | InlayHintRefreshReason::Toggle(_)
5180 | InlayHintRefreshReason::ExcerptsRemoved(_)
5181 | InlayHintRefreshReason::ModifiersChanged(_)
5182 );
5183 let (invalidate_cache, required_languages) = match reason {
5184 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5185 match self.inlay_hint_cache.modifiers_override(enabled) {
5186 Some(enabled) => {
5187 if enabled {
5188 (InvalidationStrategy::RefreshRequested, None)
5189 } else {
5190 self.splice_inlays(
5191 &self
5192 .visible_inlay_hints(cx)
5193 .iter()
5194 .map(|inlay| inlay.id)
5195 .collect::<Vec<InlayId>>(),
5196 Vec::new(),
5197 cx,
5198 );
5199 return;
5200 }
5201 }
5202 None => return,
5203 }
5204 }
5205 InlayHintRefreshReason::Toggle(enabled) => {
5206 if self.inlay_hint_cache.toggle(enabled) {
5207 if enabled {
5208 (InvalidationStrategy::RefreshRequested, None)
5209 } else {
5210 self.splice_inlays(
5211 &self
5212 .visible_inlay_hints(cx)
5213 .iter()
5214 .map(|inlay| inlay.id)
5215 .collect::<Vec<InlayId>>(),
5216 Vec::new(),
5217 cx,
5218 );
5219 return;
5220 }
5221 } else {
5222 return;
5223 }
5224 }
5225 InlayHintRefreshReason::SettingsChange(new_settings) => {
5226 match self.inlay_hint_cache.update_settings(
5227 &self.buffer,
5228 new_settings,
5229 self.visible_inlay_hints(cx),
5230 cx,
5231 ) {
5232 ControlFlow::Break(Some(InlaySplice {
5233 to_remove,
5234 to_insert,
5235 })) => {
5236 self.splice_inlays(&to_remove, to_insert, cx);
5237 return;
5238 }
5239 ControlFlow::Break(None) => return,
5240 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5241 }
5242 }
5243 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5244 if let Some(InlaySplice {
5245 to_remove,
5246 to_insert,
5247 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5248 {
5249 self.splice_inlays(&to_remove, to_insert, cx);
5250 }
5251 self.display_map.update(cx, |display_map, _| {
5252 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5253 });
5254 return;
5255 }
5256 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5257 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5258 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5259 }
5260 InlayHintRefreshReason::RefreshRequested => {
5261 (InvalidationStrategy::RefreshRequested, None)
5262 }
5263 };
5264
5265 if let Some(InlaySplice {
5266 to_remove,
5267 to_insert,
5268 }) = self.inlay_hint_cache.spawn_hint_refresh(
5269 reason_description,
5270 self.visible_excerpts(required_languages.as_ref(), cx),
5271 invalidate_cache,
5272 ignore_debounce,
5273 cx,
5274 ) {
5275 self.splice_inlays(&to_remove, to_insert, cx);
5276 }
5277 }
5278
5279 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5280 self.display_map
5281 .read(cx)
5282 .current_inlays()
5283 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5284 .cloned()
5285 .collect()
5286 }
5287
5288 pub fn visible_excerpts(
5289 &self,
5290 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5291 cx: &mut Context<Editor>,
5292 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5293 let Some(project) = self.project() else {
5294 return HashMap::default();
5295 };
5296 let project = project.read(cx);
5297 let multi_buffer = self.buffer().read(cx);
5298 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5299 let multi_buffer_visible_start = self
5300 .scroll_manager
5301 .anchor()
5302 .anchor
5303 .to_point(&multi_buffer_snapshot);
5304 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5305 multi_buffer_visible_start
5306 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5307 Bias::Left,
5308 );
5309 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5310 multi_buffer_snapshot
5311 .range_to_buffer_ranges(multi_buffer_visible_range)
5312 .into_iter()
5313 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5314 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5315 let buffer_file = project::File::from_dyn(buffer.file())?;
5316 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5317 let worktree_entry = buffer_worktree
5318 .read(cx)
5319 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5320 if worktree_entry.is_ignored {
5321 return None;
5322 }
5323
5324 let language = buffer.language()?;
5325 if let Some(restrict_to_languages) = restrict_to_languages
5326 && !restrict_to_languages.contains(language)
5327 {
5328 return None;
5329 }
5330 Some((
5331 excerpt_id,
5332 (
5333 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5334 buffer.version().clone(),
5335 excerpt_visible_range,
5336 ),
5337 ))
5338 })
5339 .collect()
5340 }
5341
5342 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5343 TextLayoutDetails {
5344 text_system: window.text_system().clone(),
5345 editor_style: self.style.clone().unwrap(),
5346 rem_size: window.rem_size(),
5347 scroll_anchor: self.scroll_manager.anchor(),
5348 visible_rows: self.visible_line_count(),
5349 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5350 }
5351 }
5352
5353 pub fn splice_inlays(
5354 &self,
5355 to_remove: &[InlayId],
5356 to_insert: Vec<Inlay>,
5357 cx: &mut Context<Self>,
5358 ) {
5359 self.display_map.update(cx, |display_map, cx| {
5360 display_map.splice_inlays(to_remove, to_insert, cx)
5361 });
5362 cx.notify();
5363 }
5364
5365 fn trigger_on_type_formatting(
5366 &self,
5367 input: String,
5368 window: &mut Window,
5369 cx: &mut Context<Self>,
5370 ) -> Option<Task<Result<()>>> {
5371 if input.len() != 1 {
5372 return None;
5373 }
5374
5375 let project = self.project()?;
5376 let position = self.selections.newest_anchor().head();
5377 let (buffer, buffer_position) = self
5378 .buffer
5379 .read(cx)
5380 .text_anchor_for_position(position, cx)?;
5381
5382 let settings = language_settings::language_settings(
5383 buffer
5384 .read(cx)
5385 .language_at(buffer_position)
5386 .map(|l| l.name()),
5387 buffer.read(cx).file(),
5388 cx,
5389 );
5390 if !settings.use_on_type_format {
5391 return None;
5392 }
5393
5394 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5395 // hence we do LSP request & edit on host side only — add formats to host's history.
5396 let push_to_lsp_host_history = true;
5397 // If this is not the host, append its history with new edits.
5398 let push_to_client_history = project.read(cx).is_via_collab();
5399
5400 let on_type_formatting = project.update(cx, |project, cx| {
5401 project.on_type_format(
5402 buffer.clone(),
5403 buffer_position,
5404 input,
5405 push_to_lsp_host_history,
5406 cx,
5407 )
5408 });
5409 Some(cx.spawn_in(window, async move |editor, cx| {
5410 if let Some(transaction) = on_type_formatting.await? {
5411 if push_to_client_history {
5412 buffer
5413 .update(cx, |buffer, _| {
5414 buffer.push_transaction(transaction, Instant::now());
5415 buffer.finalize_last_transaction();
5416 })
5417 .ok();
5418 }
5419 editor.update(cx, |editor, cx| {
5420 editor.refresh_document_highlights(cx);
5421 })?;
5422 }
5423 Ok(())
5424 }))
5425 }
5426
5427 pub fn show_word_completions(
5428 &mut self,
5429 _: &ShowWordCompletions,
5430 window: &mut Window,
5431 cx: &mut Context<Self>,
5432 ) {
5433 self.open_or_update_completions_menu(
5434 Some(CompletionsMenuSource::Words {
5435 ignore_threshold: true,
5436 }),
5437 None,
5438 window,
5439 cx,
5440 );
5441 }
5442
5443 pub fn show_completions(
5444 &mut self,
5445 options: &ShowCompletions,
5446 window: &mut Window,
5447 cx: &mut Context<Self>,
5448 ) {
5449 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5450 }
5451
5452 fn open_or_update_completions_menu(
5453 &mut self,
5454 requested_source: Option<CompletionsMenuSource>,
5455 trigger: Option<&str>,
5456 window: &mut Window,
5457 cx: &mut Context<Self>,
5458 ) {
5459 if self.pending_rename.is_some() {
5460 return;
5461 }
5462
5463 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5464
5465 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5466 // inserted and selected. To handle that case, the start of the selection is used so that
5467 // the menu starts with all choices.
5468 let position = self
5469 .selections
5470 .newest_anchor()
5471 .start
5472 .bias_right(&multibuffer_snapshot);
5473 if position.diff_base_anchor.is_some() {
5474 return;
5475 }
5476 let (buffer, buffer_position) =
5477 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5478 output
5479 } else {
5480 return;
5481 };
5482 let buffer_snapshot = buffer.read(cx).snapshot();
5483
5484 let query: Option<Arc<String>> =
5485 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5486
5487 drop(multibuffer_snapshot);
5488
5489 let mut ignore_word_threshold = false;
5490 let provider = match requested_source {
5491 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5492 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5493 ignore_word_threshold = ignore_threshold;
5494 None
5495 }
5496 Some(CompletionsMenuSource::SnippetChoices) => {
5497 log::error!("bug: SnippetChoices requested_source is not handled");
5498 None
5499 }
5500 };
5501
5502 let sort_completions = provider
5503 .as_ref()
5504 .is_some_and(|provider| provider.sort_completions());
5505
5506 let filter_completions = provider
5507 .as_ref()
5508 .is_none_or(|provider| provider.filter_completions());
5509
5510 let trigger_kind = match trigger {
5511 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5512 CompletionTriggerKind::TRIGGER_CHARACTER
5513 }
5514 _ => CompletionTriggerKind::INVOKED,
5515 };
5516 let completion_context = CompletionContext {
5517 trigger_character: trigger.and_then(|trigger| {
5518 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5519 Some(String::from(trigger))
5520 } else {
5521 None
5522 }
5523 }),
5524 trigger_kind,
5525 };
5526
5527 // Hide the current completions menu when a trigger char is typed. Without this, cached
5528 // completions from before the trigger char may be reused (#32774). Snippet choices could
5529 // involve trigger chars, so this is skipped in that case.
5530 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5531 {
5532 let menu_is_open = matches!(
5533 self.context_menu.borrow().as_ref(),
5534 Some(CodeContextMenu::Completions(_))
5535 );
5536 if menu_is_open {
5537 self.hide_context_menu(window, cx);
5538 }
5539 }
5540
5541 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5542 if filter_completions {
5543 menu.filter(query.clone(), provider.clone(), window, cx);
5544 }
5545 // When `is_incomplete` is false, no need to re-query completions when the current query
5546 // is a suffix of the initial query.
5547 if !menu.is_incomplete {
5548 // If the new query is a suffix of the old query (typing more characters) and
5549 // the previous result was complete, the existing completions can be filtered.
5550 //
5551 // Note that this is always true for snippet completions.
5552 let query_matches = match (&menu.initial_query, &query) {
5553 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5554 (None, _) => true,
5555 _ => false,
5556 };
5557 if query_matches {
5558 let position_matches = if menu.initial_position == position {
5559 true
5560 } else {
5561 let snapshot = self.buffer.read(cx).read(cx);
5562 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5563 };
5564 if position_matches {
5565 return;
5566 }
5567 }
5568 }
5569 };
5570
5571 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5572 buffer_snapshot.surrounding_word(buffer_position, false)
5573 {
5574 let word_to_exclude = buffer_snapshot
5575 .text_for_range(word_range.clone())
5576 .collect::<String>();
5577 (
5578 buffer_snapshot.anchor_before(word_range.start)
5579 ..buffer_snapshot.anchor_after(buffer_position),
5580 Some(word_to_exclude),
5581 )
5582 } else {
5583 (buffer_position..buffer_position, None)
5584 };
5585
5586 let language = buffer_snapshot
5587 .language_at(buffer_position)
5588 .map(|language| language.name());
5589
5590 let completion_settings =
5591 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5592
5593 let show_completion_documentation = buffer_snapshot
5594 .settings_at(buffer_position, cx)
5595 .show_completion_documentation;
5596
5597 // The document can be large, so stay in reasonable bounds when searching for words,
5598 // otherwise completion pop-up might be slow to appear.
5599 const WORD_LOOKUP_ROWS: u32 = 5_000;
5600 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5601 let min_word_search = buffer_snapshot.clip_point(
5602 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5603 Bias::Left,
5604 );
5605 let max_word_search = buffer_snapshot.clip_point(
5606 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5607 Bias::Right,
5608 );
5609 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5610 ..buffer_snapshot.point_to_offset(max_word_search);
5611
5612 let skip_digits = query
5613 .as_ref()
5614 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5615
5616 let omit_word_completions = !self.word_completions_enabled
5617 || (!ignore_word_threshold
5618 && match &query {
5619 Some(query) => query.chars().count() < completion_settings.words_min_length,
5620 None => completion_settings.words_min_length != 0,
5621 });
5622
5623 let (mut words, provider_responses) = match &provider {
5624 Some(provider) => {
5625 let provider_responses = provider.completions(
5626 position.excerpt_id,
5627 &buffer,
5628 buffer_position,
5629 completion_context,
5630 window,
5631 cx,
5632 );
5633
5634 let words = match (omit_word_completions, completion_settings.words) {
5635 (true, _) | (_, WordsCompletionMode::Disabled) => {
5636 Task::ready(BTreeMap::default())
5637 }
5638 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5639 .background_spawn(async move {
5640 buffer_snapshot.words_in_range(WordsQuery {
5641 fuzzy_contents: None,
5642 range: word_search_range,
5643 skip_digits,
5644 })
5645 }),
5646 };
5647
5648 (words, provider_responses)
5649 }
5650 None => {
5651 let words = if omit_word_completions {
5652 Task::ready(BTreeMap::default())
5653 } else {
5654 cx.background_spawn(async move {
5655 buffer_snapshot.words_in_range(WordsQuery {
5656 fuzzy_contents: None,
5657 range: word_search_range,
5658 skip_digits,
5659 })
5660 })
5661 };
5662 (words, Task::ready(Ok(Vec::new())))
5663 }
5664 };
5665
5666 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5667
5668 let id = post_inc(&mut self.next_completion_id);
5669 let task = cx.spawn_in(window, async move |editor, cx| {
5670 let Ok(()) = editor.update(cx, |this, _| {
5671 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5672 }) else {
5673 return;
5674 };
5675
5676 // TODO: Ideally completions from different sources would be selectively re-queried, so
5677 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5678 let mut completions = Vec::new();
5679 let mut is_incomplete = false;
5680 let mut display_options: Option<CompletionDisplayOptions> = None;
5681 if let Some(provider_responses) = provider_responses.await.log_err()
5682 && !provider_responses.is_empty()
5683 {
5684 for response in provider_responses {
5685 completions.extend(response.completions);
5686 is_incomplete = is_incomplete || response.is_incomplete;
5687 match display_options.as_mut() {
5688 None => {
5689 display_options = Some(response.display_options);
5690 }
5691 Some(options) => options.merge(&response.display_options),
5692 }
5693 }
5694 if completion_settings.words == WordsCompletionMode::Fallback {
5695 words = Task::ready(BTreeMap::default());
5696 }
5697 }
5698 let display_options = display_options.unwrap_or_default();
5699
5700 let mut words = words.await;
5701 if let Some(word_to_exclude) = &word_to_exclude {
5702 words.remove(word_to_exclude);
5703 }
5704 for lsp_completion in &completions {
5705 words.remove(&lsp_completion.new_text);
5706 }
5707 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5708 replace_range: word_replace_range.clone(),
5709 new_text: word.clone(),
5710 label: CodeLabel::plain(word, None),
5711 icon_path: None,
5712 documentation: None,
5713 source: CompletionSource::BufferWord {
5714 word_range,
5715 resolved: false,
5716 },
5717 insert_text_mode: Some(InsertTextMode::AS_IS),
5718 confirm: None,
5719 }));
5720
5721 let menu = if completions.is_empty() {
5722 None
5723 } else {
5724 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5725 let languages = editor
5726 .workspace
5727 .as_ref()
5728 .and_then(|(workspace, _)| workspace.upgrade())
5729 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5730 let menu = CompletionsMenu::new(
5731 id,
5732 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5733 sort_completions,
5734 show_completion_documentation,
5735 position,
5736 query.clone(),
5737 is_incomplete,
5738 buffer.clone(),
5739 completions.into(),
5740 display_options,
5741 snippet_sort_order,
5742 languages,
5743 language,
5744 cx,
5745 );
5746
5747 let query = if filter_completions { query } else { None };
5748 let matches_task = if let Some(query) = query {
5749 menu.do_async_filtering(query, cx)
5750 } else {
5751 Task::ready(menu.unfiltered_matches())
5752 };
5753 (menu, matches_task)
5754 }) else {
5755 return;
5756 };
5757
5758 let matches = matches_task.await;
5759
5760 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5761 // Newer menu already set, so exit.
5762 if let Some(CodeContextMenu::Completions(prev_menu)) =
5763 editor.context_menu.borrow().as_ref()
5764 && prev_menu.id > id
5765 {
5766 return;
5767 };
5768
5769 // Only valid to take prev_menu because it the new menu is immediately set
5770 // below, or the menu is hidden.
5771 if let Some(CodeContextMenu::Completions(prev_menu)) =
5772 editor.context_menu.borrow_mut().take()
5773 {
5774 let position_matches =
5775 if prev_menu.initial_position == menu.initial_position {
5776 true
5777 } else {
5778 let snapshot = editor.buffer.read(cx).read(cx);
5779 prev_menu.initial_position.to_offset(&snapshot)
5780 == menu.initial_position.to_offset(&snapshot)
5781 };
5782 if position_matches {
5783 // Preserve markdown cache before `set_filter_results` because it will
5784 // try to populate the documentation cache.
5785 menu.preserve_markdown_cache(prev_menu);
5786 }
5787 };
5788
5789 menu.set_filter_results(matches, provider, window, cx);
5790 }) else {
5791 return;
5792 };
5793
5794 menu.visible().then_some(menu)
5795 };
5796
5797 editor
5798 .update_in(cx, |editor, window, cx| {
5799 if editor.focus_handle.is_focused(window)
5800 && let Some(menu) = menu
5801 {
5802 *editor.context_menu.borrow_mut() =
5803 Some(CodeContextMenu::Completions(menu));
5804
5805 crate::hover_popover::hide_hover(editor, cx);
5806 if editor.show_edit_predictions_in_menu() {
5807 editor.update_visible_edit_prediction(window, cx);
5808 } else {
5809 editor.discard_edit_prediction(false, cx);
5810 }
5811
5812 cx.notify();
5813 return;
5814 }
5815
5816 if editor.completion_tasks.len() <= 1 {
5817 // If there are no more completion tasks and the last menu was empty, we should hide it.
5818 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5819 // If it was already hidden and we don't show edit predictions in the menu,
5820 // we should also show the edit prediction when available.
5821 if was_hidden && editor.show_edit_predictions_in_menu() {
5822 editor.update_visible_edit_prediction(window, cx);
5823 }
5824 }
5825 })
5826 .ok();
5827 });
5828
5829 self.completion_tasks.push((id, task));
5830 }
5831
5832 #[cfg(feature = "test-support")]
5833 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5834 let menu = self.context_menu.borrow();
5835 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5836 let completions = menu.completions.borrow();
5837 Some(completions.to_vec())
5838 } else {
5839 None
5840 }
5841 }
5842
5843 pub fn with_completions_menu_matching_id<R>(
5844 &self,
5845 id: CompletionId,
5846 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5847 ) -> R {
5848 let mut context_menu = self.context_menu.borrow_mut();
5849 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5850 return f(None);
5851 };
5852 if completions_menu.id != id {
5853 return f(None);
5854 }
5855 f(Some(completions_menu))
5856 }
5857
5858 pub fn confirm_completion(
5859 &mut self,
5860 action: &ConfirmCompletion,
5861 window: &mut Window,
5862 cx: &mut Context<Self>,
5863 ) -> Option<Task<Result<()>>> {
5864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5865 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5866 }
5867
5868 pub fn confirm_completion_insert(
5869 &mut self,
5870 _: &ConfirmCompletionInsert,
5871 window: &mut Window,
5872 cx: &mut Context<Self>,
5873 ) -> Option<Task<Result<()>>> {
5874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5875 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5876 }
5877
5878 pub fn confirm_completion_replace(
5879 &mut self,
5880 _: &ConfirmCompletionReplace,
5881 window: &mut Window,
5882 cx: &mut Context<Self>,
5883 ) -> Option<Task<Result<()>>> {
5884 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5885 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5886 }
5887
5888 pub fn compose_completion(
5889 &mut self,
5890 action: &ComposeCompletion,
5891 window: &mut Window,
5892 cx: &mut Context<Self>,
5893 ) -> Option<Task<Result<()>>> {
5894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5895 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5896 }
5897
5898 fn do_completion(
5899 &mut self,
5900 item_ix: Option<usize>,
5901 intent: CompletionIntent,
5902 window: &mut Window,
5903 cx: &mut Context<Editor>,
5904 ) -> Option<Task<Result<()>>> {
5905 use language::ToOffset as _;
5906
5907 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5908 else {
5909 return None;
5910 };
5911
5912 let candidate_id = {
5913 let entries = completions_menu.entries.borrow();
5914 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5915 if self.show_edit_predictions_in_menu() {
5916 self.discard_edit_prediction(true, cx);
5917 }
5918 mat.candidate_id
5919 };
5920
5921 let completion = completions_menu
5922 .completions
5923 .borrow()
5924 .get(candidate_id)?
5925 .clone();
5926 cx.stop_propagation();
5927
5928 let buffer_handle = completions_menu.buffer.clone();
5929
5930 let CompletionEdit {
5931 new_text,
5932 snippet,
5933 replace_range,
5934 } = process_completion_for_edit(
5935 &completion,
5936 intent,
5937 &buffer_handle,
5938 &completions_menu.initial_position.text_anchor,
5939 cx,
5940 );
5941
5942 let buffer = buffer_handle.read(cx);
5943 let snapshot = self.buffer.read(cx).snapshot(cx);
5944 let newest_anchor = self.selections.newest_anchor();
5945 let replace_range_multibuffer = {
5946 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5947 let multibuffer_anchor = snapshot
5948 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5949 .unwrap()
5950 ..snapshot
5951 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5952 .unwrap();
5953 multibuffer_anchor.start.to_offset(&snapshot)
5954 ..multibuffer_anchor.end.to_offset(&snapshot)
5955 };
5956 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5957 return None;
5958 }
5959
5960 let old_text = buffer
5961 .text_for_range(replace_range.clone())
5962 .collect::<String>();
5963 let lookbehind = newest_anchor
5964 .start
5965 .text_anchor
5966 .to_offset(buffer)
5967 .saturating_sub(replace_range.start);
5968 let lookahead = replace_range
5969 .end
5970 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5971 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5972 let suffix = &old_text[lookbehind.min(old_text.len())..];
5973
5974 let selections = self.selections.all::<usize>(cx);
5975 let mut ranges = Vec::new();
5976 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5977
5978 for selection in &selections {
5979 let range = if selection.id == newest_anchor.id {
5980 replace_range_multibuffer.clone()
5981 } else {
5982 let mut range = selection.range();
5983
5984 // if prefix is present, don't duplicate it
5985 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5986 range.start = range.start.saturating_sub(lookbehind);
5987
5988 // if suffix is also present, mimic the newest cursor and replace it
5989 if selection.id != newest_anchor.id
5990 && snapshot.contains_str_at(range.end, suffix)
5991 {
5992 range.end += lookahead;
5993 }
5994 }
5995 range
5996 };
5997
5998 ranges.push(range.clone());
5999
6000 if !self.linked_edit_ranges.is_empty() {
6001 let start_anchor = snapshot.anchor_before(range.start);
6002 let end_anchor = snapshot.anchor_after(range.end);
6003 if let Some(ranges) = self
6004 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6005 {
6006 for (buffer, edits) in ranges {
6007 linked_edits
6008 .entry(buffer.clone())
6009 .or_default()
6010 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6011 }
6012 }
6013 }
6014 }
6015
6016 let common_prefix_len = old_text
6017 .chars()
6018 .zip(new_text.chars())
6019 .take_while(|(a, b)| a == b)
6020 .map(|(a, _)| a.len_utf8())
6021 .sum::<usize>();
6022
6023 cx.emit(EditorEvent::InputHandled {
6024 utf16_range_to_replace: None,
6025 text: new_text[common_prefix_len..].into(),
6026 });
6027
6028 self.transact(window, cx, |editor, window, cx| {
6029 if let Some(mut snippet) = snippet {
6030 snippet.text = new_text.to_string();
6031 editor
6032 .insert_snippet(&ranges, snippet, window, cx)
6033 .log_err();
6034 } else {
6035 editor.buffer.update(cx, |multi_buffer, cx| {
6036 let auto_indent = match completion.insert_text_mode {
6037 Some(InsertTextMode::AS_IS) => None,
6038 _ => editor.autoindent_mode.clone(),
6039 };
6040 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6041 multi_buffer.edit(edits, auto_indent, cx);
6042 });
6043 }
6044 for (buffer, edits) in linked_edits {
6045 buffer.update(cx, |buffer, cx| {
6046 let snapshot = buffer.snapshot();
6047 let edits = edits
6048 .into_iter()
6049 .map(|(range, text)| {
6050 use text::ToPoint as TP;
6051 let end_point = TP::to_point(&range.end, &snapshot);
6052 let start_point = TP::to_point(&range.start, &snapshot);
6053 (start_point..end_point, text)
6054 })
6055 .sorted_by_key(|(range, _)| range.start);
6056 buffer.edit(edits, None, cx);
6057 })
6058 }
6059
6060 editor.refresh_edit_prediction(true, false, window, cx);
6061 });
6062 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6063
6064 let show_new_completions_on_confirm = completion
6065 .confirm
6066 .as_ref()
6067 .is_some_and(|confirm| confirm(intent, window, cx));
6068 if show_new_completions_on_confirm {
6069 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6070 }
6071
6072 let provider = self.completion_provider.as_ref()?;
6073 drop(completion);
6074 let apply_edits = provider.apply_additional_edits_for_completion(
6075 buffer_handle,
6076 completions_menu.completions.clone(),
6077 candidate_id,
6078 true,
6079 cx,
6080 );
6081
6082 let editor_settings = EditorSettings::get_global(cx);
6083 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6084 // After the code completion is finished, users often want to know what signatures are needed.
6085 // so we should automatically call signature_help
6086 self.show_signature_help(&ShowSignatureHelp, window, cx);
6087 }
6088
6089 Some(cx.foreground_executor().spawn(async move {
6090 apply_edits.await?;
6091 Ok(())
6092 }))
6093 }
6094
6095 pub fn toggle_code_actions(
6096 &mut self,
6097 action: &ToggleCodeActions,
6098 window: &mut Window,
6099 cx: &mut Context<Self>,
6100 ) {
6101 let quick_launch = action.quick_launch;
6102 let mut context_menu = self.context_menu.borrow_mut();
6103 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6104 if code_actions.deployed_from == action.deployed_from {
6105 // Toggle if we're selecting the same one
6106 *context_menu = None;
6107 cx.notify();
6108 return;
6109 } else {
6110 // Otherwise, clear it and start a new one
6111 *context_menu = None;
6112 cx.notify();
6113 }
6114 }
6115 drop(context_menu);
6116 let snapshot = self.snapshot(window, cx);
6117 let deployed_from = action.deployed_from.clone();
6118 let action = action.clone();
6119 self.completion_tasks.clear();
6120 self.discard_edit_prediction(false, cx);
6121
6122 let multibuffer_point = match &action.deployed_from {
6123 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6124 DisplayPoint::new(*row, 0).to_point(&snapshot)
6125 }
6126 _ => self.selections.newest::<Point>(cx).head(),
6127 };
6128 let Some((buffer, buffer_row)) = snapshot
6129 .buffer_snapshot
6130 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6131 .and_then(|(buffer_snapshot, range)| {
6132 self.buffer()
6133 .read(cx)
6134 .buffer(buffer_snapshot.remote_id())
6135 .map(|buffer| (buffer, range.start.row))
6136 })
6137 else {
6138 return;
6139 };
6140 let buffer_id = buffer.read(cx).remote_id();
6141 let tasks = self
6142 .tasks
6143 .get(&(buffer_id, buffer_row))
6144 .map(|t| Arc::new(t.to_owned()));
6145
6146 if !self.focus_handle.is_focused(window) {
6147 return;
6148 }
6149 let project = self.project.clone();
6150
6151 let code_actions_task = match deployed_from {
6152 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6153 _ => self.code_actions(buffer_row, window, cx),
6154 };
6155
6156 let runnable_task = match deployed_from {
6157 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6158 _ => {
6159 let mut task_context_task = Task::ready(None);
6160 if let Some(tasks) = &tasks
6161 && let Some(project) = project
6162 {
6163 task_context_task =
6164 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6165 }
6166
6167 cx.spawn_in(window, {
6168 let buffer = buffer.clone();
6169 async move |editor, cx| {
6170 let task_context = task_context_task.await;
6171
6172 let resolved_tasks =
6173 tasks
6174 .zip(task_context.clone())
6175 .map(|(tasks, task_context)| ResolvedTasks {
6176 templates: tasks.resolve(&task_context).collect(),
6177 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6178 multibuffer_point.row,
6179 tasks.column,
6180 )),
6181 });
6182 let debug_scenarios = editor
6183 .update(cx, |editor, cx| {
6184 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6185 })?
6186 .await;
6187 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6188 }
6189 })
6190 }
6191 };
6192
6193 cx.spawn_in(window, async move |editor, cx| {
6194 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6195 let code_actions = code_actions_task.await;
6196 let spawn_straight_away = quick_launch
6197 && resolved_tasks
6198 .as_ref()
6199 .is_some_and(|tasks| tasks.templates.len() == 1)
6200 && code_actions
6201 .as_ref()
6202 .is_none_or(|actions| actions.is_empty())
6203 && debug_scenarios.is_empty();
6204
6205 editor.update_in(cx, |editor, window, cx| {
6206 crate::hover_popover::hide_hover(editor, cx);
6207 let actions = CodeActionContents::new(
6208 resolved_tasks,
6209 code_actions,
6210 debug_scenarios,
6211 task_context.unwrap_or_default(),
6212 );
6213
6214 // Don't show the menu if there are no actions available
6215 if actions.is_empty() {
6216 cx.notify();
6217 return Task::ready(Ok(()));
6218 }
6219
6220 *editor.context_menu.borrow_mut() =
6221 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6222 buffer,
6223 actions,
6224 selected_item: Default::default(),
6225 scroll_handle: UniformListScrollHandle::default(),
6226 deployed_from,
6227 }));
6228 cx.notify();
6229 if spawn_straight_away
6230 && let Some(task) = editor.confirm_code_action(
6231 &ConfirmCodeAction { item_ix: Some(0) },
6232 window,
6233 cx,
6234 )
6235 {
6236 return task;
6237 }
6238
6239 Task::ready(Ok(()))
6240 })
6241 })
6242 .detach_and_log_err(cx);
6243 }
6244
6245 fn debug_scenarios(
6246 &mut self,
6247 resolved_tasks: &Option<ResolvedTasks>,
6248 buffer: &Entity<Buffer>,
6249 cx: &mut App,
6250 ) -> Task<Vec<task::DebugScenario>> {
6251 maybe!({
6252 let project = self.project()?;
6253 let dap_store = project.read(cx).dap_store();
6254 let mut scenarios = vec![];
6255 let resolved_tasks = resolved_tasks.as_ref()?;
6256 let buffer = buffer.read(cx);
6257 let language = buffer.language()?;
6258 let file = buffer.file();
6259 let debug_adapter = language_settings(language.name().into(), file, cx)
6260 .debuggers
6261 .first()
6262 .map(SharedString::from)
6263 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6264
6265 dap_store.update(cx, |dap_store, cx| {
6266 for (_, task) in &resolved_tasks.templates {
6267 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6268 task.original_task().clone(),
6269 debug_adapter.clone().into(),
6270 task.display_label().to_owned().into(),
6271 cx,
6272 );
6273 scenarios.push(maybe_scenario);
6274 }
6275 });
6276 Some(cx.background_spawn(async move {
6277 futures::future::join_all(scenarios)
6278 .await
6279 .into_iter()
6280 .flatten()
6281 .collect::<Vec<_>>()
6282 }))
6283 })
6284 .unwrap_or_else(|| Task::ready(vec![]))
6285 }
6286
6287 fn code_actions(
6288 &mut self,
6289 buffer_row: u32,
6290 window: &mut Window,
6291 cx: &mut Context<Self>,
6292 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6293 let mut task = self.code_actions_task.take();
6294 cx.spawn_in(window, async move |editor, cx| {
6295 while let Some(prev_task) = task {
6296 prev_task.await.log_err();
6297 task = editor
6298 .update(cx, |this, _| this.code_actions_task.take())
6299 .ok()?;
6300 }
6301
6302 editor
6303 .update(cx, |editor, cx| {
6304 editor
6305 .available_code_actions
6306 .clone()
6307 .and_then(|(location, code_actions)| {
6308 let snapshot = location.buffer.read(cx).snapshot();
6309 let point_range = location.range.to_point(&snapshot);
6310 let point_range = point_range.start.row..=point_range.end.row;
6311 if point_range.contains(&buffer_row) {
6312 Some(code_actions)
6313 } else {
6314 None
6315 }
6316 })
6317 })
6318 .ok()
6319 .flatten()
6320 })
6321 }
6322
6323 pub fn confirm_code_action(
6324 &mut self,
6325 action: &ConfirmCodeAction,
6326 window: &mut Window,
6327 cx: &mut Context<Self>,
6328 ) -> Option<Task<Result<()>>> {
6329 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6330
6331 let actions_menu =
6332 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6333 menu
6334 } else {
6335 return None;
6336 };
6337
6338 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6339 let action = actions_menu.actions.get(action_ix)?;
6340 let title = action.label();
6341 let buffer = actions_menu.buffer;
6342 let workspace = self.workspace()?;
6343
6344 match action {
6345 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6346 workspace.update(cx, |workspace, cx| {
6347 workspace.schedule_resolved_task(
6348 task_source_kind,
6349 resolved_task,
6350 false,
6351 window,
6352 cx,
6353 );
6354
6355 Some(Task::ready(Ok(())))
6356 })
6357 }
6358 CodeActionsItem::CodeAction {
6359 excerpt_id,
6360 action,
6361 provider,
6362 } => {
6363 let apply_code_action =
6364 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6365 let workspace = workspace.downgrade();
6366 Some(cx.spawn_in(window, async move |editor, cx| {
6367 let project_transaction = apply_code_action.await?;
6368 Self::open_project_transaction(
6369 &editor,
6370 workspace,
6371 project_transaction,
6372 title,
6373 cx,
6374 )
6375 .await
6376 }))
6377 }
6378 CodeActionsItem::DebugScenario(scenario) => {
6379 let context = actions_menu.actions.context;
6380
6381 workspace.update(cx, |workspace, cx| {
6382 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6383 workspace.start_debug_session(
6384 scenario,
6385 context,
6386 Some(buffer),
6387 None,
6388 window,
6389 cx,
6390 );
6391 });
6392 Some(Task::ready(Ok(())))
6393 }
6394 }
6395 }
6396
6397 pub async fn open_project_transaction(
6398 editor: &WeakEntity<Editor>,
6399 workspace: WeakEntity<Workspace>,
6400 transaction: ProjectTransaction,
6401 title: String,
6402 cx: &mut AsyncWindowContext,
6403 ) -> Result<()> {
6404 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6405 cx.update(|_, cx| {
6406 entries.sort_unstable_by_key(|(buffer, _)| {
6407 buffer.read(cx).file().map(|f| f.path().clone())
6408 });
6409 })?;
6410
6411 // If the project transaction's edits are all contained within this editor, then
6412 // avoid opening a new editor to display them.
6413
6414 if let Some((buffer, transaction)) = entries.first() {
6415 if entries.len() == 1 {
6416 let excerpt = editor.update(cx, |editor, cx| {
6417 editor
6418 .buffer()
6419 .read(cx)
6420 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6421 })?;
6422 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6423 && excerpted_buffer == *buffer
6424 {
6425 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6426 let excerpt_range = excerpt_range.to_offset(buffer);
6427 buffer
6428 .edited_ranges_for_transaction::<usize>(transaction)
6429 .all(|range| {
6430 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6431 })
6432 })?;
6433
6434 if all_edits_within_excerpt {
6435 return Ok(());
6436 }
6437 }
6438 }
6439 } else {
6440 return Ok(());
6441 }
6442
6443 let mut ranges_to_highlight = Vec::new();
6444 let excerpt_buffer = cx.new(|cx| {
6445 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6446 for (buffer_handle, transaction) in &entries {
6447 let edited_ranges = buffer_handle
6448 .read(cx)
6449 .edited_ranges_for_transaction::<Point>(transaction)
6450 .collect::<Vec<_>>();
6451 let (ranges, _) = multibuffer.set_excerpts_for_path(
6452 PathKey::for_buffer(buffer_handle, cx),
6453 buffer_handle.clone(),
6454 edited_ranges,
6455 multibuffer_context_lines(cx),
6456 cx,
6457 );
6458
6459 ranges_to_highlight.extend(ranges);
6460 }
6461 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6462 multibuffer
6463 })?;
6464
6465 workspace.update_in(cx, |workspace, window, cx| {
6466 let project = workspace.project().clone();
6467 let editor =
6468 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6469 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6470 editor.update(cx, |editor, cx| {
6471 editor.highlight_background::<Self>(
6472 &ranges_to_highlight,
6473 |theme| theme.colors().editor_highlighted_line_background,
6474 cx,
6475 );
6476 });
6477 })?;
6478
6479 Ok(())
6480 }
6481
6482 pub fn clear_code_action_providers(&mut self) {
6483 self.code_action_providers.clear();
6484 self.available_code_actions.take();
6485 }
6486
6487 pub fn add_code_action_provider(
6488 &mut self,
6489 provider: Rc<dyn CodeActionProvider>,
6490 window: &mut Window,
6491 cx: &mut Context<Self>,
6492 ) {
6493 if self
6494 .code_action_providers
6495 .iter()
6496 .any(|existing_provider| existing_provider.id() == provider.id())
6497 {
6498 return;
6499 }
6500
6501 self.code_action_providers.push(provider);
6502 self.refresh_code_actions(window, cx);
6503 }
6504
6505 pub fn remove_code_action_provider(
6506 &mut self,
6507 id: Arc<str>,
6508 window: &mut Window,
6509 cx: &mut Context<Self>,
6510 ) {
6511 self.code_action_providers
6512 .retain(|provider| provider.id() != id);
6513 self.refresh_code_actions(window, cx);
6514 }
6515
6516 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6517 !self.code_action_providers.is_empty()
6518 && EditorSettings::get_global(cx).toolbar.code_actions
6519 }
6520
6521 pub fn has_available_code_actions(&self) -> bool {
6522 self.available_code_actions
6523 .as_ref()
6524 .is_some_and(|(_, actions)| !actions.is_empty())
6525 }
6526
6527 fn render_inline_code_actions(
6528 &self,
6529 icon_size: ui::IconSize,
6530 display_row: DisplayRow,
6531 is_active: bool,
6532 cx: &mut Context<Self>,
6533 ) -> AnyElement {
6534 let show_tooltip = !self.context_menu_visible();
6535 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6536 .icon_size(icon_size)
6537 .shape(ui::IconButtonShape::Square)
6538 .icon_color(ui::Color::Hidden)
6539 .toggle_state(is_active)
6540 .when(show_tooltip, |this| {
6541 this.tooltip({
6542 let focus_handle = self.focus_handle.clone();
6543 move |window, cx| {
6544 Tooltip::for_action_in(
6545 "Toggle Code Actions",
6546 &ToggleCodeActions {
6547 deployed_from: None,
6548 quick_launch: false,
6549 },
6550 &focus_handle,
6551 window,
6552 cx,
6553 )
6554 }
6555 })
6556 })
6557 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6558 window.focus(&editor.focus_handle(cx));
6559 editor.toggle_code_actions(
6560 &crate::actions::ToggleCodeActions {
6561 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6562 display_row,
6563 )),
6564 quick_launch: false,
6565 },
6566 window,
6567 cx,
6568 );
6569 }))
6570 .into_any_element()
6571 }
6572
6573 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6574 &self.context_menu
6575 }
6576
6577 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6578 let newest_selection = self.selections.newest_anchor().clone();
6579 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6580 let buffer = self.buffer.read(cx);
6581 if newest_selection.head().diff_base_anchor.is_some() {
6582 return None;
6583 }
6584 let (start_buffer, start) =
6585 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6586 let (end_buffer, end) =
6587 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6588 if start_buffer != end_buffer {
6589 return None;
6590 }
6591
6592 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6593 cx.background_executor()
6594 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6595 .await;
6596
6597 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6598 let providers = this.code_action_providers.clone();
6599 let tasks = this
6600 .code_action_providers
6601 .iter()
6602 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6603 .collect::<Vec<_>>();
6604 (providers, tasks)
6605 })?;
6606
6607 let mut actions = Vec::new();
6608 for (provider, provider_actions) in
6609 providers.into_iter().zip(future::join_all(tasks).await)
6610 {
6611 if let Some(provider_actions) = provider_actions.log_err() {
6612 actions.extend(provider_actions.into_iter().map(|action| {
6613 AvailableCodeAction {
6614 excerpt_id: newest_selection.start.excerpt_id,
6615 action,
6616 provider: provider.clone(),
6617 }
6618 }));
6619 }
6620 }
6621
6622 this.update(cx, |this, cx| {
6623 this.available_code_actions = if actions.is_empty() {
6624 None
6625 } else {
6626 Some((
6627 Location {
6628 buffer: start_buffer,
6629 range: start..end,
6630 },
6631 actions.into(),
6632 ))
6633 };
6634 cx.notify();
6635 })
6636 }));
6637 None
6638 }
6639
6640 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6641 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6642 self.show_git_blame_inline = false;
6643
6644 self.show_git_blame_inline_delay_task =
6645 Some(cx.spawn_in(window, async move |this, cx| {
6646 cx.background_executor().timer(delay).await;
6647
6648 this.update(cx, |this, cx| {
6649 this.show_git_blame_inline = true;
6650 cx.notify();
6651 })
6652 .log_err();
6653 }));
6654 }
6655 }
6656
6657 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6658 let snapshot = self.snapshot(window, cx);
6659 let cursor = self.selections.newest::<Point>(cx).head();
6660 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6661 else {
6662 return;
6663 };
6664
6665 let Some(blame) = self.blame.as_ref() else {
6666 return;
6667 };
6668
6669 let row_info = RowInfo {
6670 buffer_id: Some(buffer.remote_id()),
6671 buffer_row: Some(point.row),
6672 ..Default::default()
6673 };
6674 let Some((buffer, blame_entry)) = blame
6675 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6676 .flatten()
6677 else {
6678 return;
6679 };
6680
6681 let anchor = self.selections.newest_anchor().head();
6682 let position = self.to_pixel_point(anchor, &snapshot, window);
6683 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6684 self.show_blame_popover(
6685 buffer,
6686 &blame_entry,
6687 position + last_bounds.origin,
6688 true,
6689 cx,
6690 );
6691 };
6692 }
6693
6694 fn show_blame_popover(
6695 &mut self,
6696 buffer: BufferId,
6697 blame_entry: &BlameEntry,
6698 position: gpui::Point<Pixels>,
6699 ignore_timeout: bool,
6700 cx: &mut Context<Self>,
6701 ) {
6702 if let Some(state) = &mut self.inline_blame_popover {
6703 state.hide_task.take();
6704 } else {
6705 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6706 let blame_entry = blame_entry.clone();
6707 let show_task = cx.spawn(async move |editor, cx| {
6708 if !ignore_timeout {
6709 cx.background_executor()
6710 .timer(std::time::Duration::from_millis(blame_popover_delay))
6711 .await;
6712 }
6713 editor
6714 .update(cx, |editor, cx| {
6715 editor.inline_blame_popover_show_task.take();
6716 let Some(blame) = editor.blame.as_ref() else {
6717 return;
6718 };
6719 let blame = blame.read(cx);
6720 let details = blame.details_for_entry(buffer, &blame_entry);
6721 let markdown = cx.new(|cx| {
6722 Markdown::new(
6723 details
6724 .as_ref()
6725 .map(|message| message.message.clone())
6726 .unwrap_or_default(),
6727 None,
6728 None,
6729 cx,
6730 )
6731 });
6732 editor.inline_blame_popover = Some(InlineBlamePopover {
6733 position,
6734 hide_task: None,
6735 popover_bounds: None,
6736 popover_state: InlineBlamePopoverState {
6737 scroll_handle: ScrollHandle::new(),
6738 commit_message: details,
6739 markdown,
6740 },
6741 keyboard_grace: ignore_timeout,
6742 });
6743 cx.notify();
6744 })
6745 .ok();
6746 });
6747 self.inline_blame_popover_show_task = Some(show_task);
6748 }
6749 }
6750
6751 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6752 self.inline_blame_popover_show_task.take();
6753 if let Some(state) = &mut self.inline_blame_popover {
6754 let hide_task = cx.spawn(async move |editor, cx| {
6755 cx.background_executor()
6756 .timer(std::time::Duration::from_millis(100))
6757 .await;
6758 editor
6759 .update(cx, |editor, cx| {
6760 editor.inline_blame_popover.take();
6761 cx.notify();
6762 })
6763 .ok();
6764 });
6765 state.hide_task = Some(hide_task);
6766 }
6767 }
6768
6769 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6770 if self.pending_rename.is_some() {
6771 return None;
6772 }
6773
6774 let provider = self.semantics_provider.clone()?;
6775 let buffer = self.buffer.read(cx);
6776 let newest_selection = self.selections.newest_anchor().clone();
6777 let cursor_position = newest_selection.head();
6778 let (cursor_buffer, cursor_buffer_position) =
6779 buffer.text_anchor_for_position(cursor_position, cx)?;
6780 let (tail_buffer, tail_buffer_position) =
6781 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6782 if cursor_buffer != tail_buffer {
6783 return None;
6784 }
6785
6786 let snapshot = cursor_buffer.read(cx).snapshot();
6787 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6788 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6789 if start_word_range != end_word_range {
6790 self.document_highlights_task.take();
6791 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6792 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6793 return None;
6794 }
6795
6796 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6797 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6798 cx.background_executor()
6799 .timer(Duration::from_millis(debounce))
6800 .await;
6801
6802 let highlights = if let Some(highlights) = cx
6803 .update(|cx| {
6804 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6805 })
6806 .ok()
6807 .flatten()
6808 {
6809 highlights.await.log_err()
6810 } else {
6811 None
6812 };
6813
6814 if let Some(highlights) = highlights {
6815 this.update(cx, |this, cx| {
6816 if this.pending_rename.is_some() {
6817 return;
6818 }
6819
6820 let buffer = this.buffer.read(cx);
6821 if buffer
6822 .text_anchor_for_position(cursor_position, cx)
6823 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6824 {
6825 return;
6826 }
6827
6828 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6829 let mut write_ranges = Vec::new();
6830 let mut read_ranges = Vec::new();
6831 for highlight in highlights {
6832 let buffer_id = cursor_buffer.read(cx).remote_id();
6833 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6834 {
6835 let start = highlight
6836 .range
6837 .start
6838 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6839 let end = highlight
6840 .range
6841 .end
6842 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6843 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6844 continue;
6845 }
6846
6847 let range = Anchor {
6848 buffer_id: Some(buffer_id),
6849 excerpt_id,
6850 text_anchor: start,
6851 diff_base_anchor: None,
6852 }..Anchor {
6853 buffer_id: Some(buffer_id),
6854 excerpt_id,
6855 text_anchor: end,
6856 diff_base_anchor: None,
6857 };
6858 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6859 write_ranges.push(range);
6860 } else {
6861 read_ranges.push(range);
6862 }
6863 }
6864 }
6865
6866 this.highlight_background::<DocumentHighlightRead>(
6867 &read_ranges,
6868 |theme| theme.colors().editor_document_highlight_read_background,
6869 cx,
6870 );
6871 this.highlight_background::<DocumentHighlightWrite>(
6872 &write_ranges,
6873 |theme| theme.colors().editor_document_highlight_write_background,
6874 cx,
6875 );
6876 cx.notify();
6877 })
6878 .log_err();
6879 }
6880 }));
6881 None
6882 }
6883
6884 fn prepare_highlight_query_from_selection(
6885 &mut self,
6886 cx: &mut Context<Editor>,
6887 ) -> Option<(String, Range<Anchor>)> {
6888 if matches!(self.mode, EditorMode::SingleLine) {
6889 return None;
6890 }
6891 if !EditorSettings::get_global(cx).selection_highlight {
6892 return None;
6893 }
6894 if self.selections.count() != 1 || self.selections.line_mode {
6895 return None;
6896 }
6897 let selection = self.selections.newest::<Point>(cx);
6898 if selection.is_empty() || selection.start.row != selection.end.row {
6899 return None;
6900 }
6901 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6902 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6903 let query = multi_buffer_snapshot
6904 .text_for_range(selection_anchor_range.clone())
6905 .collect::<String>();
6906 if query.trim().is_empty() {
6907 return None;
6908 }
6909 Some((query, selection_anchor_range))
6910 }
6911
6912 fn update_selection_occurrence_highlights(
6913 &mut self,
6914 query_text: String,
6915 query_range: Range<Anchor>,
6916 multi_buffer_range_to_query: Range<Point>,
6917 use_debounce: bool,
6918 window: &mut Window,
6919 cx: &mut Context<Editor>,
6920 ) -> Task<()> {
6921 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6922 cx.spawn_in(window, async move |editor, cx| {
6923 if use_debounce {
6924 cx.background_executor()
6925 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6926 .await;
6927 }
6928 let match_task = cx.background_spawn(async move {
6929 let buffer_ranges = multi_buffer_snapshot
6930 .range_to_buffer_ranges(multi_buffer_range_to_query)
6931 .into_iter()
6932 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6933 let mut match_ranges = Vec::new();
6934 let Ok(regex) = project::search::SearchQuery::text(
6935 query_text.clone(),
6936 false,
6937 false,
6938 false,
6939 Default::default(),
6940 Default::default(),
6941 false,
6942 None,
6943 ) else {
6944 return Vec::default();
6945 };
6946 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6947 match_ranges.extend(
6948 regex
6949 .search(buffer_snapshot, Some(search_range.clone()))
6950 .await
6951 .into_iter()
6952 .filter_map(|match_range| {
6953 let match_start = buffer_snapshot
6954 .anchor_after(search_range.start + match_range.start);
6955 let match_end = buffer_snapshot
6956 .anchor_before(search_range.start + match_range.end);
6957 let match_anchor_range = Anchor::range_in_buffer(
6958 excerpt_id,
6959 buffer_snapshot.remote_id(),
6960 match_start..match_end,
6961 );
6962 (match_anchor_range != query_range).then_some(match_anchor_range)
6963 }),
6964 );
6965 }
6966 match_ranges
6967 });
6968 let match_ranges = match_task.await;
6969 editor
6970 .update_in(cx, |editor, _, cx| {
6971 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6972 if !match_ranges.is_empty() {
6973 editor.highlight_background::<SelectedTextHighlight>(
6974 &match_ranges,
6975 |theme| theme.colors().editor_document_highlight_bracket_background,
6976 cx,
6977 )
6978 }
6979 })
6980 .log_err();
6981 })
6982 }
6983
6984 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6985 struct NewlineFold;
6986 let type_id = std::any::TypeId::of::<NewlineFold>();
6987 if !self.mode.is_single_line() {
6988 return;
6989 }
6990 let snapshot = self.snapshot(window, cx);
6991 if snapshot.buffer_snapshot.max_point().row == 0 {
6992 return;
6993 }
6994 let task = cx.background_spawn(async move {
6995 let new_newlines = snapshot
6996 .buffer_chars_at(0)
6997 .filter_map(|(c, i)| {
6998 if c == '\n' {
6999 Some(
7000 snapshot.buffer_snapshot.anchor_after(i)
7001 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7002 )
7003 } else {
7004 None
7005 }
7006 })
7007 .collect::<Vec<_>>();
7008 let existing_newlines = snapshot
7009 .folds_in_range(0..snapshot.buffer_snapshot.len())
7010 .filter_map(|fold| {
7011 if fold.placeholder.type_tag == Some(type_id) {
7012 Some(fold.range.start..fold.range.end)
7013 } else {
7014 None
7015 }
7016 })
7017 .collect::<Vec<_>>();
7018
7019 (new_newlines, existing_newlines)
7020 });
7021 self.folding_newlines = cx.spawn(async move |this, cx| {
7022 let (new_newlines, existing_newlines) = task.await;
7023 if new_newlines == existing_newlines {
7024 return;
7025 }
7026 let placeholder = FoldPlaceholder {
7027 render: Arc::new(move |_, _, cx| {
7028 div()
7029 .bg(cx.theme().status().hint_background)
7030 .border_b_1()
7031 .size_full()
7032 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7033 .border_color(cx.theme().status().hint)
7034 .child("\\n")
7035 .into_any()
7036 }),
7037 constrain_width: false,
7038 merge_adjacent: false,
7039 type_tag: Some(type_id),
7040 };
7041 let creases = new_newlines
7042 .into_iter()
7043 .map(|range| Crease::simple(range, placeholder.clone()))
7044 .collect();
7045 this.update(cx, |this, cx| {
7046 this.display_map.update(cx, |display_map, cx| {
7047 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7048 display_map.fold(creases, cx);
7049 });
7050 })
7051 .ok();
7052 });
7053 }
7054
7055 fn refresh_selected_text_highlights(
7056 &mut self,
7057 on_buffer_edit: bool,
7058 window: &mut Window,
7059 cx: &mut Context<Editor>,
7060 ) {
7061 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7062 else {
7063 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7064 self.quick_selection_highlight_task.take();
7065 self.debounced_selection_highlight_task.take();
7066 return;
7067 };
7068 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7069 if on_buffer_edit
7070 || self
7071 .quick_selection_highlight_task
7072 .as_ref()
7073 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7074 {
7075 let multi_buffer_visible_start = self
7076 .scroll_manager
7077 .anchor()
7078 .anchor
7079 .to_point(&multi_buffer_snapshot);
7080 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7081 multi_buffer_visible_start
7082 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7083 Bias::Left,
7084 );
7085 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7086 self.quick_selection_highlight_task = Some((
7087 query_range.clone(),
7088 self.update_selection_occurrence_highlights(
7089 query_text.clone(),
7090 query_range.clone(),
7091 multi_buffer_visible_range,
7092 false,
7093 window,
7094 cx,
7095 ),
7096 ));
7097 }
7098 if on_buffer_edit
7099 || self
7100 .debounced_selection_highlight_task
7101 .as_ref()
7102 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7103 {
7104 let multi_buffer_start = multi_buffer_snapshot
7105 .anchor_before(0)
7106 .to_point(&multi_buffer_snapshot);
7107 let multi_buffer_end = multi_buffer_snapshot
7108 .anchor_after(multi_buffer_snapshot.len())
7109 .to_point(&multi_buffer_snapshot);
7110 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7111 self.debounced_selection_highlight_task = Some((
7112 query_range.clone(),
7113 self.update_selection_occurrence_highlights(
7114 query_text,
7115 query_range,
7116 multi_buffer_full_range,
7117 true,
7118 window,
7119 cx,
7120 ),
7121 ));
7122 }
7123 }
7124
7125 pub fn refresh_edit_prediction(
7126 &mut self,
7127 debounce: bool,
7128 user_requested: bool,
7129 window: &mut Window,
7130 cx: &mut Context<Self>,
7131 ) -> Option<()> {
7132 if DisableAiSettings::get_global(cx).disable_ai {
7133 return None;
7134 }
7135
7136 let provider = self.edit_prediction_provider()?;
7137 let cursor = self.selections.newest_anchor().head();
7138 let (buffer, cursor_buffer_position) =
7139 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7140
7141 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7142 self.discard_edit_prediction(false, cx);
7143 return None;
7144 }
7145
7146 if !user_requested
7147 && (!self.should_show_edit_predictions()
7148 || !self.is_focused(window)
7149 || buffer.read(cx).is_empty())
7150 {
7151 self.discard_edit_prediction(false, cx);
7152 return None;
7153 }
7154
7155 self.update_visible_edit_prediction(window, cx);
7156 provider.refresh(
7157 self.project.clone(),
7158 buffer,
7159 cursor_buffer_position,
7160 debounce,
7161 cx,
7162 );
7163 Some(())
7164 }
7165
7166 fn show_edit_predictions_in_menu(&self) -> bool {
7167 match self.edit_prediction_settings {
7168 EditPredictionSettings::Disabled => false,
7169 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7170 }
7171 }
7172
7173 pub fn edit_predictions_enabled(&self) -> bool {
7174 match self.edit_prediction_settings {
7175 EditPredictionSettings::Disabled => false,
7176 EditPredictionSettings::Enabled { .. } => true,
7177 }
7178 }
7179
7180 fn edit_prediction_requires_modifier(&self) -> bool {
7181 match self.edit_prediction_settings {
7182 EditPredictionSettings::Disabled => false,
7183 EditPredictionSettings::Enabled {
7184 preview_requires_modifier,
7185 ..
7186 } => preview_requires_modifier,
7187 }
7188 }
7189
7190 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7191 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7192 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7193 self.discard_edit_prediction(false, cx);
7194 } else {
7195 let selection = self.selections.newest_anchor();
7196 let cursor = selection.head();
7197
7198 if let Some((buffer, cursor_buffer_position)) =
7199 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7200 {
7201 self.edit_prediction_settings =
7202 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7203 }
7204 }
7205 }
7206
7207 fn edit_prediction_settings_at_position(
7208 &self,
7209 buffer: &Entity<Buffer>,
7210 buffer_position: language::Anchor,
7211 cx: &App,
7212 ) -> EditPredictionSettings {
7213 if !self.mode.is_full()
7214 || !self.show_edit_predictions_override.unwrap_or(true)
7215 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7216 {
7217 return EditPredictionSettings::Disabled;
7218 }
7219
7220 let buffer = buffer.read(cx);
7221
7222 let file = buffer.file();
7223
7224 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7225 return EditPredictionSettings::Disabled;
7226 };
7227
7228 let by_provider = matches!(
7229 self.menu_edit_predictions_policy,
7230 MenuEditPredictionsPolicy::ByProvider
7231 );
7232
7233 let show_in_menu = by_provider
7234 && self
7235 .edit_prediction_provider
7236 .as_ref()
7237 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7238
7239 let preview_requires_modifier =
7240 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7241
7242 EditPredictionSettings::Enabled {
7243 show_in_menu,
7244 preview_requires_modifier,
7245 }
7246 }
7247
7248 fn should_show_edit_predictions(&self) -> bool {
7249 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7250 }
7251
7252 pub fn edit_prediction_preview_is_active(&self) -> bool {
7253 matches!(
7254 self.edit_prediction_preview,
7255 EditPredictionPreview::Active { .. }
7256 )
7257 }
7258
7259 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7260 let cursor = self.selections.newest_anchor().head();
7261 if let Some((buffer, cursor_position)) =
7262 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7263 {
7264 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7265 } else {
7266 false
7267 }
7268 }
7269
7270 pub fn supports_minimap(&self, cx: &App) -> bool {
7271 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7272 }
7273
7274 fn edit_predictions_enabled_in_buffer(
7275 &self,
7276 buffer: &Entity<Buffer>,
7277 buffer_position: language::Anchor,
7278 cx: &App,
7279 ) -> bool {
7280 maybe!({
7281 if self.read_only(cx) {
7282 return Some(false);
7283 }
7284 let provider = self.edit_prediction_provider()?;
7285 if !provider.is_enabled(buffer, buffer_position, cx) {
7286 return Some(false);
7287 }
7288 let buffer = buffer.read(cx);
7289 let Some(file) = buffer.file() else {
7290 return Some(true);
7291 };
7292 let settings = all_language_settings(Some(file), cx);
7293 Some(settings.edit_predictions_enabled_for_file(file, cx))
7294 })
7295 .unwrap_or(false)
7296 }
7297
7298 fn cycle_edit_prediction(
7299 &mut self,
7300 direction: Direction,
7301 window: &mut Window,
7302 cx: &mut Context<Self>,
7303 ) -> Option<()> {
7304 let provider = self.edit_prediction_provider()?;
7305 let cursor = self.selections.newest_anchor().head();
7306 let (buffer, cursor_buffer_position) =
7307 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7308 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7309 return None;
7310 }
7311
7312 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7313 self.update_visible_edit_prediction(window, cx);
7314
7315 Some(())
7316 }
7317
7318 pub fn show_edit_prediction(
7319 &mut self,
7320 _: &ShowEditPrediction,
7321 window: &mut Window,
7322 cx: &mut Context<Self>,
7323 ) {
7324 if !self.has_active_edit_prediction() {
7325 self.refresh_edit_prediction(false, true, window, cx);
7326 return;
7327 }
7328
7329 self.update_visible_edit_prediction(window, cx);
7330 }
7331
7332 pub fn display_cursor_names(
7333 &mut self,
7334 _: &DisplayCursorNames,
7335 window: &mut Window,
7336 cx: &mut Context<Self>,
7337 ) {
7338 self.show_cursor_names(window, cx);
7339 }
7340
7341 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7342 self.show_cursor_names = true;
7343 cx.notify();
7344 cx.spawn_in(window, async move |this, cx| {
7345 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7346 this.update(cx, |this, cx| {
7347 this.show_cursor_names = false;
7348 cx.notify()
7349 })
7350 .ok()
7351 })
7352 .detach();
7353 }
7354
7355 pub fn next_edit_prediction(
7356 &mut self,
7357 _: &NextEditPrediction,
7358 window: &mut Window,
7359 cx: &mut Context<Self>,
7360 ) {
7361 if self.has_active_edit_prediction() {
7362 self.cycle_edit_prediction(Direction::Next, window, cx);
7363 } else {
7364 let is_copilot_disabled = self
7365 .refresh_edit_prediction(false, true, window, cx)
7366 .is_none();
7367 if is_copilot_disabled {
7368 cx.propagate();
7369 }
7370 }
7371 }
7372
7373 pub fn previous_edit_prediction(
7374 &mut self,
7375 _: &PreviousEditPrediction,
7376 window: &mut Window,
7377 cx: &mut Context<Self>,
7378 ) {
7379 if self.has_active_edit_prediction() {
7380 self.cycle_edit_prediction(Direction::Prev, window, cx);
7381 } else {
7382 let is_copilot_disabled = self
7383 .refresh_edit_prediction(false, true, window, cx)
7384 .is_none();
7385 if is_copilot_disabled {
7386 cx.propagate();
7387 }
7388 }
7389 }
7390
7391 pub fn accept_edit_prediction(
7392 &mut self,
7393 _: &AcceptEditPrediction,
7394 window: &mut Window,
7395 cx: &mut Context<Self>,
7396 ) {
7397 if self.show_edit_predictions_in_menu() {
7398 self.hide_context_menu(window, cx);
7399 }
7400
7401 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7402 return;
7403 };
7404
7405 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7406
7407 match &active_edit_prediction.completion {
7408 EditPrediction::Move { target, .. } => {
7409 let target = *target;
7410
7411 if let Some(position_map) = &self.last_position_map {
7412 if position_map
7413 .visible_row_range
7414 .contains(&target.to_display_point(&position_map.snapshot).row())
7415 || !self.edit_prediction_requires_modifier()
7416 {
7417 self.unfold_ranges(&[target..target], true, false, cx);
7418 // Note that this is also done in vim's handler of the Tab action.
7419 self.change_selections(
7420 SelectionEffects::scroll(Autoscroll::newest()),
7421 window,
7422 cx,
7423 |selections| {
7424 selections.select_anchor_ranges([target..target]);
7425 },
7426 );
7427 self.clear_row_highlights::<EditPredictionPreview>();
7428
7429 self.edit_prediction_preview
7430 .set_previous_scroll_position(None);
7431 } else {
7432 self.edit_prediction_preview
7433 .set_previous_scroll_position(Some(
7434 position_map.snapshot.scroll_anchor,
7435 ));
7436
7437 self.highlight_rows::<EditPredictionPreview>(
7438 target..target,
7439 cx.theme().colors().editor_highlighted_line_background,
7440 RowHighlightOptions {
7441 autoscroll: true,
7442 ..Default::default()
7443 },
7444 cx,
7445 );
7446 self.request_autoscroll(Autoscroll::fit(), cx);
7447 }
7448 }
7449 }
7450 EditPrediction::Edit { edits, .. } => {
7451 if let Some(provider) = self.edit_prediction_provider() {
7452 provider.accept(cx);
7453 }
7454
7455 // Store the transaction ID and selections before applying the edit
7456 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7457
7458 let snapshot = self.buffer.read(cx).snapshot(cx);
7459 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7460
7461 self.buffer.update(cx, |buffer, cx| {
7462 buffer.edit(edits.iter().cloned(), None, cx)
7463 });
7464
7465 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7466 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7467 });
7468
7469 let selections = self.selections.disjoint_anchors();
7470 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7471 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7472 if has_new_transaction {
7473 self.selection_history
7474 .insert_transaction(transaction_id_now, selections);
7475 }
7476 }
7477
7478 self.update_visible_edit_prediction(window, cx);
7479 if self.active_edit_prediction.is_none() {
7480 self.refresh_edit_prediction(true, true, window, cx);
7481 }
7482
7483 cx.notify();
7484 }
7485 }
7486
7487 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7488 }
7489
7490 pub fn accept_partial_edit_prediction(
7491 &mut self,
7492 _: &AcceptPartialEditPrediction,
7493 window: &mut Window,
7494 cx: &mut Context<Self>,
7495 ) {
7496 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7497 return;
7498 };
7499 if self.selections.count() != 1 {
7500 return;
7501 }
7502
7503 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7504
7505 match &active_edit_prediction.completion {
7506 EditPrediction::Move { target, .. } => {
7507 let target = *target;
7508 self.change_selections(
7509 SelectionEffects::scroll(Autoscroll::newest()),
7510 window,
7511 cx,
7512 |selections| {
7513 selections.select_anchor_ranges([target..target]);
7514 },
7515 );
7516 }
7517 EditPrediction::Edit { edits, .. } => {
7518 // Find an insertion that starts at the cursor position.
7519 let snapshot = self.buffer.read(cx).snapshot(cx);
7520 let cursor_offset = self.selections.newest::<usize>(cx).head();
7521 let insertion = edits.iter().find_map(|(range, text)| {
7522 let range = range.to_offset(&snapshot);
7523 if range.is_empty() && range.start == cursor_offset {
7524 Some(text)
7525 } else {
7526 None
7527 }
7528 });
7529
7530 if let Some(text) = insertion {
7531 let mut partial_completion = text
7532 .chars()
7533 .by_ref()
7534 .take_while(|c| c.is_alphabetic())
7535 .collect::<String>();
7536 if partial_completion.is_empty() {
7537 partial_completion = text
7538 .chars()
7539 .by_ref()
7540 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7541 .collect::<String>();
7542 }
7543
7544 cx.emit(EditorEvent::InputHandled {
7545 utf16_range_to_replace: None,
7546 text: partial_completion.clone().into(),
7547 });
7548
7549 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7550
7551 self.refresh_edit_prediction(true, true, window, cx);
7552 cx.notify();
7553 } else {
7554 self.accept_edit_prediction(&Default::default(), window, cx);
7555 }
7556 }
7557 }
7558 }
7559
7560 fn discard_edit_prediction(
7561 &mut self,
7562 should_report_edit_prediction_event: bool,
7563 cx: &mut Context<Self>,
7564 ) -> bool {
7565 if should_report_edit_prediction_event {
7566 let completion_id = self
7567 .active_edit_prediction
7568 .as_ref()
7569 .and_then(|active_completion| active_completion.completion_id.clone());
7570
7571 self.report_edit_prediction_event(completion_id, false, cx);
7572 }
7573
7574 if let Some(provider) = self.edit_prediction_provider() {
7575 provider.discard(cx);
7576 }
7577
7578 self.take_active_edit_prediction(cx)
7579 }
7580
7581 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7582 let Some(provider) = self.edit_prediction_provider() else {
7583 return;
7584 };
7585
7586 let Some((_, buffer, _)) = self
7587 .buffer
7588 .read(cx)
7589 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7590 else {
7591 return;
7592 };
7593
7594 let extension = buffer
7595 .read(cx)
7596 .file()
7597 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7598
7599 let event_type = match accepted {
7600 true => "Edit Prediction Accepted",
7601 false => "Edit Prediction Discarded",
7602 };
7603 telemetry::event!(
7604 event_type,
7605 provider = provider.name(),
7606 prediction_id = id,
7607 suggestion_accepted = accepted,
7608 file_extension = extension,
7609 );
7610 }
7611
7612 pub fn has_active_edit_prediction(&self) -> bool {
7613 self.active_edit_prediction.is_some()
7614 }
7615
7616 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7617 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7618 return false;
7619 };
7620
7621 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7622 self.clear_highlights::<EditPredictionHighlight>(cx);
7623 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7624 true
7625 }
7626
7627 /// Returns true when we're displaying the edit prediction popover below the cursor
7628 /// like we are not previewing and the LSP autocomplete menu is visible
7629 /// or we are in `when_holding_modifier` mode.
7630 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7631 if self.edit_prediction_preview_is_active()
7632 || !self.show_edit_predictions_in_menu()
7633 || !self.edit_predictions_enabled()
7634 {
7635 return false;
7636 }
7637
7638 if self.has_visible_completions_menu() {
7639 return true;
7640 }
7641
7642 has_completion && self.edit_prediction_requires_modifier()
7643 }
7644
7645 fn handle_modifiers_changed(
7646 &mut self,
7647 modifiers: Modifiers,
7648 position_map: &PositionMap,
7649 window: &mut Window,
7650 cx: &mut Context<Self>,
7651 ) {
7652 if self.show_edit_predictions_in_menu() {
7653 self.update_edit_prediction_preview(&modifiers, window, cx);
7654 }
7655
7656 self.update_selection_mode(&modifiers, position_map, window, cx);
7657
7658 let mouse_position = window.mouse_position();
7659 if !position_map.text_hitbox.is_hovered(window) {
7660 return;
7661 }
7662
7663 self.update_hovered_link(
7664 position_map.point_for_position(mouse_position),
7665 &position_map.snapshot,
7666 modifiers,
7667 window,
7668 cx,
7669 )
7670 }
7671
7672 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7673 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7674 if invert {
7675 match multi_cursor_setting {
7676 MultiCursorModifier::Alt => modifiers.alt,
7677 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7678 }
7679 } else {
7680 match multi_cursor_setting {
7681 MultiCursorModifier::Alt => modifiers.secondary(),
7682 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7683 }
7684 }
7685 }
7686
7687 fn columnar_selection_mode(
7688 modifiers: &Modifiers,
7689 cx: &mut Context<Self>,
7690 ) -> Option<ColumnarMode> {
7691 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7692 if Self::multi_cursor_modifier(false, modifiers, cx) {
7693 Some(ColumnarMode::FromMouse)
7694 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7695 Some(ColumnarMode::FromSelection)
7696 } else {
7697 None
7698 }
7699 } else {
7700 None
7701 }
7702 }
7703
7704 fn update_selection_mode(
7705 &mut self,
7706 modifiers: &Modifiers,
7707 position_map: &PositionMap,
7708 window: &mut Window,
7709 cx: &mut Context<Self>,
7710 ) {
7711 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7712 return;
7713 };
7714 if self.selections.pending.is_none() {
7715 return;
7716 }
7717
7718 let mouse_position = window.mouse_position();
7719 let point_for_position = position_map.point_for_position(mouse_position);
7720 let position = point_for_position.previous_valid;
7721
7722 self.select(
7723 SelectPhase::BeginColumnar {
7724 position,
7725 reset: false,
7726 mode,
7727 goal_column: point_for_position.exact_unclipped.column(),
7728 },
7729 window,
7730 cx,
7731 );
7732 }
7733
7734 fn update_edit_prediction_preview(
7735 &mut self,
7736 modifiers: &Modifiers,
7737 window: &mut Window,
7738 cx: &mut Context<Self>,
7739 ) {
7740 let mut modifiers_held = false;
7741 if let Some(accept_keystroke) = self
7742 .accept_edit_prediction_keybind(false, window, cx)
7743 .keystroke()
7744 {
7745 modifiers_held = modifiers_held
7746 || (accept_keystroke.modifiers() == modifiers
7747 && accept_keystroke.modifiers().modified());
7748 };
7749 if let Some(accept_partial_keystroke) = self
7750 .accept_edit_prediction_keybind(true, window, cx)
7751 .keystroke()
7752 {
7753 modifiers_held = modifiers_held
7754 || (accept_partial_keystroke.modifiers() == modifiers
7755 && accept_partial_keystroke.modifiers().modified());
7756 }
7757
7758 if modifiers_held {
7759 if matches!(
7760 self.edit_prediction_preview,
7761 EditPredictionPreview::Inactive { .. }
7762 ) {
7763 self.edit_prediction_preview = EditPredictionPreview::Active {
7764 previous_scroll_position: None,
7765 since: Instant::now(),
7766 };
7767
7768 self.update_visible_edit_prediction(window, cx);
7769 cx.notify();
7770 }
7771 } else if let EditPredictionPreview::Active {
7772 previous_scroll_position,
7773 since,
7774 } = self.edit_prediction_preview
7775 {
7776 if let (Some(previous_scroll_position), Some(position_map)) =
7777 (previous_scroll_position, self.last_position_map.as_ref())
7778 {
7779 self.set_scroll_position(
7780 previous_scroll_position
7781 .scroll_position(&position_map.snapshot.display_snapshot),
7782 window,
7783 cx,
7784 );
7785 }
7786
7787 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7788 released_too_fast: since.elapsed() < Duration::from_millis(200),
7789 };
7790 self.clear_row_highlights::<EditPredictionPreview>();
7791 self.update_visible_edit_prediction(window, cx);
7792 cx.notify();
7793 }
7794 }
7795
7796 fn update_visible_edit_prediction(
7797 &mut self,
7798 _window: &mut Window,
7799 cx: &mut Context<Self>,
7800 ) -> Option<()> {
7801 if DisableAiSettings::get_global(cx).disable_ai {
7802 return None;
7803 }
7804
7805 if self.ime_transaction.is_some() {
7806 self.discard_edit_prediction(false, cx);
7807 return None;
7808 }
7809
7810 let selection = self.selections.newest_anchor();
7811 let cursor = selection.head();
7812 let multibuffer = self.buffer.read(cx).snapshot(cx);
7813 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7814 let excerpt_id = cursor.excerpt_id;
7815
7816 let show_in_menu = self.show_edit_predictions_in_menu();
7817 let completions_menu_has_precedence = !show_in_menu
7818 && (self.context_menu.borrow().is_some()
7819 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7820
7821 if completions_menu_has_precedence
7822 || !offset_selection.is_empty()
7823 || self
7824 .active_edit_prediction
7825 .as_ref()
7826 .is_some_and(|completion| {
7827 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7828 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7829 !invalidation_range.contains(&offset_selection.head())
7830 })
7831 {
7832 self.discard_edit_prediction(false, cx);
7833 return None;
7834 }
7835
7836 self.take_active_edit_prediction(cx);
7837 let Some(provider) = self.edit_prediction_provider() else {
7838 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7839 return None;
7840 };
7841
7842 let (buffer, cursor_buffer_position) =
7843 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7844
7845 self.edit_prediction_settings =
7846 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7847
7848 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7849 self.discard_edit_prediction(false, cx);
7850 return None;
7851 };
7852
7853 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7854
7855 if self.edit_prediction_indent_conflict {
7856 let cursor_point = cursor.to_point(&multibuffer);
7857
7858 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7859
7860 if let Some((_, indent)) = indents.iter().next()
7861 && indent.len == cursor_point.column
7862 {
7863 self.edit_prediction_indent_conflict = false;
7864 }
7865 }
7866
7867 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7868 let edits = edit_prediction
7869 .edits
7870 .into_iter()
7871 .flat_map(|(range, new_text)| {
7872 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7873 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7874 Some((start..end, new_text))
7875 })
7876 .collect::<Vec<_>>();
7877 if edits.is_empty() {
7878 return None;
7879 }
7880
7881 let first_edit_start = edits.first().unwrap().0.start;
7882 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7883 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7884
7885 let last_edit_end = edits.last().unwrap().0.end;
7886 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7887 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7888
7889 let cursor_row = cursor.to_point(&multibuffer).row;
7890
7891 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7892
7893 let mut inlay_ids = Vec::new();
7894 let invalidation_row_range;
7895 let move_invalidation_row_range = if cursor_row < edit_start_row {
7896 Some(cursor_row..edit_end_row)
7897 } else if cursor_row > edit_end_row {
7898 Some(edit_start_row..cursor_row)
7899 } else {
7900 None
7901 };
7902 let supports_jump = self
7903 .edit_prediction_provider
7904 .as_ref()
7905 .map(|provider| provider.provider.supports_jump_to_edit())
7906 .unwrap_or(true);
7907
7908 let is_move = supports_jump
7909 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7910 let completion = if is_move {
7911 invalidation_row_range =
7912 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7913 let target = first_edit_start;
7914 EditPrediction::Move { target, snapshot }
7915 } else {
7916 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7917 && !self.edit_predictions_hidden_for_vim_mode;
7918
7919 if show_completions_in_buffer {
7920 if edits
7921 .iter()
7922 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7923 {
7924 let mut inlays = Vec::new();
7925 for (range, new_text) in &edits {
7926 let inlay = Inlay::edit_prediction(
7927 post_inc(&mut self.next_inlay_id),
7928 range.start,
7929 new_text.as_str(),
7930 );
7931 inlay_ids.push(inlay.id);
7932 inlays.push(inlay);
7933 }
7934
7935 self.splice_inlays(&[], inlays, cx);
7936 } else {
7937 let background_color = cx.theme().status().deleted_background;
7938 self.highlight_text::<EditPredictionHighlight>(
7939 edits.iter().map(|(range, _)| range.clone()).collect(),
7940 HighlightStyle {
7941 background_color: Some(background_color),
7942 ..Default::default()
7943 },
7944 cx,
7945 );
7946 }
7947 }
7948
7949 invalidation_row_range = edit_start_row..edit_end_row;
7950
7951 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7952 if provider.show_tab_accept_marker() {
7953 EditDisplayMode::TabAccept
7954 } else {
7955 EditDisplayMode::Inline
7956 }
7957 } else {
7958 EditDisplayMode::DiffPopover
7959 };
7960
7961 EditPrediction::Edit {
7962 edits,
7963 edit_preview: edit_prediction.edit_preview,
7964 display_mode,
7965 snapshot,
7966 }
7967 };
7968
7969 let invalidation_range = multibuffer
7970 .anchor_before(Point::new(invalidation_row_range.start, 0))
7971 ..multibuffer.anchor_after(Point::new(
7972 invalidation_row_range.end,
7973 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7974 ));
7975
7976 self.stale_edit_prediction_in_menu = None;
7977 self.active_edit_prediction = Some(EditPredictionState {
7978 inlay_ids,
7979 completion,
7980 completion_id: edit_prediction.id,
7981 invalidation_range,
7982 });
7983
7984 cx.notify();
7985
7986 Some(())
7987 }
7988
7989 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7990 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7991 }
7992
7993 fn clear_tasks(&mut self) {
7994 self.tasks.clear()
7995 }
7996
7997 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7998 if self.tasks.insert(key, value).is_some() {
7999 // This case should hopefully be rare, but just in case...
8000 log::error!(
8001 "multiple different run targets found on a single line, only the last target will be rendered"
8002 )
8003 }
8004 }
8005
8006 /// Get all display points of breakpoints that will be rendered within editor
8007 ///
8008 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8009 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8010 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8011 fn active_breakpoints(
8012 &self,
8013 range: Range<DisplayRow>,
8014 window: &mut Window,
8015 cx: &mut Context<Self>,
8016 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8017 let mut breakpoint_display_points = HashMap::default();
8018
8019 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8020 return breakpoint_display_points;
8021 };
8022
8023 let snapshot = self.snapshot(window, cx);
8024
8025 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8026 let Some(project) = self.project() else {
8027 return breakpoint_display_points;
8028 };
8029
8030 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8031 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8032
8033 for (buffer_snapshot, range, excerpt_id) in
8034 multi_buffer_snapshot.range_to_buffer_ranges(range)
8035 {
8036 let Some(buffer) = project
8037 .read(cx)
8038 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8039 else {
8040 continue;
8041 };
8042 let breakpoints = breakpoint_store.read(cx).breakpoints(
8043 &buffer,
8044 Some(
8045 buffer_snapshot.anchor_before(range.start)
8046 ..buffer_snapshot.anchor_after(range.end),
8047 ),
8048 buffer_snapshot,
8049 cx,
8050 );
8051 for (breakpoint, state) in breakpoints {
8052 let multi_buffer_anchor =
8053 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8054 let position = multi_buffer_anchor
8055 .to_point(multi_buffer_snapshot)
8056 .to_display_point(&snapshot);
8057
8058 breakpoint_display_points.insert(
8059 position.row(),
8060 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8061 );
8062 }
8063 }
8064
8065 breakpoint_display_points
8066 }
8067
8068 fn breakpoint_context_menu(
8069 &self,
8070 anchor: Anchor,
8071 window: &mut Window,
8072 cx: &mut Context<Self>,
8073 ) -> Entity<ui::ContextMenu> {
8074 let weak_editor = cx.weak_entity();
8075 let focus_handle = self.focus_handle(cx);
8076
8077 let row = self
8078 .buffer
8079 .read(cx)
8080 .snapshot(cx)
8081 .summary_for_anchor::<Point>(&anchor)
8082 .row;
8083
8084 let breakpoint = self
8085 .breakpoint_at_row(row, window, cx)
8086 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8087
8088 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8089 "Edit Log Breakpoint"
8090 } else {
8091 "Set Log Breakpoint"
8092 };
8093
8094 let condition_breakpoint_msg = if breakpoint
8095 .as_ref()
8096 .is_some_and(|bp| bp.1.condition.is_some())
8097 {
8098 "Edit Condition Breakpoint"
8099 } else {
8100 "Set Condition Breakpoint"
8101 };
8102
8103 let hit_condition_breakpoint_msg = if breakpoint
8104 .as_ref()
8105 .is_some_and(|bp| bp.1.hit_condition.is_some())
8106 {
8107 "Edit Hit Condition Breakpoint"
8108 } else {
8109 "Set Hit Condition Breakpoint"
8110 };
8111
8112 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8113 "Unset Breakpoint"
8114 } else {
8115 "Set Breakpoint"
8116 };
8117
8118 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8119
8120 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8121 BreakpointState::Enabled => Some("Disable"),
8122 BreakpointState::Disabled => Some("Enable"),
8123 });
8124
8125 let (anchor, breakpoint) =
8126 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8127
8128 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8129 menu.on_blur_subscription(Subscription::new(|| {}))
8130 .context(focus_handle)
8131 .when(run_to_cursor, |this| {
8132 let weak_editor = weak_editor.clone();
8133 this.entry("Run to cursor", None, move |window, cx| {
8134 weak_editor
8135 .update(cx, |editor, cx| {
8136 editor.change_selections(
8137 SelectionEffects::no_scroll(),
8138 window,
8139 cx,
8140 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8141 );
8142 })
8143 .ok();
8144
8145 window.dispatch_action(Box::new(RunToCursor), cx);
8146 })
8147 .separator()
8148 })
8149 .when_some(toggle_state_msg, |this, msg| {
8150 this.entry(msg, None, {
8151 let weak_editor = weak_editor.clone();
8152 let breakpoint = breakpoint.clone();
8153 move |_window, cx| {
8154 weak_editor
8155 .update(cx, |this, cx| {
8156 this.edit_breakpoint_at_anchor(
8157 anchor,
8158 breakpoint.as_ref().clone(),
8159 BreakpointEditAction::InvertState,
8160 cx,
8161 );
8162 })
8163 .log_err();
8164 }
8165 })
8166 })
8167 .entry(set_breakpoint_msg, None, {
8168 let weak_editor = weak_editor.clone();
8169 let breakpoint = breakpoint.clone();
8170 move |_window, cx| {
8171 weak_editor
8172 .update(cx, |this, cx| {
8173 this.edit_breakpoint_at_anchor(
8174 anchor,
8175 breakpoint.as_ref().clone(),
8176 BreakpointEditAction::Toggle,
8177 cx,
8178 );
8179 })
8180 .log_err();
8181 }
8182 })
8183 .entry(log_breakpoint_msg, None, {
8184 let breakpoint = breakpoint.clone();
8185 let weak_editor = weak_editor.clone();
8186 move |window, cx| {
8187 weak_editor
8188 .update(cx, |this, cx| {
8189 this.add_edit_breakpoint_block(
8190 anchor,
8191 breakpoint.as_ref(),
8192 BreakpointPromptEditAction::Log,
8193 window,
8194 cx,
8195 );
8196 })
8197 .log_err();
8198 }
8199 })
8200 .entry(condition_breakpoint_msg, None, {
8201 let breakpoint = breakpoint.clone();
8202 let weak_editor = weak_editor.clone();
8203 move |window, cx| {
8204 weak_editor
8205 .update(cx, |this, cx| {
8206 this.add_edit_breakpoint_block(
8207 anchor,
8208 breakpoint.as_ref(),
8209 BreakpointPromptEditAction::Condition,
8210 window,
8211 cx,
8212 );
8213 })
8214 .log_err();
8215 }
8216 })
8217 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8218 weak_editor
8219 .update(cx, |this, cx| {
8220 this.add_edit_breakpoint_block(
8221 anchor,
8222 breakpoint.as_ref(),
8223 BreakpointPromptEditAction::HitCondition,
8224 window,
8225 cx,
8226 );
8227 })
8228 .log_err();
8229 })
8230 })
8231 }
8232
8233 fn render_breakpoint(
8234 &self,
8235 position: Anchor,
8236 row: DisplayRow,
8237 breakpoint: &Breakpoint,
8238 state: Option<BreakpointSessionState>,
8239 cx: &mut Context<Self>,
8240 ) -> IconButton {
8241 let is_rejected = state.is_some_and(|s| !s.verified);
8242 // Is it a breakpoint that shows up when hovering over gutter?
8243 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8244 (false, false),
8245 |PhantomBreakpointIndicator {
8246 is_active,
8247 display_row,
8248 collides_with_existing_breakpoint,
8249 }| {
8250 (
8251 is_active && display_row == row,
8252 collides_with_existing_breakpoint,
8253 )
8254 },
8255 );
8256
8257 let (color, icon) = {
8258 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8259 (false, false) => ui::IconName::DebugBreakpoint,
8260 (true, false) => ui::IconName::DebugLogBreakpoint,
8261 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8262 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8263 };
8264
8265 let color = if is_phantom {
8266 Color::Hint
8267 } else if is_rejected {
8268 Color::Disabled
8269 } else {
8270 Color::Debugger
8271 };
8272
8273 (color, icon)
8274 };
8275
8276 let breakpoint = Arc::from(breakpoint.clone());
8277
8278 let alt_as_text = gpui::Keystroke {
8279 modifiers: Modifiers::secondary_key(),
8280 ..Default::default()
8281 };
8282 let primary_action_text = if breakpoint.is_disabled() {
8283 "Enable breakpoint"
8284 } else if is_phantom && !collides_with_existing {
8285 "Set breakpoint"
8286 } else {
8287 "Unset breakpoint"
8288 };
8289 let focus_handle = self.focus_handle.clone();
8290
8291 let meta = if is_rejected {
8292 SharedString::from("No executable code is associated with this line.")
8293 } else if collides_with_existing && !breakpoint.is_disabled() {
8294 SharedString::from(format!(
8295 "{alt_as_text}-click to disable,\nright-click for more options."
8296 ))
8297 } else {
8298 SharedString::from("Right-click for more options.")
8299 };
8300 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8301 .icon_size(IconSize::XSmall)
8302 .size(ui::ButtonSize::None)
8303 .when(is_rejected, |this| {
8304 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8305 })
8306 .icon_color(color)
8307 .style(ButtonStyle::Transparent)
8308 .on_click(cx.listener({
8309 move |editor, event: &ClickEvent, window, cx| {
8310 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8311 BreakpointEditAction::InvertState
8312 } else {
8313 BreakpointEditAction::Toggle
8314 };
8315
8316 window.focus(&editor.focus_handle(cx));
8317 editor.edit_breakpoint_at_anchor(
8318 position,
8319 breakpoint.as_ref().clone(),
8320 edit_action,
8321 cx,
8322 );
8323 }
8324 }))
8325 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8326 editor.set_breakpoint_context_menu(
8327 row,
8328 Some(position),
8329 event.position(),
8330 window,
8331 cx,
8332 );
8333 }))
8334 .tooltip(move |window, cx| {
8335 Tooltip::with_meta_in(
8336 primary_action_text,
8337 Some(&ToggleBreakpoint),
8338 meta.clone(),
8339 &focus_handle,
8340 window,
8341 cx,
8342 )
8343 })
8344 }
8345
8346 fn build_tasks_context(
8347 project: &Entity<Project>,
8348 buffer: &Entity<Buffer>,
8349 buffer_row: u32,
8350 tasks: &Arc<RunnableTasks>,
8351 cx: &mut Context<Self>,
8352 ) -> Task<Option<task::TaskContext>> {
8353 let position = Point::new(buffer_row, tasks.column);
8354 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8355 let location = Location {
8356 buffer: buffer.clone(),
8357 range: range_start..range_start,
8358 };
8359 // Fill in the environmental variables from the tree-sitter captures
8360 let mut captured_task_variables = TaskVariables::default();
8361 for (capture_name, value) in tasks.extra_variables.clone() {
8362 captured_task_variables.insert(
8363 task::VariableName::Custom(capture_name.into()),
8364 value.clone(),
8365 );
8366 }
8367 project.update(cx, |project, cx| {
8368 project.task_store().update(cx, |task_store, cx| {
8369 task_store.task_context_for_location(captured_task_variables, location, cx)
8370 })
8371 })
8372 }
8373
8374 pub fn spawn_nearest_task(
8375 &mut self,
8376 action: &SpawnNearestTask,
8377 window: &mut Window,
8378 cx: &mut Context<Self>,
8379 ) {
8380 let Some((workspace, _)) = self.workspace.clone() else {
8381 return;
8382 };
8383 let Some(project) = self.project.clone() else {
8384 return;
8385 };
8386
8387 // Try to find a closest, enclosing node using tree-sitter that has a task
8388 let Some((buffer, buffer_row, tasks)) = self
8389 .find_enclosing_node_task(cx)
8390 // Or find the task that's closest in row-distance.
8391 .or_else(|| self.find_closest_task(cx))
8392 else {
8393 return;
8394 };
8395
8396 let reveal_strategy = action.reveal;
8397 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8398 cx.spawn_in(window, async move |_, cx| {
8399 let context = task_context.await?;
8400 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8401
8402 let resolved = &mut resolved_task.resolved;
8403 resolved.reveal = reveal_strategy;
8404
8405 workspace
8406 .update_in(cx, |workspace, window, cx| {
8407 workspace.schedule_resolved_task(
8408 task_source_kind,
8409 resolved_task,
8410 false,
8411 window,
8412 cx,
8413 );
8414 })
8415 .ok()
8416 })
8417 .detach();
8418 }
8419
8420 fn find_closest_task(
8421 &mut self,
8422 cx: &mut Context<Self>,
8423 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8424 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8425
8426 let ((buffer_id, row), tasks) = self
8427 .tasks
8428 .iter()
8429 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8430
8431 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8432 let tasks = Arc::new(tasks.to_owned());
8433 Some((buffer, *row, tasks))
8434 }
8435
8436 fn find_enclosing_node_task(
8437 &mut self,
8438 cx: &mut Context<Self>,
8439 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8440 let snapshot = self.buffer.read(cx).snapshot(cx);
8441 let offset = self.selections.newest::<usize>(cx).head();
8442 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8443 let buffer_id = excerpt.buffer().remote_id();
8444
8445 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8446 let mut cursor = layer.node().walk();
8447
8448 while cursor.goto_first_child_for_byte(offset).is_some() {
8449 if cursor.node().end_byte() == offset {
8450 cursor.goto_next_sibling();
8451 }
8452 }
8453
8454 // Ascend to the smallest ancestor that contains the range and has a task.
8455 loop {
8456 let node = cursor.node();
8457 let node_range = node.byte_range();
8458 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8459
8460 // Check if this node contains our offset
8461 if node_range.start <= offset && node_range.end >= offset {
8462 // If it contains offset, check for task
8463 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8464 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8465 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8466 }
8467 }
8468
8469 if !cursor.goto_parent() {
8470 break;
8471 }
8472 }
8473 None
8474 }
8475
8476 fn render_run_indicator(
8477 &self,
8478 _style: &EditorStyle,
8479 is_active: bool,
8480 row: DisplayRow,
8481 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8482 cx: &mut Context<Self>,
8483 ) -> IconButton {
8484 let color = Color::Muted;
8485 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8486
8487 IconButton::new(
8488 ("run_indicator", row.0 as usize),
8489 ui::IconName::PlayOutlined,
8490 )
8491 .shape(ui::IconButtonShape::Square)
8492 .icon_size(IconSize::XSmall)
8493 .icon_color(color)
8494 .toggle_state(is_active)
8495 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8496 let quick_launch = match e {
8497 ClickEvent::Keyboard(_) => true,
8498 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8499 };
8500
8501 window.focus(&editor.focus_handle(cx));
8502 editor.toggle_code_actions(
8503 &ToggleCodeActions {
8504 deployed_from: Some(CodeActionSource::RunMenu(row)),
8505 quick_launch,
8506 },
8507 window,
8508 cx,
8509 );
8510 }))
8511 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8512 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8513 }))
8514 }
8515
8516 pub fn context_menu_visible(&self) -> bool {
8517 !self.edit_prediction_preview_is_active()
8518 && self
8519 .context_menu
8520 .borrow()
8521 .as_ref()
8522 .is_some_and(|menu| menu.visible())
8523 }
8524
8525 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8526 self.context_menu
8527 .borrow()
8528 .as_ref()
8529 .map(|menu| menu.origin())
8530 }
8531
8532 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8533 self.context_menu_options = Some(options);
8534 }
8535
8536 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8537 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8538
8539 fn render_edit_prediction_popover(
8540 &mut self,
8541 text_bounds: &Bounds<Pixels>,
8542 content_origin: gpui::Point<Pixels>,
8543 right_margin: Pixels,
8544 editor_snapshot: &EditorSnapshot,
8545 visible_row_range: Range<DisplayRow>,
8546 scroll_top: f32,
8547 scroll_bottom: f32,
8548 line_layouts: &[LineWithInvisibles],
8549 line_height: Pixels,
8550 scroll_pixel_position: gpui::Point<Pixels>,
8551 newest_selection_head: Option<DisplayPoint>,
8552 editor_width: Pixels,
8553 style: &EditorStyle,
8554 window: &mut Window,
8555 cx: &mut App,
8556 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8557 if self.mode().is_minimap() {
8558 return None;
8559 }
8560 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8561
8562 if self.edit_prediction_visible_in_cursor_popover(true) {
8563 return None;
8564 }
8565
8566 match &active_edit_prediction.completion {
8567 EditPrediction::Move { target, .. } => {
8568 let target_display_point = target.to_display_point(editor_snapshot);
8569
8570 if self.edit_prediction_requires_modifier() {
8571 if !self.edit_prediction_preview_is_active() {
8572 return None;
8573 }
8574
8575 self.render_edit_prediction_modifier_jump_popover(
8576 text_bounds,
8577 content_origin,
8578 visible_row_range,
8579 line_layouts,
8580 line_height,
8581 scroll_pixel_position,
8582 newest_selection_head,
8583 target_display_point,
8584 window,
8585 cx,
8586 )
8587 } else {
8588 self.render_edit_prediction_eager_jump_popover(
8589 text_bounds,
8590 content_origin,
8591 editor_snapshot,
8592 visible_row_range,
8593 scroll_top,
8594 scroll_bottom,
8595 line_height,
8596 scroll_pixel_position,
8597 target_display_point,
8598 editor_width,
8599 window,
8600 cx,
8601 )
8602 }
8603 }
8604 EditPrediction::Edit {
8605 display_mode: EditDisplayMode::Inline,
8606 ..
8607 } => None,
8608 EditPrediction::Edit {
8609 display_mode: EditDisplayMode::TabAccept,
8610 edits,
8611 ..
8612 } => {
8613 let range = &edits.first()?.0;
8614 let target_display_point = range.end.to_display_point(editor_snapshot);
8615
8616 self.render_edit_prediction_end_of_line_popover(
8617 "Accept",
8618 editor_snapshot,
8619 visible_row_range,
8620 target_display_point,
8621 line_height,
8622 scroll_pixel_position,
8623 content_origin,
8624 editor_width,
8625 window,
8626 cx,
8627 )
8628 }
8629 EditPrediction::Edit {
8630 edits,
8631 edit_preview,
8632 display_mode: EditDisplayMode::DiffPopover,
8633 snapshot,
8634 } => self.render_edit_prediction_diff_popover(
8635 text_bounds,
8636 content_origin,
8637 right_margin,
8638 editor_snapshot,
8639 visible_row_range,
8640 line_layouts,
8641 line_height,
8642 scroll_pixel_position,
8643 newest_selection_head,
8644 editor_width,
8645 style,
8646 edits,
8647 edit_preview,
8648 snapshot,
8649 window,
8650 cx,
8651 ),
8652 }
8653 }
8654
8655 fn render_edit_prediction_modifier_jump_popover(
8656 &mut self,
8657 text_bounds: &Bounds<Pixels>,
8658 content_origin: gpui::Point<Pixels>,
8659 visible_row_range: Range<DisplayRow>,
8660 line_layouts: &[LineWithInvisibles],
8661 line_height: Pixels,
8662 scroll_pixel_position: gpui::Point<Pixels>,
8663 newest_selection_head: Option<DisplayPoint>,
8664 target_display_point: DisplayPoint,
8665 window: &mut Window,
8666 cx: &mut App,
8667 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8668 let scrolled_content_origin =
8669 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8670
8671 const SCROLL_PADDING_Y: Pixels = px(12.);
8672
8673 if target_display_point.row() < visible_row_range.start {
8674 return self.render_edit_prediction_scroll_popover(
8675 |_| SCROLL_PADDING_Y,
8676 IconName::ArrowUp,
8677 visible_row_range,
8678 line_layouts,
8679 newest_selection_head,
8680 scrolled_content_origin,
8681 window,
8682 cx,
8683 );
8684 } else if target_display_point.row() >= visible_row_range.end {
8685 return self.render_edit_prediction_scroll_popover(
8686 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8687 IconName::ArrowDown,
8688 visible_row_range,
8689 line_layouts,
8690 newest_selection_head,
8691 scrolled_content_origin,
8692 window,
8693 cx,
8694 );
8695 }
8696
8697 const POLE_WIDTH: Pixels = px(2.);
8698
8699 let line_layout =
8700 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8701 let target_column = target_display_point.column() as usize;
8702
8703 let target_x = line_layout.x_for_index(target_column);
8704 let target_y =
8705 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8706
8707 let flag_on_right = target_x < text_bounds.size.width / 2.;
8708
8709 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8710 border_color.l += 0.001;
8711
8712 let mut element = v_flex()
8713 .items_end()
8714 .when(flag_on_right, |el| el.items_start())
8715 .child(if flag_on_right {
8716 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8717 .rounded_bl(px(0.))
8718 .rounded_tl(px(0.))
8719 .border_l_2()
8720 .border_color(border_color)
8721 } else {
8722 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8723 .rounded_br(px(0.))
8724 .rounded_tr(px(0.))
8725 .border_r_2()
8726 .border_color(border_color)
8727 })
8728 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8729 .into_any();
8730
8731 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8732
8733 let mut origin = scrolled_content_origin + point(target_x, target_y)
8734 - point(
8735 if flag_on_right {
8736 POLE_WIDTH
8737 } else {
8738 size.width - POLE_WIDTH
8739 },
8740 size.height - line_height,
8741 );
8742
8743 origin.x = origin.x.max(content_origin.x);
8744
8745 element.prepaint_at(origin, window, cx);
8746
8747 Some((element, origin))
8748 }
8749
8750 fn render_edit_prediction_scroll_popover(
8751 &mut self,
8752 to_y: impl Fn(Size<Pixels>) -> Pixels,
8753 scroll_icon: IconName,
8754 visible_row_range: Range<DisplayRow>,
8755 line_layouts: &[LineWithInvisibles],
8756 newest_selection_head: Option<DisplayPoint>,
8757 scrolled_content_origin: gpui::Point<Pixels>,
8758 window: &mut Window,
8759 cx: &mut App,
8760 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8761 let mut element = self
8762 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8763 .into_any();
8764
8765 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8766
8767 let cursor = newest_selection_head?;
8768 let cursor_row_layout =
8769 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8770 let cursor_column = cursor.column() as usize;
8771
8772 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8773
8774 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8775
8776 element.prepaint_at(origin, window, cx);
8777 Some((element, origin))
8778 }
8779
8780 fn render_edit_prediction_eager_jump_popover(
8781 &mut self,
8782 text_bounds: &Bounds<Pixels>,
8783 content_origin: gpui::Point<Pixels>,
8784 editor_snapshot: &EditorSnapshot,
8785 visible_row_range: Range<DisplayRow>,
8786 scroll_top: f32,
8787 scroll_bottom: f32,
8788 line_height: Pixels,
8789 scroll_pixel_position: gpui::Point<Pixels>,
8790 target_display_point: DisplayPoint,
8791 editor_width: Pixels,
8792 window: &mut Window,
8793 cx: &mut App,
8794 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8795 if target_display_point.row().as_f32() < scroll_top {
8796 let mut element = self
8797 .render_edit_prediction_line_popover(
8798 "Jump to Edit",
8799 Some(IconName::ArrowUp),
8800 window,
8801 cx,
8802 )?
8803 .into_any();
8804
8805 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8806 let offset = point(
8807 (text_bounds.size.width - size.width) / 2.,
8808 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8809 );
8810
8811 let origin = text_bounds.origin + offset;
8812 element.prepaint_at(origin, window, cx);
8813 Some((element, origin))
8814 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8815 let mut element = self
8816 .render_edit_prediction_line_popover(
8817 "Jump to Edit",
8818 Some(IconName::ArrowDown),
8819 window,
8820 cx,
8821 )?
8822 .into_any();
8823
8824 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8825 let offset = point(
8826 (text_bounds.size.width - size.width) / 2.,
8827 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8828 );
8829
8830 let origin = text_bounds.origin + offset;
8831 element.prepaint_at(origin, window, cx);
8832 Some((element, origin))
8833 } else {
8834 self.render_edit_prediction_end_of_line_popover(
8835 "Jump to Edit",
8836 editor_snapshot,
8837 visible_row_range,
8838 target_display_point,
8839 line_height,
8840 scroll_pixel_position,
8841 content_origin,
8842 editor_width,
8843 window,
8844 cx,
8845 )
8846 }
8847 }
8848
8849 fn render_edit_prediction_end_of_line_popover(
8850 self: &mut Editor,
8851 label: &'static str,
8852 editor_snapshot: &EditorSnapshot,
8853 visible_row_range: Range<DisplayRow>,
8854 target_display_point: DisplayPoint,
8855 line_height: Pixels,
8856 scroll_pixel_position: gpui::Point<Pixels>,
8857 content_origin: gpui::Point<Pixels>,
8858 editor_width: Pixels,
8859 window: &mut Window,
8860 cx: &mut App,
8861 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8862 let target_line_end = DisplayPoint::new(
8863 target_display_point.row(),
8864 editor_snapshot.line_len(target_display_point.row()),
8865 );
8866
8867 let mut element = self
8868 .render_edit_prediction_line_popover(label, None, window, cx)?
8869 .into_any();
8870
8871 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8872
8873 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8874
8875 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8876 let mut origin = start_point
8877 + line_origin
8878 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8879 origin.x = origin.x.max(content_origin.x);
8880
8881 let max_x = content_origin.x + editor_width - size.width;
8882
8883 if origin.x > max_x {
8884 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8885
8886 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8887 origin.y += offset;
8888 IconName::ArrowUp
8889 } else {
8890 origin.y -= offset;
8891 IconName::ArrowDown
8892 };
8893
8894 element = self
8895 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8896 .into_any();
8897
8898 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8899
8900 origin.x = content_origin.x + editor_width - size.width - px(2.);
8901 }
8902
8903 element.prepaint_at(origin, window, cx);
8904 Some((element, origin))
8905 }
8906
8907 fn render_edit_prediction_diff_popover(
8908 self: &Editor,
8909 text_bounds: &Bounds<Pixels>,
8910 content_origin: gpui::Point<Pixels>,
8911 right_margin: Pixels,
8912 editor_snapshot: &EditorSnapshot,
8913 visible_row_range: Range<DisplayRow>,
8914 line_layouts: &[LineWithInvisibles],
8915 line_height: Pixels,
8916 scroll_pixel_position: gpui::Point<Pixels>,
8917 newest_selection_head: Option<DisplayPoint>,
8918 editor_width: Pixels,
8919 style: &EditorStyle,
8920 edits: &Vec<(Range<Anchor>, String)>,
8921 edit_preview: &Option<language::EditPreview>,
8922 snapshot: &language::BufferSnapshot,
8923 window: &mut Window,
8924 cx: &mut App,
8925 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8926 let edit_start = edits
8927 .first()
8928 .unwrap()
8929 .0
8930 .start
8931 .to_display_point(editor_snapshot);
8932 let edit_end = edits
8933 .last()
8934 .unwrap()
8935 .0
8936 .end
8937 .to_display_point(editor_snapshot);
8938
8939 let is_visible = visible_row_range.contains(&edit_start.row())
8940 || visible_row_range.contains(&edit_end.row());
8941 if !is_visible {
8942 return None;
8943 }
8944
8945 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8946 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8947 } else {
8948 // Fallback for providers without edit_preview
8949 crate::edit_prediction_fallback_text(edits, cx)
8950 };
8951
8952 let styled_text = highlighted_edits.to_styled_text(&style.text);
8953 let line_count = highlighted_edits.text.lines().count();
8954
8955 const BORDER_WIDTH: Pixels = px(1.);
8956
8957 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8958 let has_keybind = keybind.is_some();
8959
8960 let mut element = h_flex()
8961 .items_start()
8962 .child(
8963 h_flex()
8964 .bg(cx.theme().colors().editor_background)
8965 .border(BORDER_WIDTH)
8966 .shadow_xs()
8967 .border_color(cx.theme().colors().border)
8968 .rounded_l_lg()
8969 .when(line_count > 1, |el| el.rounded_br_lg())
8970 .pr_1()
8971 .child(styled_text),
8972 )
8973 .child(
8974 h_flex()
8975 .h(line_height + BORDER_WIDTH * 2.)
8976 .px_1p5()
8977 .gap_1()
8978 // Workaround: For some reason, there's a gap if we don't do this
8979 .ml(-BORDER_WIDTH)
8980 .shadow(vec![gpui::BoxShadow {
8981 color: gpui::black().opacity(0.05),
8982 offset: point(px(1.), px(1.)),
8983 blur_radius: px(2.),
8984 spread_radius: px(0.),
8985 }])
8986 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8987 .border(BORDER_WIDTH)
8988 .border_color(cx.theme().colors().border)
8989 .rounded_r_lg()
8990 .id("edit_prediction_diff_popover_keybind")
8991 .when(!has_keybind, |el| {
8992 let status_colors = cx.theme().status();
8993
8994 el.bg(status_colors.error_background)
8995 .border_color(status_colors.error.opacity(0.6))
8996 .child(Icon::new(IconName::Info).color(Color::Error))
8997 .cursor_default()
8998 .hoverable_tooltip(move |_window, cx| {
8999 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9000 })
9001 })
9002 .children(keybind),
9003 )
9004 .into_any();
9005
9006 let longest_row =
9007 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9008 let longest_line_width = if visible_row_range.contains(&longest_row) {
9009 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9010 } else {
9011 layout_line(
9012 longest_row,
9013 editor_snapshot,
9014 style,
9015 editor_width,
9016 |_| false,
9017 window,
9018 cx,
9019 )
9020 .width
9021 };
9022
9023 let viewport_bounds =
9024 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9025 right: -right_margin,
9026 ..Default::default()
9027 });
9028
9029 let x_after_longest =
9030 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9031 - scroll_pixel_position.x;
9032
9033 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9034
9035 // Fully visible if it can be displayed within the window (allow overlapping other
9036 // panes). However, this is only allowed if the popover starts within text_bounds.
9037 let can_position_to_the_right = x_after_longest < text_bounds.right()
9038 && x_after_longest + element_bounds.width < viewport_bounds.right();
9039
9040 let mut origin = if can_position_to_the_right {
9041 point(
9042 x_after_longest,
9043 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9044 - scroll_pixel_position.y,
9045 )
9046 } else {
9047 let cursor_row = newest_selection_head.map(|head| head.row());
9048 let above_edit = edit_start
9049 .row()
9050 .0
9051 .checked_sub(line_count as u32)
9052 .map(DisplayRow);
9053 let below_edit = Some(edit_end.row() + 1);
9054 let above_cursor =
9055 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9056 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9057
9058 // Place the edit popover adjacent to the edit if there is a location
9059 // available that is onscreen and does not obscure the cursor. Otherwise,
9060 // place it adjacent to the cursor.
9061 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9062 .into_iter()
9063 .flatten()
9064 .find(|&start_row| {
9065 let end_row = start_row + line_count as u32;
9066 visible_row_range.contains(&start_row)
9067 && visible_row_range.contains(&end_row)
9068 && cursor_row
9069 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9070 })?;
9071
9072 content_origin
9073 + point(
9074 -scroll_pixel_position.x,
9075 row_target.as_f32() * line_height - scroll_pixel_position.y,
9076 )
9077 };
9078
9079 origin.x -= BORDER_WIDTH;
9080
9081 window.defer_draw(element, origin, 1);
9082
9083 // Do not return an element, since it will already be drawn due to defer_draw.
9084 None
9085 }
9086
9087 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9088 px(30.)
9089 }
9090
9091 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9092 if self.read_only(cx) {
9093 cx.theme().players().read_only()
9094 } else {
9095 self.style.as_ref().unwrap().local_player
9096 }
9097 }
9098
9099 fn render_edit_prediction_accept_keybind(
9100 &self,
9101 window: &mut Window,
9102 cx: &App,
9103 ) -> Option<AnyElement> {
9104 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9105 let accept_keystroke = accept_binding.keystroke()?;
9106
9107 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9108
9109 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9110 Color::Accent
9111 } else {
9112 Color::Muted
9113 };
9114
9115 h_flex()
9116 .px_0p5()
9117 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9118 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9119 .text_size(TextSize::XSmall.rems(cx))
9120 .child(h_flex().children(ui::render_modifiers(
9121 accept_keystroke.modifiers(),
9122 PlatformStyle::platform(),
9123 Some(modifiers_color),
9124 Some(IconSize::XSmall.rems().into()),
9125 true,
9126 )))
9127 .when(is_platform_style_mac, |parent| {
9128 parent.child(accept_keystroke.key().to_string())
9129 })
9130 .when(!is_platform_style_mac, |parent| {
9131 parent.child(
9132 Key::new(
9133 util::capitalize(accept_keystroke.key()),
9134 Some(Color::Default),
9135 )
9136 .size(Some(IconSize::XSmall.rems().into())),
9137 )
9138 })
9139 .into_any()
9140 .into()
9141 }
9142
9143 fn render_edit_prediction_line_popover(
9144 &self,
9145 label: impl Into<SharedString>,
9146 icon: Option<IconName>,
9147 window: &mut Window,
9148 cx: &App,
9149 ) -> Option<Stateful<Div>> {
9150 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9151
9152 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9153 let has_keybind = keybind.is_some();
9154
9155 let result = h_flex()
9156 .id("ep-line-popover")
9157 .py_0p5()
9158 .pl_1()
9159 .pr(padding_right)
9160 .gap_1()
9161 .rounded_md()
9162 .border_1()
9163 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9164 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9165 .shadow_xs()
9166 .when(!has_keybind, |el| {
9167 let status_colors = cx.theme().status();
9168
9169 el.bg(status_colors.error_background)
9170 .border_color(status_colors.error.opacity(0.6))
9171 .pl_2()
9172 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9173 .cursor_default()
9174 .hoverable_tooltip(move |_window, cx| {
9175 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9176 })
9177 })
9178 .children(keybind)
9179 .child(
9180 Label::new(label)
9181 .size(LabelSize::Small)
9182 .when(!has_keybind, |el| {
9183 el.color(cx.theme().status().error.into()).strikethrough()
9184 }),
9185 )
9186 .when(!has_keybind, |el| {
9187 el.child(
9188 h_flex().ml_1().child(
9189 Icon::new(IconName::Info)
9190 .size(IconSize::Small)
9191 .color(cx.theme().status().error.into()),
9192 ),
9193 )
9194 })
9195 .when_some(icon, |element, icon| {
9196 element.child(
9197 div()
9198 .mt(px(1.5))
9199 .child(Icon::new(icon).size(IconSize::Small)),
9200 )
9201 });
9202
9203 Some(result)
9204 }
9205
9206 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9207 let accent_color = cx.theme().colors().text_accent;
9208 let editor_bg_color = cx.theme().colors().editor_background;
9209 editor_bg_color.blend(accent_color.opacity(0.1))
9210 }
9211
9212 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9213 let accent_color = cx.theme().colors().text_accent;
9214 let editor_bg_color = cx.theme().colors().editor_background;
9215 editor_bg_color.blend(accent_color.opacity(0.6))
9216 }
9217 fn get_prediction_provider_icon_name(
9218 provider: &Option<RegisteredEditPredictionProvider>,
9219 ) -> IconName {
9220 match provider {
9221 Some(provider) => match provider.provider.name() {
9222 "copilot" => IconName::Copilot,
9223 "supermaven" => IconName::Supermaven,
9224 _ => IconName::ZedPredict,
9225 },
9226 None => IconName::ZedPredict,
9227 }
9228 }
9229
9230 fn render_edit_prediction_cursor_popover(
9231 &self,
9232 min_width: Pixels,
9233 max_width: Pixels,
9234 cursor_point: Point,
9235 style: &EditorStyle,
9236 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9237 _window: &Window,
9238 cx: &mut Context<Editor>,
9239 ) -> Option<AnyElement> {
9240 let provider = self.edit_prediction_provider.as_ref()?;
9241 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9242
9243 let is_refreshing = provider.provider.is_refreshing(cx);
9244
9245 fn pending_completion_container(icon: IconName) -> Div {
9246 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9247 }
9248
9249 let completion = match &self.active_edit_prediction {
9250 Some(prediction) => {
9251 if !self.has_visible_completions_menu() {
9252 const RADIUS: Pixels = px(6.);
9253 const BORDER_WIDTH: Pixels = px(1.);
9254
9255 return Some(
9256 h_flex()
9257 .elevation_2(cx)
9258 .border(BORDER_WIDTH)
9259 .border_color(cx.theme().colors().border)
9260 .when(accept_keystroke.is_none(), |el| {
9261 el.border_color(cx.theme().status().error)
9262 })
9263 .rounded(RADIUS)
9264 .rounded_tl(px(0.))
9265 .overflow_hidden()
9266 .child(div().px_1p5().child(match &prediction.completion {
9267 EditPrediction::Move { target, snapshot } => {
9268 use text::ToPoint as _;
9269 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9270 {
9271 Icon::new(IconName::ZedPredictDown)
9272 } else {
9273 Icon::new(IconName::ZedPredictUp)
9274 }
9275 }
9276 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9277 }))
9278 .child(
9279 h_flex()
9280 .gap_1()
9281 .py_1()
9282 .px_2()
9283 .rounded_r(RADIUS - BORDER_WIDTH)
9284 .border_l_1()
9285 .border_color(cx.theme().colors().border)
9286 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9287 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9288 el.child(
9289 Label::new("Hold")
9290 .size(LabelSize::Small)
9291 .when(accept_keystroke.is_none(), |el| {
9292 el.strikethrough()
9293 })
9294 .line_height_style(LineHeightStyle::UiLabel),
9295 )
9296 })
9297 .id("edit_prediction_cursor_popover_keybind")
9298 .when(accept_keystroke.is_none(), |el| {
9299 let status_colors = cx.theme().status();
9300
9301 el.bg(status_colors.error_background)
9302 .border_color(status_colors.error.opacity(0.6))
9303 .child(Icon::new(IconName::Info).color(Color::Error))
9304 .cursor_default()
9305 .hoverable_tooltip(move |_window, cx| {
9306 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9307 .into()
9308 })
9309 })
9310 .when_some(
9311 accept_keystroke.as_ref(),
9312 |el, accept_keystroke| {
9313 el.child(h_flex().children(ui::render_modifiers(
9314 accept_keystroke.modifiers(),
9315 PlatformStyle::platform(),
9316 Some(Color::Default),
9317 Some(IconSize::XSmall.rems().into()),
9318 false,
9319 )))
9320 },
9321 ),
9322 )
9323 .into_any(),
9324 );
9325 }
9326
9327 self.render_edit_prediction_cursor_popover_preview(
9328 prediction,
9329 cursor_point,
9330 style,
9331 cx,
9332 )?
9333 }
9334
9335 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9336 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9337 stale_completion,
9338 cursor_point,
9339 style,
9340 cx,
9341 )?,
9342
9343 None => pending_completion_container(provider_icon)
9344 .child(Label::new("...").size(LabelSize::Small)),
9345 },
9346
9347 None => pending_completion_container(provider_icon)
9348 .child(Label::new("...").size(LabelSize::Small)),
9349 };
9350
9351 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9352 completion
9353 .with_animation(
9354 "loading-completion",
9355 Animation::new(Duration::from_secs(2))
9356 .repeat()
9357 .with_easing(pulsating_between(0.4, 0.8)),
9358 |label, delta| label.opacity(delta),
9359 )
9360 .into_any_element()
9361 } else {
9362 completion.into_any_element()
9363 };
9364
9365 let has_completion = self.active_edit_prediction.is_some();
9366
9367 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9368 Some(
9369 h_flex()
9370 .min_w(min_width)
9371 .max_w(max_width)
9372 .flex_1()
9373 .elevation_2(cx)
9374 .border_color(cx.theme().colors().border)
9375 .child(
9376 div()
9377 .flex_1()
9378 .py_1()
9379 .px_2()
9380 .overflow_hidden()
9381 .child(completion),
9382 )
9383 .when_some(accept_keystroke, |el, accept_keystroke| {
9384 if !accept_keystroke.modifiers().modified() {
9385 return el;
9386 }
9387
9388 el.child(
9389 h_flex()
9390 .h_full()
9391 .border_l_1()
9392 .rounded_r_lg()
9393 .border_color(cx.theme().colors().border)
9394 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9395 .gap_1()
9396 .py_1()
9397 .px_2()
9398 .child(
9399 h_flex()
9400 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9401 .when(is_platform_style_mac, |parent| parent.gap_1())
9402 .child(h_flex().children(ui::render_modifiers(
9403 accept_keystroke.modifiers(),
9404 PlatformStyle::platform(),
9405 Some(if !has_completion {
9406 Color::Muted
9407 } else {
9408 Color::Default
9409 }),
9410 None,
9411 false,
9412 ))),
9413 )
9414 .child(Label::new("Preview").into_any_element())
9415 .opacity(if has_completion { 1.0 } else { 0.4 }),
9416 )
9417 })
9418 .into_any(),
9419 )
9420 }
9421
9422 fn render_edit_prediction_cursor_popover_preview(
9423 &self,
9424 completion: &EditPredictionState,
9425 cursor_point: Point,
9426 style: &EditorStyle,
9427 cx: &mut Context<Editor>,
9428 ) -> Option<Div> {
9429 use text::ToPoint as _;
9430
9431 fn render_relative_row_jump(
9432 prefix: impl Into<String>,
9433 current_row: u32,
9434 target_row: u32,
9435 ) -> Div {
9436 let (row_diff, arrow) = if target_row < current_row {
9437 (current_row - target_row, IconName::ArrowUp)
9438 } else {
9439 (target_row - current_row, IconName::ArrowDown)
9440 };
9441
9442 h_flex()
9443 .child(
9444 Label::new(format!("{}{}", prefix.into(), row_diff))
9445 .color(Color::Muted)
9446 .size(LabelSize::Small),
9447 )
9448 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9449 }
9450
9451 let supports_jump = self
9452 .edit_prediction_provider
9453 .as_ref()
9454 .map(|provider| provider.provider.supports_jump_to_edit())
9455 .unwrap_or(true);
9456
9457 match &completion.completion {
9458 EditPrediction::Move {
9459 target, snapshot, ..
9460 } => {
9461 if !supports_jump {
9462 return None;
9463 }
9464
9465 Some(
9466 h_flex()
9467 .px_2()
9468 .gap_2()
9469 .flex_1()
9470 .child(
9471 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9472 Icon::new(IconName::ZedPredictDown)
9473 } else {
9474 Icon::new(IconName::ZedPredictUp)
9475 },
9476 )
9477 .child(Label::new("Jump to Edit")),
9478 )
9479 }
9480
9481 EditPrediction::Edit {
9482 edits,
9483 edit_preview,
9484 snapshot,
9485 display_mode: _,
9486 } => {
9487 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9488
9489 let (highlighted_edits, has_more_lines) =
9490 if let Some(edit_preview) = edit_preview.as_ref() {
9491 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9492 .first_line_preview()
9493 } else {
9494 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9495 };
9496
9497 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9498 .with_default_highlights(&style.text, highlighted_edits.highlights);
9499
9500 let preview = h_flex()
9501 .gap_1()
9502 .min_w_16()
9503 .child(styled_text)
9504 .when(has_more_lines, |parent| parent.child("…"));
9505
9506 let left = if supports_jump && first_edit_row != cursor_point.row {
9507 render_relative_row_jump("", cursor_point.row, first_edit_row)
9508 .into_any_element()
9509 } else {
9510 let icon_name =
9511 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9512 Icon::new(icon_name).into_any_element()
9513 };
9514
9515 Some(
9516 h_flex()
9517 .h_full()
9518 .flex_1()
9519 .gap_2()
9520 .pr_1()
9521 .overflow_x_hidden()
9522 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9523 .child(left)
9524 .child(preview),
9525 )
9526 }
9527 }
9528 }
9529
9530 pub fn render_context_menu(
9531 &self,
9532 style: &EditorStyle,
9533 max_height_in_lines: u32,
9534 window: &mut Window,
9535 cx: &mut Context<Editor>,
9536 ) -> Option<AnyElement> {
9537 let menu = self.context_menu.borrow();
9538 let menu = menu.as_ref()?;
9539 if !menu.visible() {
9540 return None;
9541 };
9542 Some(menu.render(style, max_height_in_lines, window, cx))
9543 }
9544
9545 fn render_context_menu_aside(
9546 &mut self,
9547 max_size: Size<Pixels>,
9548 window: &mut Window,
9549 cx: &mut Context<Editor>,
9550 ) -> Option<AnyElement> {
9551 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9552 if menu.visible() {
9553 menu.render_aside(max_size, window, cx)
9554 } else {
9555 None
9556 }
9557 })
9558 }
9559
9560 fn hide_context_menu(
9561 &mut self,
9562 window: &mut Window,
9563 cx: &mut Context<Self>,
9564 ) -> Option<CodeContextMenu> {
9565 cx.notify();
9566 self.completion_tasks.clear();
9567 let context_menu = self.context_menu.borrow_mut().take();
9568 self.stale_edit_prediction_in_menu.take();
9569 self.update_visible_edit_prediction(window, cx);
9570 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9571 && let Some(completion_provider) = &self.completion_provider
9572 {
9573 completion_provider.selection_changed(None, window, cx);
9574 }
9575 context_menu
9576 }
9577
9578 fn show_snippet_choices(
9579 &mut self,
9580 choices: &Vec<String>,
9581 selection: Range<Anchor>,
9582 cx: &mut Context<Self>,
9583 ) {
9584 let Some((_, buffer, _)) = self
9585 .buffer()
9586 .read(cx)
9587 .excerpt_containing(selection.start, cx)
9588 else {
9589 return;
9590 };
9591 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9592 else {
9593 return;
9594 };
9595 if buffer != end_buffer {
9596 log::error!("expected anchor range to have matching buffer IDs");
9597 return;
9598 }
9599
9600 let id = post_inc(&mut self.next_completion_id);
9601 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9602 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9603 CompletionsMenu::new_snippet_choices(
9604 id,
9605 true,
9606 choices,
9607 selection,
9608 buffer,
9609 snippet_sort_order,
9610 ),
9611 ));
9612 }
9613
9614 pub fn insert_snippet(
9615 &mut self,
9616 insertion_ranges: &[Range<usize>],
9617 snippet: Snippet,
9618 window: &mut Window,
9619 cx: &mut Context<Self>,
9620 ) -> Result<()> {
9621 struct Tabstop<T> {
9622 is_end_tabstop: bool,
9623 ranges: Vec<Range<T>>,
9624 choices: Option<Vec<String>>,
9625 }
9626
9627 let tabstops = self.buffer.update(cx, |buffer, cx| {
9628 let snippet_text: Arc<str> = snippet.text.clone().into();
9629 let edits = insertion_ranges
9630 .iter()
9631 .cloned()
9632 .map(|range| (range, snippet_text.clone()));
9633 let autoindent_mode = AutoindentMode::Block {
9634 original_indent_columns: Vec::new(),
9635 };
9636 buffer.edit(edits, Some(autoindent_mode), cx);
9637
9638 let snapshot = &*buffer.read(cx);
9639 let snippet = &snippet;
9640 snippet
9641 .tabstops
9642 .iter()
9643 .map(|tabstop| {
9644 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9645 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9646 });
9647 let mut tabstop_ranges = tabstop
9648 .ranges
9649 .iter()
9650 .flat_map(|tabstop_range| {
9651 let mut delta = 0_isize;
9652 insertion_ranges.iter().map(move |insertion_range| {
9653 let insertion_start = insertion_range.start as isize + delta;
9654 delta +=
9655 snippet.text.len() as isize - insertion_range.len() as isize;
9656
9657 let start = ((insertion_start + tabstop_range.start) as usize)
9658 .min(snapshot.len());
9659 let end = ((insertion_start + tabstop_range.end) as usize)
9660 .min(snapshot.len());
9661 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9662 })
9663 })
9664 .collect::<Vec<_>>();
9665 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9666
9667 Tabstop {
9668 is_end_tabstop,
9669 ranges: tabstop_ranges,
9670 choices: tabstop.choices.clone(),
9671 }
9672 })
9673 .collect::<Vec<_>>()
9674 });
9675 if let Some(tabstop) = tabstops.first() {
9676 self.change_selections(Default::default(), window, cx, |s| {
9677 // Reverse order so that the first range is the newest created selection.
9678 // Completions will use it and autoscroll will prioritize it.
9679 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9680 });
9681
9682 if let Some(choices) = &tabstop.choices
9683 && let Some(selection) = tabstop.ranges.first()
9684 {
9685 self.show_snippet_choices(choices, selection.clone(), cx)
9686 }
9687
9688 // If we're already at the last tabstop and it's at the end of the snippet,
9689 // we're done, we don't need to keep the state around.
9690 if !tabstop.is_end_tabstop {
9691 let choices = tabstops
9692 .iter()
9693 .map(|tabstop| tabstop.choices.clone())
9694 .collect();
9695
9696 let ranges = tabstops
9697 .into_iter()
9698 .map(|tabstop| tabstop.ranges)
9699 .collect::<Vec<_>>();
9700
9701 self.snippet_stack.push(SnippetState {
9702 active_index: 0,
9703 ranges,
9704 choices,
9705 });
9706 }
9707
9708 // Check whether the just-entered snippet ends with an auto-closable bracket.
9709 if self.autoclose_regions.is_empty() {
9710 let snapshot = self.buffer.read(cx).snapshot(cx);
9711 let mut all_selections = self.selections.all::<Point>(cx);
9712 for selection in &mut all_selections {
9713 let selection_head = selection.head();
9714 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9715 continue;
9716 };
9717
9718 let mut bracket_pair = None;
9719 let max_lookup_length = scope
9720 .brackets()
9721 .map(|(pair, _)| {
9722 pair.start
9723 .as_str()
9724 .chars()
9725 .count()
9726 .max(pair.end.as_str().chars().count())
9727 })
9728 .max();
9729 if let Some(max_lookup_length) = max_lookup_length {
9730 let next_text = snapshot
9731 .chars_at(selection_head)
9732 .take(max_lookup_length)
9733 .collect::<String>();
9734 let prev_text = snapshot
9735 .reversed_chars_at(selection_head)
9736 .take(max_lookup_length)
9737 .collect::<String>();
9738
9739 for (pair, enabled) in scope.brackets() {
9740 if enabled
9741 && pair.close
9742 && prev_text.starts_with(pair.start.as_str())
9743 && next_text.starts_with(pair.end.as_str())
9744 {
9745 bracket_pair = Some(pair.clone());
9746 break;
9747 }
9748 }
9749 }
9750
9751 if let Some(pair) = bracket_pair {
9752 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9753 let autoclose_enabled =
9754 self.use_autoclose && snapshot_settings.use_autoclose;
9755 if autoclose_enabled {
9756 let start = snapshot.anchor_after(selection_head);
9757 let end = snapshot.anchor_after(selection_head);
9758 self.autoclose_regions.push(AutocloseRegion {
9759 selection_id: selection.id,
9760 range: start..end,
9761 pair,
9762 });
9763 }
9764 }
9765 }
9766 }
9767 }
9768 Ok(())
9769 }
9770
9771 pub fn move_to_next_snippet_tabstop(
9772 &mut self,
9773 window: &mut Window,
9774 cx: &mut Context<Self>,
9775 ) -> bool {
9776 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9777 }
9778
9779 pub fn move_to_prev_snippet_tabstop(
9780 &mut self,
9781 window: &mut Window,
9782 cx: &mut Context<Self>,
9783 ) -> bool {
9784 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9785 }
9786
9787 pub fn move_to_snippet_tabstop(
9788 &mut self,
9789 bias: Bias,
9790 window: &mut Window,
9791 cx: &mut Context<Self>,
9792 ) -> bool {
9793 if let Some(mut snippet) = self.snippet_stack.pop() {
9794 match bias {
9795 Bias::Left => {
9796 if snippet.active_index > 0 {
9797 snippet.active_index -= 1;
9798 } else {
9799 self.snippet_stack.push(snippet);
9800 return false;
9801 }
9802 }
9803 Bias::Right => {
9804 if snippet.active_index + 1 < snippet.ranges.len() {
9805 snippet.active_index += 1;
9806 } else {
9807 self.snippet_stack.push(snippet);
9808 return false;
9809 }
9810 }
9811 }
9812 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9813 self.change_selections(Default::default(), window, cx, |s| {
9814 // Reverse order so that the first range is the newest created selection.
9815 // Completions will use it and autoscroll will prioritize it.
9816 s.select_ranges(current_ranges.iter().rev().cloned())
9817 });
9818
9819 if let Some(choices) = &snippet.choices[snippet.active_index]
9820 && let Some(selection) = current_ranges.first()
9821 {
9822 self.show_snippet_choices(choices, selection.clone(), cx);
9823 }
9824
9825 // If snippet state is not at the last tabstop, push it back on the stack
9826 if snippet.active_index + 1 < snippet.ranges.len() {
9827 self.snippet_stack.push(snippet);
9828 }
9829 return true;
9830 }
9831 }
9832
9833 false
9834 }
9835
9836 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9837 self.transact(window, cx, |this, window, cx| {
9838 this.select_all(&SelectAll, window, cx);
9839 this.insert("", window, cx);
9840 });
9841 }
9842
9843 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9844 if self.read_only(cx) {
9845 return;
9846 }
9847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9848 self.transact(window, cx, |this, window, cx| {
9849 this.select_autoclose_pair(window, cx);
9850 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9851 if !this.linked_edit_ranges.is_empty() {
9852 let selections = this.selections.all::<MultiBufferPoint>(cx);
9853 let snapshot = this.buffer.read(cx).snapshot(cx);
9854
9855 for selection in selections.iter() {
9856 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9857 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9858 if selection_start.buffer_id != selection_end.buffer_id {
9859 continue;
9860 }
9861 if let Some(ranges) =
9862 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9863 {
9864 for (buffer, entries) in ranges {
9865 linked_ranges.entry(buffer).or_default().extend(entries);
9866 }
9867 }
9868 }
9869 }
9870
9871 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9872 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9873 for selection in &mut selections {
9874 if selection.is_empty() {
9875 let old_head = selection.head();
9876 let mut new_head =
9877 movement::left(&display_map, old_head.to_display_point(&display_map))
9878 .to_point(&display_map);
9879 if let Some((buffer, line_buffer_range)) = display_map
9880 .buffer_snapshot
9881 .buffer_line_for_row(MultiBufferRow(old_head.row))
9882 {
9883 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9884 let indent_len = match indent_size.kind {
9885 IndentKind::Space => {
9886 buffer.settings_at(line_buffer_range.start, cx).tab_size
9887 }
9888 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9889 };
9890 if old_head.column <= indent_size.len && old_head.column > 0 {
9891 let indent_len = indent_len.get();
9892 new_head = cmp::min(
9893 new_head,
9894 MultiBufferPoint::new(
9895 old_head.row,
9896 ((old_head.column - 1) / indent_len) * indent_len,
9897 ),
9898 );
9899 }
9900 }
9901
9902 selection.set_head(new_head, SelectionGoal::None);
9903 }
9904 }
9905
9906 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9907 this.insert("", window, cx);
9908 let empty_str: Arc<str> = Arc::from("");
9909 for (buffer, edits) in linked_ranges {
9910 let snapshot = buffer.read(cx).snapshot();
9911 use text::ToPoint as TP;
9912
9913 let edits = edits
9914 .into_iter()
9915 .map(|range| {
9916 let end_point = TP::to_point(&range.end, &snapshot);
9917 let mut start_point = TP::to_point(&range.start, &snapshot);
9918
9919 if end_point == start_point {
9920 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9921 .saturating_sub(1);
9922 start_point =
9923 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9924 };
9925
9926 (start_point..end_point, empty_str.clone())
9927 })
9928 .sorted_by_key(|(range, _)| range.start)
9929 .collect::<Vec<_>>();
9930 buffer.update(cx, |this, cx| {
9931 this.edit(edits, None, cx);
9932 })
9933 }
9934 this.refresh_edit_prediction(true, false, window, cx);
9935 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9936 });
9937 }
9938
9939 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9940 if self.read_only(cx) {
9941 return;
9942 }
9943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9944 self.transact(window, cx, |this, window, cx| {
9945 this.change_selections(Default::default(), window, cx, |s| {
9946 s.move_with(|map, selection| {
9947 if selection.is_empty() {
9948 let cursor = movement::right(map, selection.head());
9949 selection.end = cursor;
9950 selection.reversed = true;
9951 selection.goal = SelectionGoal::None;
9952 }
9953 })
9954 });
9955 this.insert("", window, cx);
9956 this.refresh_edit_prediction(true, false, window, cx);
9957 });
9958 }
9959
9960 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9961 if self.mode.is_single_line() {
9962 cx.propagate();
9963 return;
9964 }
9965
9966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9967 if self.move_to_prev_snippet_tabstop(window, cx) {
9968 return;
9969 }
9970 self.outdent(&Outdent, window, cx);
9971 }
9972
9973 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9974 if self.mode.is_single_line() {
9975 cx.propagate();
9976 return;
9977 }
9978
9979 if self.move_to_next_snippet_tabstop(window, cx) {
9980 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9981 return;
9982 }
9983 if self.read_only(cx) {
9984 return;
9985 }
9986 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9987 let mut selections = self.selections.all_adjusted(cx);
9988 let buffer = self.buffer.read(cx);
9989 let snapshot = buffer.snapshot(cx);
9990 let rows_iter = selections.iter().map(|s| s.head().row);
9991 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9992
9993 let has_some_cursor_in_whitespace = selections
9994 .iter()
9995 .filter(|selection| selection.is_empty())
9996 .any(|selection| {
9997 let cursor = selection.head();
9998 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9999 cursor.column < current_indent.len
10000 });
10001
10002 let mut edits = Vec::new();
10003 let mut prev_edited_row = 0;
10004 let mut row_delta = 0;
10005 for selection in &mut selections {
10006 if selection.start.row != prev_edited_row {
10007 row_delta = 0;
10008 }
10009 prev_edited_row = selection.end.row;
10010
10011 // If the selection is non-empty, then increase the indentation of the selected lines.
10012 if !selection.is_empty() {
10013 row_delta =
10014 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10015 continue;
10016 }
10017
10018 let cursor = selection.head();
10019 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10020 if let Some(suggested_indent) =
10021 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10022 {
10023 // Don't do anything if already at suggested indent
10024 // and there is any other cursor which is not
10025 if has_some_cursor_in_whitespace
10026 && cursor.column == current_indent.len
10027 && current_indent.len == suggested_indent.len
10028 {
10029 continue;
10030 }
10031
10032 // Adjust line and move cursor to suggested indent
10033 // if cursor is not at suggested indent
10034 if cursor.column < suggested_indent.len
10035 && cursor.column <= current_indent.len
10036 && current_indent.len <= suggested_indent.len
10037 {
10038 selection.start = Point::new(cursor.row, suggested_indent.len);
10039 selection.end = selection.start;
10040 if row_delta == 0 {
10041 edits.extend(Buffer::edit_for_indent_size_adjustment(
10042 cursor.row,
10043 current_indent,
10044 suggested_indent,
10045 ));
10046 row_delta = suggested_indent.len - current_indent.len;
10047 }
10048 continue;
10049 }
10050
10051 // If current indent is more than suggested indent
10052 // only move cursor to current indent and skip indent
10053 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10054 selection.start = Point::new(cursor.row, current_indent.len);
10055 selection.end = selection.start;
10056 continue;
10057 }
10058 }
10059
10060 // Otherwise, insert a hard or soft tab.
10061 let settings = buffer.language_settings_at(cursor, cx);
10062 let tab_size = if settings.hard_tabs {
10063 IndentSize::tab()
10064 } else {
10065 let tab_size = settings.tab_size.get();
10066 let indent_remainder = snapshot
10067 .text_for_range(Point::new(cursor.row, 0)..cursor)
10068 .flat_map(str::chars)
10069 .fold(row_delta % tab_size, |counter: u32, c| {
10070 if c == '\t' {
10071 0
10072 } else {
10073 (counter + 1) % tab_size
10074 }
10075 });
10076
10077 let chars_to_next_tab_stop = tab_size - indent_remainder;
10078 IndentSize::spaces(chars_to_next_tab_stop)
10079 };
10080 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10081 selection.end = selection.start;
10082 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10083 row_delta += tab_size.len;
10084 }
10085
10086 self.transact(window, cx, |this, window, cx| {
10087 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10088 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10089 this.refresh_edit_prediction(true, false, window, cx);
10090 });
10091 }
10092
10093 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10094 if self.read_only(cx) {
10095 return;
10096 }
10097 if self.mode.is_single_line() {
10098 cx.propagate();
10099 return;
10100 }
10101
10102 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10103 let mut selections = self.selections.all::<Point>(cx);
10104 let mut prev_edited_row = 0;
10105 let mut row_delta = 0;
10106 let mut edits = Vec::new();
10107 let buffer = self.buffer.read(cx);
10108 let snapshot = buffer.snapshot(cx);
10109 for selection in &mut selections {
10110 if selection.start.row != prev_edited_row {
10111 row_delta = 0;
10112 }
10113 prev_edited_row = selection.end.row;
10114
10115 row_delta =
10116 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10117 }
10118
10119 self.transact(window, cx, |this, window, cx| {
10120 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10121 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10122 });
10123 }
10124
10125 fn indent_selection(
10126 buffer: &MultiBuffer,
10127 snapshot: &MultiBufferSnapshot,
10128 selection: &mut Selection<Point>,
10129 edits: &mut Vec<(Range<Point>, String)>,
10130 delta_for_start_row: u32,
10131 cx: &App,
10132 ) -> u32 {
10133 let settings = buffer.language_settings_at(selection.start, cx);
10134 let tab_size = settings.tab_size.get();
10135 let indent_kind = if settings.hard_tabs {
10136 IndentKind::Tab
10137 } else {
10138 IndentKind::Space
10139 };
10140 let mut start_row = selection.start.row;
10141 let mut end_row = selection.end.row + 1;
10142
10143 // If a selection ends at the beginning of a line, don't indent
10144 // that last line.
10145 if selection.end.column == 0 && selection.end.row > selection.start.row {
10146 end_row -= 1;
10147 }
10148
10149 // Avoid re-indenting a row that has already been indented by a
10150 // previous selection, but still update this selection's column
10151 // to reflect that indentation.
10152 if delta_for_start_row > 0 {
10153 start_row += 1;
10154 selection.start.column += delta_for_start_row;
10155 if selection.end.row == selection.start.row {
10156 selection.end.column += delta_for_start_row;
10157 }
10158 }
10159
10160 let mut delta_for_end_row = 0;
10161 let has_multiple_rows = start_row + 1 != end_row;
10162 for row in start_row..end_row {
10163 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10164 let indent_delta = match (current_indent.kind, indent_kind) {
10165 (IndentKind::Space, IndentKind::Space) => {
10166 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10167 IndentSize::spaces(columns_to_next_tab_stop)
10168 }
10169 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10170 (_, IndentKind::Tab) => IndentSize::tab(),
10171 };
10172
10173 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10174 0
10175 } else {
10176 selection.start.column
10177 };
10178 let row_start = Point::new(row, start);
10179 edits.push((
10180 row_start..row_start,
10181 indent_delta.chars().collect::<String>(),
10182 ));
10183
10184 // Update this selection's endpoints to reflect the indentation.
10185 if row == selection.start.row {
10186 selection.start.column += indent_delta.len;
10187 }
10188 if row == selection.end.row {
10189 selection.end.column += indent_delta.len;
10190 delta_for_end_row = indent_delta.len;
10191 }
10192 }
10193
10194 if selection.start.row == selection.end.row {
10195 delta_for_start_row + delta_for_end_row
10196 } else {
10197 delta_for_end_row
10198 }
10199 }
10200
10201 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10202 if self.read_only(cx) {
10203 return;
10204 }
10205 if self.mode.is_single_line() {
10206 cx.propagate();
10207 return;
10208 }
10209
10210 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10211 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10212 let selections = self.selections.all::<Point>(cx);
10213 let mut deletion_ranges = Vec::new();
10214 let mut last_outdent = None;
10215 {
10216 let buffer = self.buffer.read(cx);
10217 let snapshot = buffer.snapshot(cx);
10218 for selection in &selections {
10219 let settings = buffer.language_settings_at(selection.start, cx);
10220 let tab_size = settings.tab_size.get();
10221 let mut rows = selection.spanned_rows(false, &display_map);
10222
10223 // Avoid re-outdenting a row that has already been outdented by a
10224 // previous selection.
10225 if let Some(last_row) = last_outdent
10226 && last_row == rows.start
10227 {
10228 rows.start = rows.start.next_row();
10229 }
10230 let has_multiple_rows = rows.len() > 1;
10231 for row in rows.iter_rows() {
10232 let indent_size = snapshot.indent_size_for_line(row);
10233 if indent_size.len > 0 {
10234 let deletion_len = match indent_size.kind {
10235 IndentKind::Space => {
10236 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10237 if columns_to_prev_tab_stop == 0 {
10238 tab_size
10239 } else {
10240 columns_to_prev_tab_stop
10241 }
10242 }
10243 IndentKind::Tab => 1,
10244 };
10245 let start = if has_multiple_rows
10246 || deletion_len > selection.start.column
10247 || indent_size.len < selection.start.column
10248 {
10249 0
10250 } else {
10251 selection.start.column - deletion_len
10252 };
10253 deletion_ranges.push(
10254 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10255 );
10256 last_outdent = Some(row);
10257 }
10258 }
10259 }
10260 }
10261
10262 self.transact(window, cx, |this, window, cx| {
10263 this.buffer.update(cx, |buffer, cx| {
10264 let empty_str: Arc<str> = Arc::default();
10265 buffer.edit(
10266 deletion_ranges
10267 .into_iter()
10268 .map(|range| (range, empty_str.clone())),
10269 None,
10270 cx,
10271 );
10272 });
10273 let selections = this.selections.all::<usize>(cx);
10274 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10275 });
10276 }
10277
10278 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10279 if self.read_only(cx) {
10280 return;
10281 }
10282 if self.mode.is_single_line() {
10283 cx.propagate();
10284 return;
10285 }
10286
10287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10288 let selections = self
10289 .selections
10290 .all::<usize>(cx)
10291 .into_iter()
10292 .map(|s| s.range());
10293
10294 self.transact(window, cx, |this, window, cx| {
10295 this.buffer.update(cx, |buffer, cx| {
10296 buffer.autoindent_ranges(selections, cx);
10297 });
10298 let selections = this.selections.all::<usize>(cx);
10299 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10300 });
10301 }
10302
10303 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10304 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10305 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10306 let selections = self.selections.all::<Point>(cx);
10307
10308 let mut new_cursors = Vec::new();
10309 let mut edit_ranges = Vec::new();
10310 let mut selections = selections.iter().peekable();
10311 while let Some(selection) = selections.next() {
10312 let mut rows = selection.spanned_rows(false, &display_map);
10313 let goal_display_column = selection.head().to_display_point(&display_map).column();
10314
10315 // Accumulate contiguous regions of rows that we want to delete.
10316 while let Some(next_selection) = selections.peek() {
10317 let next_rows = next_selection.spanned_rows(false, &display_map);
10318 if next_rows.start <= rows.end {
10319 rows.end = next_rows.end;
10320 selections.next().unwrap();
10321 } else {
10322 break;
10323 }
10324 }
10325
10326 let buffer = &display_map.buffer_snapshot;
10327 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10328 let edit_end;
10329 let cursor_buffer_row;
10330 if buffer.max_point().row >= rows.end.0 {
10331 // If there's a line after the range, delete the \n from the end of the row range
10332 // and position the cursor on the next line.
10333 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10334 cursor_buffer_row = rows.end;
10335 } else {
10336 // If there isn't a line after the range, delete the \n from the line before the
10337 // start of the row range and position the cursor there.
10338 edit_start = edit_start.saturating_sub(1);
10339 edit_end = buffer.len();
10340 cursor_buffer_row = rows.start.previous_row();
10341 }
10342
10343 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10344 *cursor.column_mut() =
10345 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10346
10347 new_cursors.push((
10348 selection.id,
10349 buffer.anchor_after(cursor.to_point(&display_map)),
10350 ));
10351 edit_ranges.push(edit_start..edit_end);
10352 }
10353
10354 self.transact(window, cx, |this, window, cx| {
10355 let buffer = this.buffer.update(cx, |buffer, cx| {
10356 let empty_str: Arc<str> = Arc::default();
10357 buffer.edit(
10358 edit_ranges
10359 .into_iter()
10360 .map(|range| (range, empty_str.clone())),
10361 None,
10362 cx,
10363 );
10364 buffer.snapshot(cx)
10365 });
10366 let new_selections = new_cursors
10367 .into_iter()
10368 .map(|(id, cursor)| {
10369 let cursor = cursor.to_point(&buffer);
10370 Selection {
10371 id,
10372 start: cursor,
10373 end: cursor,
10374 reversed: false,
10375 goal: SelectionGoal::None,
10376 }
10377 })
10378 .collect();
10379
10380 this.change_selections(Default::default(), window, cx, |s| {
10381 s.select(new_selections);
10382 });
10383 });
10384 }
10385
10386 pub fn join_lines_impl(
10387 &mut self,
10388 insert_whitespace: bool,
10389 window: &mut Window,
10390 cx: &mut Context<Self>,
10391 ) {
10392 if self.read_only(cx) {
10393 return;
10394 }
10395 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10396 for selection in self.selections.all::<Point>(cx) {
10397 let start = MultiBufferRow(selection.start.row);
10398 // Treat single line selections as if they include the next line. Otherwise this action
10399 // would do nothing for single line selections individual cursors.
10400 let end = if selection.start.row == selection.end.row {
10401 MultiBufferRow(selection.start.row + 1)
10402 } else {
10403 MultiBufferRow(selection.end.row)
10404 };
10405
10406 if let Some(last_row_range) = row_ranges.last_mut()
10407 && start <= last_row_range.end
10408 {
10409 last_row_range.end = end;
10410 continue;
10411 }
10412 row_ranges.push(start..end);
10413 }
10414
10415 let snapshot = self.buffer.read(cx).snapshot(cx);
10416 let mut cursor_positions = Vec::new();
10417 for row_range in &row_ranges {
10418 let anchor = snapshot.anchor_before(Point::new(
10419 row_range.end.previous_row().0,
10420 snapshot.line_len(row_range.end.previous_row()),
10421 ));
10422 cursor_positions.push(anchor..anchor);
10423 }
10424
10425 self.transact(window, cx, |this, window, cx| {
10426 for row_range in row_ranges.into_iter().rev() {
10427 for row in row_range.iter_rows().rev() {
10428 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10429 let next_line_row = row.next_row();
10430 let indent = snapshot.indent_size_for_line(next_line_row);
10431 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10432
10433 let replace =
10434 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10435 " "
10436 } else {
10437 ""
10438 };
10439
10440 this.buffer.update(cx, |buffer, cx| {
10441 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10442 });
10443 }
10444 }
10445
10446 this.change_selections(Default::default(), window, cx, |s| {
10447 s.select_anchor_ranges(cursor_positions)
10448 });
10449 });
10450 }
10451
10452 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10453 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10454 self.join_lines_impl(true, window, cx);
10455 }
10456
10457 pub fn sort_lines_case_sensitive(
10458 &mut self,
10459 _: &SortLinesCaseSensitive,
10460 window: &mut Window,
10461 cx: &mut Context<Self>,
10462 ) {
10463 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10464 }
10465
10466 pub fn sort_lines_by_length(
10467 &mut self,
10468 _: &SortLinesByLength,
10469 window: &mut Window,
10470 cx: &mut Context<Self>,
10471 ) {
10472 self.manipulate_immutable_lines(window, cx, |lines| {
10473 lines.sort_by_key(|&line| line.chars().count())
10474 })
10475 }
10476
10477 pub fn sort_lines_case_insensitive(
10478 &mut self,
10479 _: &SortLinesCaseInsensitive,
10480 window: &mut Window,
10481 cx: &mut Context<Self>,
10482 ) {
10483 self.manipulate_immutable_lines(window, cx, |lines| {
10484 lines.sort_by_key(|line| line.to_lowercase())
10485 })
10486 }
10487
10488 pub fn unique_lines_case_insensitive(
10489 &mut self,
10490 _: &UniqueLinesCaseInsensitive,
10491 window: &mut Window,
10492 cx: &mut Context<Self>,
10493 ) {
10494 self.manipulate_immutable_lines(window, cx, |lines| {
10495 let mut seen = HashSet::default();
10496 lines.retain(|line| seen.insert(line.to_lowercase()));
10497 })
10498 }
10499
10500 pub fn unique_lines_case_sensitive(
10501 &mut self,
10502 _: &UniqueLinesCaseSensitive,
10503 window: &mut Window,
10504 cx: &mut Context<Self>,
10505 ) {
10506 self.manipulate_immutable_lines(window, cx, |lines| {
10507 let mut seen = HashSet::default();
10508 lines.retain(|line| seen.insert(*line));
10509 })
10510 }
10511
10512 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10513 let snapshot = self.buffer.read(cx).snapshot(cx);
10514 for selection in self.selections.disjoint_anchors().iter() {
10515 if snapshot
10516 .language_at(selection.start)
10517 .and_then(|lang| lang.config().wrap_characters.as_ref())
10518 .is_some()
10519 {
10520 return true;
10521 }
10522 }
10523 false
10524 }
10525
10526 fn wrap_selections_in_tag(
10527 &mut self,
10528 _: &WrapSelectionsInTag,
10529 window: &mut Window,
10530 cx: &mut Context<Self>,
10531 ) {
10532 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10533
10534 let snapshot = self.buffer.read(cx).snapshot(cx);
10535
10536 let mut edits = Vec::new();
10537 let mut boundaries = Vec::new();
10538
10539 for selection in self.selections.all::<Point>(cx).iter() {
10540 let Some(wrap_config) = snapshot
10541 .language_at(selection.start)
10542 .and_then(|lang| lang.config().wrap_characters.clone())
10543 else {
10544 continue;
10545 };
10546
10547 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10548 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10549
10550 let start_before = snapshot.anchor_before(selection.start);
10551 let end_after = snapshot.anchor_after(selection.end);
10552
10553 edits.push((start_before..start_before, open_tag));
10554 edits.push((end_after..end_after, close_tag));
10555
10556 boundaries.push((
10557 start_before,
10558 end_after,
10559 wrap_config.start_prefix.len(),
10560 wrap_config.end_suffix.len(),
10561 ));
10562 }
10563
10564 if edits.is_empty() {
10565 return;
10566 }
10567
10568 self.transact(window, cx, |this, window, cx| {
10569 let buffer = this.buffer.update(cx, |buffer, cx| {
10570 buffer.edit(edits, None, cx);
10571 buffer.snapshot(cx)
10572 });
10573
10574 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10575 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10576 boundaries.into_iter()
10577 {
10578 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10579 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10580 new_selections.push(open_offset..open_offset);
10581 new_selections.push(close_offset..close_offset);
10582 }
10583
10584 this.change_selections(Default::default(), window, cx, |s| {
10585 s.select_ranges(new_selections);
10586 });
10587
10588 this.request_autoscroll(Autoscroll::fit(), cx);
10589 });
10590 }
10591
10592 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10593 let Some(project) = self.project.clone() else {
10594 return;
10595 };
10596 self.reload(project, window, cx)
10597 .detach_and_notify_err(window, cx);
10598 }
10599
10600 pub fn restore_file(
10601 &mut self,
10602 _: &::git::RestoreFile,
10603 window: &mut Window,
10604 cx: &mut Context<Self>,
10605 ) {
10606 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10607 let mut buffer_ids = HashSet::default();
10608 let snapshot = self.buffer().read(cx).snapshot(cx);
10609 for selection in self.selections.all::<usize>(cx) {
10610 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10611 }
10612
10613 let buffer = self.buffer().read(cx);
10614 let ranges = buffer_ids
10615 .into_iter()
10616 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10617 .collect::<Vec<_>>();
10618
10619 self.restore_hunks_in_ranges(ranges, window, cx);
10620 }
10621
10622 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10623 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10624 let selections = self
10625 .selections
10626 .all(cx)
10627 .into_iter()
10628 .map(|s| s.range())
10629 .collect();
10630 self.restore_hunks_in_ranges(selections, window, cx);
10631 }
10632
10633 pub fn restore_hunks_in_ranges(
10634 &mut self,
10635 ranges: Vec<Range<Point>>,
10636 window: &mut Window,
10637 cx: &mut Context<Editor>,
10638 ) {
10639 let mut revert_changes = HashMap::default();
10640 let chunk_by = self
10641 .snapshot(window, cx)
10642 .hunks_for_ranges(ranges)
10643 .into_iter()
10644 .chunk_by(|hunk| hunk.buffer_id);
10645 for (buffer_id, hunks) in &chunk_by {
10646 let hunks = hunks.collect::<Vec<_>>();
10647 for hunk in &hunks {
10648 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10649 }
10650 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10651 }
10652 drop(chunk_by);
10653 if !revert_changes.is_empty() {
10654 self.transact(window, cx, |editor, window, cx| {
10655 editor.restore(revert_changes, window, cx);
10656 });
10657 }
10658 }
10659
10660 pub fn open_active_item_in_terminal(
10661 &mut self,
10662 _: &OpenInTerminal,
10663 window: &mut Window,
10664 cx: &mut Context<Self>,
10665 ) {
10666 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10667 let project_path = buffer.read(cx).project_path(cx)?;
10668 let project = self.project()?.read(cx);
10669 let entry = project.entry_for_path(&project_path, cx)?;
10670 let parent = match &entry.canonical_path {
10671 Some(canonical_path) => canonical_path.to_path_buf(),
10672 None => project.absolute_path(&project_path, cx)?,
10673 }
10674 .parent()?
10675 .to_path_buf();
10676 Some(parent)
10677 }) {
10678 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10679 }
10680 }
10681
10682 fn set_breakpoint_context_menu(
10683 &mut self,
10684 display_row: DisplayRow,
10685 position: Option<Anchor>,
10686 clicked_point: gpui::Point<Pixels>,
10687 window: &mut Window,
10688 cx: &mut Context<Self>,
10689 ) {
10690 let source = self
10691 .buffer
10692 .read(cx)
10693 .snapshot(cx)
10694 .anchor_before(Point::new(display_row.0, 0u32));
10695
10696 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10697
10698 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10699 self,
10700 source,
10701 clicked_point,
10702 context_menu,
10703 window,
10704 cx,
10705 );
10706 }
10707
10708 fn add_edit_breakpoint_block(
10709 &mut self,
10710 anchor: Anchor,
10711 breakpoint: &Breakpoint,
10712 edit_action: BreakpointPromptEditAction,
10713 window: &mut Window,
10714 cx: &mut Context<Self>,
10715 ) {
10716 let weak_editor = cx.weak_entity();
10717 let bp_prompt = cx.new(|cx| {
10718 BreakpointPromptEditor::new(
10719 weak_editor,
10720 anchor,
10721 breakpoint.clone(),
10722 edit_action,
10723 window,
10724 cx,
10725 )
10726 });
10727
10728 let height = bp_prompt.update(cx, |this, cx| {
10729 this.prompt
10730 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10731 });
10732 let cloned_prompt = bp_prompt.clone();
10733 let blocks = vec![BlockProperties {
10734 style: BlockStyle::Sticky,
10735 placement: BlockPlacement::Above(anchor),
10736 height: Some(height),
10737 render: Arc::new(move |cx| {
10738 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10739 cloned_prompt.clone().into_any_element()
10740 }),
10741 priority: 0,
10742 }];
10743
10744 let focus_handle = bp_prompt.focus_handle(cx);
10745 window.focus(&focus_handle);
10746
10747 let block_ids = self.insert_blocks(blocks, None, cx);
10748 bp_prompt.update(cx, |prompt, _| {
10749 prompt.add_block_ids(block_ids);
10750 });
10751 }
10752
10753 pub(crate) fn breakpoint_at_row(
10754 &self,
10755 row: u32,
10756 window: &mut Window,
10757 cx: &mut Context<Self>,
10758 ) -> Option<(Anchor, Breakpoint)> {
10759 let snapshot = self.snapshot(window, cx);
10760 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10761
10762 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10763 }
10764
10765 pub(crate) fn breakpoint_at_anchor(
10766 &self,
10767 breakpoint_position: Anchor,
10768 snapshot: &EditorSnapshot,
10769 cx: &mut Context<Self>,
10770 ) -> Option<(Anchor, Breakpoint)> {
10771 let buffer = self
10772 .buffer
10773 .read(cx)
10774 .buffer_for_anchor(breakpoint_position, cx)?;
10775
10776 let enclosing_excerpt = breakpoint_position.excerpt_id;
10777 let buffer_snapshot = buffer.read(cx).snapshot();
10778
10779 let row = buffer_snapshot
10780 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10781 .row;
10782
10783 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10784 let anchor_end = snapshot
10785 .buffer_snapshot
10786 .anchor_after(Point::new(row, line_len));
10787
10788 self.breakpoint_store
10789 .as_ref()?
10790 .read_with(cx, |breakpoint_store, cx| {
10791 breakpoint_store
10792 .breakpoints(
10793 &buffer,
10794 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10795 &buffer_snapshot,
10796 cx,
10797 )
10798 .next()
10799 .and_then(|(bp, _)| {
10800 let breakpoint_row = buffer_snapshot
10801 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10802 .row;
10803
10804 if breakpoint_row == row {
10805 snapshot
10806 .buffer_snapshot
10807 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10808 .map(|position| (position, bp.bp.clone()))
10809 } else {
10810 None
10811 }
10812 })
10813 })
10814 }
10815
10816 pub fn edit_log_breakpoint(
10817 &mut self,
10818 _: &EditLogBreakpoint,
10819 window: &mut Window,
10820 cx: &mut Context<Self>,
10821 ) {
10822 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10823 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10824 message: None,
10825 state: BreakpointState::Enabled,
10826 condition: None,
10827 hit_condition: None,
10828 });
10829
10830 self.add_edit_breakpoint_block(
10831 anchor,
10832 &breakpoint,
10833 BreakpointPromptEditAction::Log,
10834 window,
10835 cx,
10836 );
10837 }
10838 }
10839
10840 fn breakpoints_at_cursors(
10841 &self,
10842 window: &mut Window,
10843 cx: &mut Context<Self>,
10844 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10845 let snapshot = self.snapshot(window, cx);
10846 let cursors = self
10847 .selections
10848 .disjoint_anchors()
10849 .iter()
10850 .map(|selection| {
10851 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10852
10853 let breakpoint_position = self
10854 .breakpoint_at_row(cursor_position.row, window, cx)
10855 .map(|bp| bp.0)
10856 .unwrap_or_else(|| {
10857 snapshot
10858 .display_snapshot
10859 .buffer_snapshot
10860 .anchor_after(Point::new(cursor_position.row, 0))
10861 });
10862
10863 let breakpoint = self
10864 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10865 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10866
10867 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10868 })
10869 // 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.
10870 .collect::<HashMap<Anchor, _>>();
10871
10872 cursors.into_iter().collect()
10873 }
10874
10875 pub fn enable_breakpoint(
10876 &mut self,
10877 _: &crate::actions::EnableBreakpoint,
10878 window: &mut Window,
10879 cx: &mut Context<Self>,
10880 ) {
10881 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10882 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10883 continue;
10884 };
10885 self.edit_breakpoint_at_anchor(
10886 anchor,
10887 breakpoint,
10888 BreakpointEditAction::InvertState,
10889 cx,
10890 );
10891 }
10892 }
10893
10894 pub fn disable_breakpoint(
10895 &mut self,
10896 _: &crate::actions::DisableBreakpoint,
10897 window: &mut Window,
10898 cx: &mut Context<Self>,
10899 ) {
10900 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10901 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10902 continue;
10903 };
10904 self.edit_breakpoint_at_anchor(
10905 anchor,
10906 breakpoint,
10907 BreakpointEditAction::InvertState,
10908 cx,
10909 );
10910 }
10911 }
10912
10913 pub fn toggle_breakpoint(
10914 &mut self,
10915 _: &crate::actions::ToggleBreakpoint,
10916 window: &mut Window,
10917 cx: &mut Context<Self>,
10918 ) {
10919 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10920 if let Some(breakpoint) = breakpoint {
10921 self.edit_breakpoint_at_anchor(
10922 anchor,
10923 breakpoint,
10924 BreakpointEditAction::Toggle,
10925 cx,
10926 );
10927 } else {
10928 self.edit_breakpoint_at_anchor(
10929 anchor,
10930 Breakpoint::new_standard(),
10931 BreakpointEditAction::Toggle,
10932 cx,
10933 );
10934 }
10935 }
10936 }
10937
10938 pub fn edit_breakpoint_at_anchor(
10939 &mut self,
10940 breakpoint_position: Anchor,
10941 breakpoint: Breakpoint,
10942 edit_action: BreakpointEditAction,
10943 cx: &mut Context<Self>,
10944 ) {
10945 let Some(breakpoint_store) = &self.breakpoint_store else {
10946 return;
10947 };
10948
10949 let Some(buffer) = self
10950 .buffer
10951 .read(cx)
10952 .buffer_for_anchor(breakpoint_position, cx)
10953 else {
10954 return;
10955 };
10956
10957 breakpoint_store.update(cx, |breakpoint_store, cx| {
10958 breakpoint_store.toggle_breakpoint(
10959 buffer,
10960 BreakpointWithPosition {
10961 position: breakpoint_position.text_anchor,
10962 bp: breakpoint,
10963 },
10964 edit_action,
10965 cx,
10966 );
10967 });
10968
10969 cx.notify();
10970 }
10971
10972 #[cfg(any(test, feature = "test-support"))]
10973 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10974 self.breakpoint_store.clone()
10975 }
10976
10977 pub fn prepare_restore_change(
10978 &self,
10979 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10980 hunk: &MultiBufferDiffHunk,
10981 cx: &mut App,
10982 ) -> Option<()> {
10983 if hunk.is_created_file() {
10984 return None;
10985 }
10986 let buffer = self.buffer.read(cx);
10987 let diff = buffer.diff_for(hunk.buffer_id)?;
10988 let buffer = buffer.buffer(hunk.buffer_id)?;
10989 let buffer = buffer.read(cx);
10990 let original_text = diff
10991 .read(cx)
10992 .base_text()
10993 .as_rope()
10994 .slice(hunk.diff_base_byte_range.clone());
10995 let buffer_snapshot = buffer.snapshot();
10996 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10997 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10998 probe
10999 .0
11000 .start
11001 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11002 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11003 }) {
11004 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11005 Some(())
11006 } else {
11007 None
11008 }
11009 }
11010
11011 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11012 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11013 }
11014
11015 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11016 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11017 }
11018
11019 fn manipulate_lines<M>(
11020 &mut self,
11021 window: &mut Window,
11022 cx: &mut Context<Self>,
11023 mut manipulate: M,
11024 ) where
11025 M: FnMut(&str) -> LineManipulationResult,
11026 {
11027 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11028
11029 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11030 let buffer = self.buffer.read(cx).snapshot(cx);
11031
11032 let mut edits = Vec::new();
11033
11034 let selections = self.selections.all::<Point>(cx);
11035 let mut selections = selections.iter().peekable();
11036 let mut contiguous_row_selections = Vec::new();
11037 let mut new_selections = Vec::new();
11038 let mut added_lines = 0;
11039 let mut removed_lines = 0;
11040
11041 while let Some(selection) = selections.next() {
11042 let (start_row, end_row) = consume_contiguous_rows(
11043 &mut contiguous_row_selections,
11044 selection,
11045 &display_map,
11046 &mut selections,
11047 );
11048
11049 let start_point = Point::new(start_row.0, 0);
11050 let end_point = Point::new(
11051 end_row.previous_row().0,
11052 buffer.line_len(end_row.previous_row()),
11053 );
11054 let text = buffer
11055 .text_for_range(start_point..end_point)
11056 .collect::<String>();
11057
11058 let LineManipulationResult {
11059 new_text,
11060 line_count_before,
11061 line_count_after,
11062 } = manipulate(&text);
11063
11064 edits.push((start_point..end_point, new_text));
11065
11066 // Selections must change based on added and removed line count
11067 let start_row =
11068 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11069 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11070 new_selections.push(Selection {
11071 id: selection.id,
11072 start: start_row,
11073 end: end_row,
11074 goal: SelectionGoal::None,
11075 reversed: selection.reversed,
11076 });
11077
11078 if line_count_after > line_count_before {
11079 added_lines += line_count_after - line_count_before;
11080 } else if line_count_before > line_count_after {
11081 removed_lines += line_count_before - line_count_after;
11082 }
11083 }
11084
11085 self.transact(window, cx, |this, window, cx| {
11086 let buffer = this.buffer.update(cx, |buffer, cx| {
11087 buffer.edit(edits, None, cx);
11088 buffer.snapshot(cx)
11089 });
11090
11091 // Recalculate offsets on newly edited buffer
11092 let new_selections = new_selections
11093 .iter()
11094 .map(|s| {
11095 let start_point = Point::new(s.start.0, 0);
11096 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11097 Selection {
11098 id: s.id,
11099 start: buffer.point_to_offset(start_point),
11100 end: buffer.point_to_offset(end_point),
11101 goal: s.goal,
11102 reversed: s.reversed,
11103 }
11104 })
11105 .collect();
11106
11107 this.change_selections(Default::default(), window, cx, |s| {
11108 s.select(new_selections);
11109 });
11110
11111 this.request_autoscroll(Autoscroll::fit(), cx);
11112 });
11113 }
11114
11115 fn manipulate_immutable_lines<Fn>(
11116 &mut self,
11117 window: &mut Window,
11118 cx: &mut Context<Self>,
11119 mut callback: Fn,
11120 ) where
11121 Fn: FnMut(&mut Vec<&str>),
11122 {
11123 self.manipulate_lines(window, cx, |text| {
11124 let mut lines: Vec<&str> = text.split('\n').collect();
11125 let line_count_before = lines.len();
11126
11127 callback(&mut lines);
11128
11129 LineManipulationResult {
11130 new_text: lines.join("\n"),
11131 line_count_before,
11132 line_count_after: lines.len(),
11133 }
11134 });
11135 }
11136
11137 fn manipulate_mutable_lines<Fn>(
11138 &mut self,
11139 window: &mut Window,
11140 cx: &mut Context<Self>,
11141 mut callback: Fn,
11142 ) where
11143 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11144 {
11145 self.manipulate_lines(window, cx, |text| {
11146 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11147 let line_count_before = lines.len();
11148
11149 callback(&mut lines);
11150
11151 LineManipulationResult {
11152 new_text: lines.join("\n"),
11153 line_count_before,
11154 line_count_after: lines.len(),
11155 }
11156 });
11157 }
11158
11159 pub fn convert_indentation_to_spaces(
11160 &mut self,
11161 _: &ConvertIndentationToSpaces,
11162 window: &mut Window,
11163 cx: &mut Context<Self>,
11164 ) {
11165 let settings = self.buffer.read(cx).language_settings(cx);
11166 let tab_size = settings.tab_size.get() as usize;
11167
11168 self.manipulate_mutable_lines(window, cx, |lines| {
11169 // Allocates a reasonably sized scratch buffer once for the whole loop
11170 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11171 // Avoids recomputing spaces that could be inserted many times
11172 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11173 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11174 .collect();
11175
11176 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11177 let mut chars = line.as_ref().chars();
11178 let mut col = 0;
11179 let mut changed = false;
11180
11181 for ch in chars.by_ref() {
11182 match ch {
11183 ' ' => {
11184 reindented_line.push(' ');
11185 col += 1;
11186 }
11187 '\t' => {
11188 // \t are converted to spaces depending on the current column
11189 let spaces_len = tab_size - (col % tab_size);
11190 reindented_line.extend(&space_cache[spaces_len - 1]);
11191 col += spaces_len;
11192 changed = true;
11193 }
11194 _ => {
11195 // If we dont append before break, the character is consumed
11196 reindented_line.push(ch);
11197 break;
11198 }
11199 }
11200 }
11201
11202 if !changed {
11203 reindented_line.clear();
11204 continue;
11205 }
11206 // Append the rest of the line and replace old reference with new one
11207 reindented_line.extend(chars);
11208 *line = Cow::Owned(reindented_line.clone());
11209 reindented_line.clear();
11210 }
11211 });
11212 }
11213
11214 pub fn convert_indentation_to_tabs(
11215 &mut self,
11216 _: &ConvertIndentationToTabs,
11217 window: &mut Window,
11218 cx: &mut Context<Self>,
11219 ) {
11220 let settings = self.buffer.read(cx).language_settings(cx);
11221 let tab_size = settings.tab_size.get() as usize;
11222
11223 self.manipulate_mutable_lines(window, cx, |lines| {
11224 // Allocates a reasonably sized buffer once for the whole loop
11225 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11226 // Avoids recomputing spaces that could be inserted many times
11227 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11228 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11229 .collect();
11230
11231 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11232 let mut chars = line.chars();
11233 let mut spaces_count = 0;
11234 let mut first_non_indent_char = None;
11235 let mut changed = false;
11236
11237 for ch in chars.by_ref() {
11238 match ch {
11239 ' ' => {
11240 // Keep track of spaces. Append \t when we reach tab_size
11241 spaces_count += 1;
11242 changed = true;
11243 if spaces_count == tab_size {
11244 reindented_line.push('\t');
11245 spaces_count = 0;
11246 }
11247 }
11248 '\t' => {
11249 reindented_line.push('\t');
11250 spaces_count = 0;
11251 }
11252 _ => {
11253 // Dont append it yet, we might have remaining spaces
11254 first_non_indent_char = Some(ch);
11255 break;
11256 }
11257 }
11258 }
11259
11260 if !changed {
11261 reindented_line.clear();
11262 continue;
11263 }
11264 // Remaining spaces that didn't make a full tab stop
11265 if spaces_count > 0 {
11266 reindented_line.extend(&space_cache[spaces_count - 1]);
11267 }
11268 // If we consume an extra character that was not indentation, add it back
11269 if let Some(extra_char) = first_non_indent_char {
11270 reindented_line.push(extra_char);
11271 }
11272 // Append the rest of the line and replace old reference with new one
11273 reindented_line.extend(chars);
11274 *line = Cow::Owned(reindented_line.clone());
11275 reindented_line.clear();
11276 }
11277 });
11278 }
11279
11280 pub fn convert_to_upper_case(
11281 &mut self,
11282 _: &ConvertToUpperCase,
11283 window: &mut Window,
11284 cx: &mut Context<Self>,
11285 ) {
11286 self.manipulate_text(window, cx, |text| text.to_uppercase())
11287 }
11288
11289 pub fn convert_to_lower_case(
11290 &mut self,
11291 _: &ConvertToLowerCase,
11292 window: &mut Window,
11293 cx: &mut Context<Self>,
11294 ) {
11295 self.manipulate_text(window, cx, |text| text.to_lowercase())
11296 }
11297
11298 pub fn convert_to_title_case(
11299 &mut self,
11300 _: &ConvertToTitleCase,
11301 window: &mut Window,
11302 cx: &mut Context<Self>,
11303 ) {
11304 self.manipulate_text(window, cx, |text| {
11305 text.split('\n')
11306 .map(|line| line.to_case(Case::Title))
11307 .join("\n")
11308 })
11309 }
11310
11311 pub fn convert_to_snake_case(
11312 &mut self,
11313 _: &ConvertToSnakeCase,
11314 window: &mut Window,
11315 cx: &mut Context<Self>,
11316 ) {
11317 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11318 }
11319
11320 pub fn convert_to_kebab_case(
11321 &mut self,
11322 _: &ConvertToKebabCase,
11323 window: &mut Window,
11324 cx: &mut Context<Self>,
11325 ) {
11326 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11327 }
11328
11329 pub fn convert_to_upper_camel_case(
11330 &mut self,
11331 _: &ConvertToUpperCamelCase,
11332 window: &mut Window,
11333 cx: &mut Context<Self>,
11334 ) {
11335 self.manipulate_text(window, cx, |text| {
11336 text.split('\n')
11337 .map(|line| line.to_case(Case::UpperCamel))
11338 .join("\n")
11339 })
11340 }
11341
11342 pub fn convert_to_lower_camel_case(
11343 &mut self,
11344 _: &ConvertToLowerCamelCase,
11345 window: &mut Window,
11346 cx: &mut Context<Self>,
11347 ) {
11348 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11349 }
11350
11351 pub fn convert_to_opposite_case(
11352 &mut self,
11353 _: &ConvertToOppositeCase,
11354 window: &mut Window,
11355 cx: &mut Context<Self>,
11356 ) {
11357 self.manipulate_text(window, cx, |text| {
11358 text.chars()
11359 .fold(String::with_capacity(text.len()), |mut t, c| {
11360 if c.is_uppercase() {
11361 t.extend(c.to_lowercase());
11362 } else {
11363 t.extend(c.to_uppercase());
11364 }
11365 t
11366 })
11367 })
11368 }
11369
11370 pub fn convert_to_sentence_case(
11371 &mut self,
11372 _: &ConvertToSentenceCase,
11373 window: &mut Window,
11374 cx: &mut Context<Self>,
11375 ) {
11376 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11377 }
11378
11379 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11380 self.manipulate_text(window, cx, |text| {
11381 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11382 if has_upper_case_characters {
11383 text.to_lowercase()
11384 } else {
11385 text.to_uppercase()
11386 }
11387 })
11388 }
11389
11390 pub fn convert_to_rot13(
11391 &mut self,
11392 _: &ConvertToRot13,
11393 window: &mut Window,
11394 cx: &mut Context<Self>,
11395 ) {
11396 self.manipulate_text(window, cx, |text| {
11397 text.chars()
11398 .map(|c| match c {
11399 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11400 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11401 _ => c,
11402 })
11403 .collect()
11404 })
11405 }
11406
11407 pub fn convert_to_rot47(
11408 &mut self,
11409 _: &ConvertToRot47,
11410 window: &mut Window,
11411 cx: &mut Context<Self>,
11412 ) {
11413 self.manipulate_text(window, cx, |text| {
11414 text.chars()
11415 .map(|c| {
11416 let code_point = c as u32;
11417 if code_point >= 33 && code_point <= 126 {
11418 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11419 }
11420 c
11421 })
11422 .collect()
11423 })
11424 }
11425
11426 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11427 where
11428 Fn: FnMut(&str) -> String,
11429 {
11430 let buffer = self.buffer.read(cx).snapshot(cx);
11431
11432 let mut new_selections = Vec::new();
11433 let mut edits = Vec::new();
11434 let mut selection_adjustment = 0i32;
11435
11436 for selection in self.selections.all_adjusted(cx) {
11437 let selection_is_empty = selection.is_empty();
11438
11439 let (start, end) = if selection_is_empty {
11440 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11441 (word_range.start, word_range.end)
11442 } else {
11443 (
11444 buffer.point_to_offset(selection.start),
11445 buffer.point_to_offset(selection.end),
11446 )
11447 };
11448
11449 let text = buffer.text_for_range(start..end).collect::<String>();
11450 let old_length = text.len() as i32;
11451 let text = callback(&text);
11452
11453 new_selections.push(Selection {
11454 start: (start as i32 - selection_adjustment) as usize,
11455 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11456 goal: SelectionGoal::None,
11457 id: selection.id,
11458 reversed: selection.reversed,
11459 });
11460
11461 selection_adjustment += old_length - text.len() as i32;
11462
11463 edits.push((start..end, text));
11464 }
11465
11466 self.transact(window, cx, |this, window, cx| {
11467 this.buffer.update(cx, |buffer, cx| {
11468 buffer.edit(edits, None, cx);
11469 });
11470
11471 this.change_selections(Default::default(), window, cx, |s| {
11472 s.select(new_selections);
11473 });
11474
11475 this.request_autoscroll(Autoscroll::fit(), cx);
11476 });
11477 }
11478
11479 pub fn move_selection_on_drop(
11480 &mut self,
11481 selection: &Selection<Anchor>,
11482 target: DisplayPoint,
11483 is_cut: bool,
11484 window: &mut Window,
11485 cx: &mut Context<Self>,
11486 ) {
11487 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11488 let buffer = &display_map.buffer_snapshot;
11489 let mut edits = Vec::new();
11490 let insert_point = display_map
11491 .clip_point(target, Bias::Left)
11492 .to_point(&display_map);
11493 let text = buffer
11494 .text_for_range(selection.start..selection.end)
11495 .collect::<String>();
11496 if is_cut {
11497 edits.push(((selection.start..selection.end), String::new()));
11498 }
11499 let insert_anchor = buffer.anchor_before(insert_point);
11500 edits.push(((insert_anchor..insert_anchor), text));
11501 let last_edit_start = insert_anchor.bias_left(buffer);
11502 let last_edit_end = insert_anchor.bias_right(buffer);
11503 self.transact(window, cx, |this, window, cx| {
11504 this.buffer.update(cx, |buffer, cx| {
11505 buffer.edit(edits, None, cx);
11506 });
11507 this.change_selections(Default::default(), window, cx, |s| {
11508 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11509 });
11510 });
11511 }
11512
11513 pub fn clear_selection_drag_state(&mut self) {
11514 self.selection_drag_state = SelectionDragState::None;
11515 }
11516
11517 pub fn duplicate(
11518 &mut self,
11519 upwards: bool,
11520 whole_lines: bool,
11521 window: &mut Window,
11522 cx: &mut Context<Self>,
11523 ) {
11524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11525
11526 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11527 let buffer = &display_map.buffer_snapshot;
11528 let selections = self.selections.all::<Point>(cx);
11529
11530 let mut edits = Vec::new();
11531 let mut selections_iter = selections.iter().peekable();
11532 while let Some(selection) = selections_iter.next() {
11533 let mut rows = selection.spanned_rows(false, &display_map);
11534 // duplicate line-wise
11535 if whole_lines || selection.start == selection.end {
11536 // Avoid duplicating the same lines twice.
11537 while let Some(next_selection) = selections_iter.peek() {
11538 let next_rows = next_selection.spanned_rows(false, &display_map);
11539 if next_rows.start < rows.end {
11540 rows.end = next_rows.end;
11541 selections_iter.next().unwrap();
11542 } else {
11543 break;
11544 }
11545 }
11546
11547 // Copy the text from the selected row region and splice it either at the start
11548 // or end of the region.
11549 let start = Point::new(rows.start.0, 0);
11550 let end = Point::new(
11551 rows.end.previous_row().0,
11552 buffer.line_len(rows.end.previous_row()),
11553 );
11554 let text = buffer
11555 .text_for_range(start..end)
11556 .chain(Some("\n"))
11557 .collect::<String>();
11558 let insert_location = if upwards {
11559 Point::new(rows.end.0, 0)
11560 } else {
11561 start
11562 };
11563 edits.push((insert_location..insert_location, text));
11564 } else {
11565 // duplicate character-wise
11566 let start = selection.start;
11567 let end = selection.end;
11568 let text = buffer.text_for_range(start..end).collect::<String>();
11569 edits.push((selection.end..selection.end, text));
11570 }
11571 }
11572
11573 self.transact(window, cx, |this, _, cx| {
11574 this.buffer.update(cx, |buffer, cx| {
11575 buffer.edit(edits, None, cx);
11576 });
11577
11578 this.request_autoscroll(Autoscroll::fit(), cx);
11579 });
11580 }
11581
11582 pub fn duplicate_line_up(
11583 &mut self,
11584 _: &DuplicateLineUp,
11585 window: &mut Window,
11586 cx: &mut Context<Self>,
11587 ) {
11588 self.duplicate(true, true, window, cx);
11589 }
11590
11591 pub fn duplicate_line_down(
11592 &mut self,
11593 _: &DuplicateLineDown,
11594 window: &mut Window,
11595 cx: &mut Context<Self>,
11596 ) {
11597 self.duplicate(false, true, window, cx);
11598 }
11599
11600 pub fn duplicate_selection(
11601 &mut self,
11602 _: &DuplicateSelection,
11603 window: &mut Window,
11604 cx: &mut Context<Self>,
11605 ) {
11606 self.duplicate(false, false, window, cx);
11607 }
11608
11609 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11610 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11611 if self.mode.is_single_line() {
11612 cx.propagate();
11613 return;
11614 }
11615
11616 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11617 let buffer = self.buffer.read(cx).snapshot(cx);
11618
11619 let mut edits = Vec::new();
11620 let mut unfold_ranges = Vec::new();
11621 let mut refold_creases = Vec::new();
11622
11623 let selections = self.selections.all::<Point>(cx);
11624 let mut selections = selections.iter().peekable();
11625 let mut contiguous_row_selections = Vec::new();
11626 let mut new_selections = Vec::new();
11627
11628 while let Some(selection) = selections.next() {
11629 // Find all the selections that span a contiguous row range
11630 let (start_row, end_row) = consume_contiguous_rows(
11631 &mut contiguous_row_selections,
11632 selection,
11633 &display_map,
11634 &mut selections,
11635 );
11636
11637 // Move the text spanned by the row range to be before the line preceding the row range
11638 if start_row.0 > 0 {
11639 let range_to_move = Point::new(
11640 start_row.previous_row().0,
11641 buffer.line_len(start_row.previous_row()),
11642 )
11643 ..Point::new(
11644 end_row.previous_row().0,
11645 buffer.line_len(end_row.previous_row()),
11646 );
11647 let insertion_point = display_map
11648 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11649 .0;
11650
11651 // Don't move lines across excerpts
11652 if buffer
11653 .excerpt_containing(insertion_point..range_to_move.end)
11654 .is_some()
11655 {
11656 let text = buffer
11657 .text_for_range(range_to_move.clone())
11658 .flat_map(|s| s.chars())
11659 .skip(1)
11660 .chain(['\n'])
11661 .collect::<String>();
11662
11663 edits.push((
11664 buffer.anchor_after(range_to_move.start)
11665 ..buffer.anchor_before(range_to_move.end),
11666 String::new(),
11667 ));
11668 let insertion_anchor = buffer.anchor_after(insertion_point);
11669 edits.push((insertion_anchor..insertion_anchor, text));
11670
11671 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11672
11673 // Move selections up
11674 new_selections.extend(contiguous_row_selections.drain(..).map(
11675 |mut selection| {
11676 selection.start.row -= row_delta;
11677 selection.end.row -= row_delta;
11678 selection
11679 },
11680 ));
11681
11682 // Move folds up
11683 unfold_ranges.push(range_to_move.clone());
11684 for fold in display_map.folds_in_range(
11685 buffer.anchor_before(range_to_move.start)
11686 ..buffer.anchor_after(range_to_move.end),
11687 ) {
11688 let mut start = fold.range.start.to_point(&buffer);
11689 let mut end = fold.range.end.to_point(&buffer);
11690 start.row -= row_delta;
11691 end.row -= row_delta;
11692 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11693 }
11694 }
11695 }
11696
11697 // If we didn't move line(s), preserve the existing selections
11698 new_selections.append(&mut contiguous_row_selections);
11699 }
11700
11701 self.transact(window, cx, |this, window, cx| {
11702 this.unfold_ranges(&unfold_ranges, true, true, cx);
11703 this.buffer.update(cx, |buffer, cx| {
11704 for (range, text) in edits {
11705 buffer.edit([(range, text)], None, cx);
11706 }
11707 });
11708 this.fold_creases(refold_creases, true, window, cx);
11709 this.change_selections(Default::default(), window, cx, |s| {
11710 s.select(new_selections);
11711 })
11712 });
11713 }
11714
11715 pub fn move_line_down(
11716 &mut self,
11717 _: &MoveLineDown,
11718 window: &mut Window,
11719 cx: &mut Context<Self>,
11720 ) {
11721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11722 if self.mode.is_single_line() {
11723 cx.propagate();
11724 return;
11725 }
11726
11727 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11728 let buffer = self.buffer.read(cx).snapshot(cx);
11729
11730 let mut edits = Vec::new();
11731 let mut unfold_ranges = Vec::new();
11732 let mut refold_creases = Vec::new();
11733
11734 let selections = self.selections.all::<Point>(cx);
11735 let mut selections = selections.iter().peekable();
11736 let mut contiguous_row_selections = Vec::new();
11737 let mut new_selections = Vec::new();
11738
11739 while let Some(selection) = selections.next() {
11740 // Find all the selections that span a contiguous row range
11741 let (start_row, end_row) = consume_contiguous_rows(
11742 &mut contiguous_row_selections,
11743 selection,
11744 &display_map,
11745 &mut selections,
11746 );
11747
11748 // Move the text spanned by the row range to be after the last line of the row range
11749 if end_row.0 <= buffer.max_point().row {
11750 let range_to_move =
11751 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11752 let insertion_point = display_map
11753 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11754 .0;
11755
11756 // Don't move lines across excerpt boundaries
11757 if buffer
11758 .excerpt_containing(range_to_move.start..insertion_point)
11759 .is_some()
11760 {
11761 let mut text = String::from("\n");
11762 text.extend(buffer.text_for_range(range_to_move.clone()));
11763 text.pop(); // Drop trailing newline
11764 edits.push((
11765 buffer.anchor_after(range_to_move.start)
11766 ..buffer.anchor_before(range_to_move.end),
11767 String::new(),
11768 ));
11769 let insertion_anchor = buffer.anchor_after(insertion_point);
11770 edits.push((insertion_anchor..insertion_anchor, text));
11771
11772 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11773
11774 // Move selections down
11775 new_selections.extend(contiguous_row_selections.drain(..).map(
11776 |mut selection| {
11777 selection.start.row += row_delta;
11778 selection.end.row += row_delta;
11779 selection
11780 },
11781 ));
11782
11783 // Move folds down
11784 unfold_ranges.push(range_to_move.clone());
11785 for fold in display_map.folds_in_range(
11786 buffer.anchor_before(range_to_move.start)
11787 ..buffer.anchor_after(range_to_move.end),
11788 ) {
11789 let mut start = fold.range.start.to_point(&buffer);
11790 let mut end = fold.range.end.to_point(&buffer);
11791 start.row += row_delta;
11792 end.row += row_delta;
11793 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11794 }
11795 }
11796 }
11797
11798 // If we didn't move line(s), preserve the existing selections
11799 new_selections.append(&mut contiguous_row_selections);
11800 }
11801
11802 self.transact(window, cx, |this, window, cx| {
11803 this.unfold_ranges(&unfold_ranges, true, true, cx);
11804 this.buffer.update(cx, |buffer, cx| {
11805 for (range, text) in edits {
11806 buffer.edit([(range, text)], None, cx);
11807 }
11808 });
11809 this.fold_creases(refold_creases, true, window, cx);
11810 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11811 });
11812 }
11813
11814 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11816 let text_layout_details = &self.text_layout_details(window);
11817 self.transact(window, cx, |this, window, cx| {
11818 let edits = this.change_selections(Default::default(), window, cx, |s| {
11819 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11820 s.move_with(|display_map, selection| {
11821 if !selection.is_empty() {
11822 return;
11823 }
11824
11825 let mut head = selection.head();
11826 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11827 if head.column() == display_map.line_len(head.row()) {
11828 transpose_offset = display_map
11829 .buffer_snapshot
11830 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11831 }
11832
11833 if transpose_offset == 0 {
11834 return;
11835 }
11836
11837 *head.column_mut() += 1;
11838 head = display_map.clip_point(head, Bias::Right);
11839 let goal = SelectionGoal::HorizontalPosition(
11840 display_map
11841 .x_for_display_point(head, text_layout_details)
11842 .into(),
11843 );
11844 selection.collapse_to(head, goal);
11845
11846 let transpose_start = display_map
11847 .buffer_snapshot
11848 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11849 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11850 let transpose_end = display_map
11851 .buffer_snapshot
11852 .clip_offset(transpose_offset + 1, Bias::Right);
11853 if let Some(ch) =
11854 display_map.buffer_snapshot.chars_at(transpose_start).next()
11855 {
11856 edits.push((transpose_start..transpose_offset, String::new()));
11857 edits.push((transpose_end..transpose_end, ch.to_string()));
11858 }
11859 }
11860 });
11861 edits
11862 });
11863 this.buffer
11864 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11865 let selections = this.selections.all::<usize>(cx);
11866 this.change_selections(Default::default(), window, cx, |s| {
11867 s.select(selections);
11868 });
11869 });
11870 }
11871
11872 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11874 if self.mode.is_single_line() {
11875 cx.propagate();
11876 return;
11877 }
11878
11879 self.rewrap_impl(RewrapOptions::default(), cx)
11880 }
11881
11882 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11883 let buffer = self.buffer.read(cx).snapshot(cx);
11884 let selections = self.selections.all::<Point>(cx);
11885
11886 #[derive(Clone, Debug, PartialEq)]
11887 enum CommentFormat {
11888 /// single line comment, with prefix for line
11889 Line(String),
11890 /// single line within a block comment, with prefix for line
11891 BlockLine(String),
11892 /// a single line of a block comment that includes the initial delimiter
11893 BlockCommentWithStart(BlockCommentConfig),
11894 /// a single line of a block comment that includes the ending delimiter
11895 BlockCommentWithEnd(BlockCommentConfig),
11896 }
11897
11898 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11899 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11900 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11901 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11902 .peekable();
11903
11904 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11905 row
11906 } else {
11907 return Vec::new();
11908 };
11909
11910 let language_settings = buffer.language_settings_at(selection.head(), cx);
11911 let language_scope = buffer.language_scope_at(selection.head());
11912
11913 let indent_and_prefix_for_row =
11914 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11915 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11916 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11917 &language_scope
11918 {
11919 let indent_end = Point::new(row, indent.len);
11920 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11921 let line_text_after_indent = buffer
11922 .text_for_range(indent_end..line_end)
11923 .collect::<String>();
11924
11925 let is_within_comment_override = buffer
11926 .language_scope_at(indent_end)
11927 .is_some_and(|scope| scope.override_name() == Some("comment"));
11928 let comment_delimiters = if is_within_comment_override {
11929 // we are within a comment syntax node, but we don't
11930 // yet know what kind of comment: block, doc or line
11931 match (
11932 language_scope.documentation_comment(),
11933 language_scope.block_comment(),
11934 ) {
11935 (Some(config), _) | (_, Some(config))
11936 if buffer.contains_str_at(indent_end, &config.start) =>
11937 {
11938 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11939 }
11940 (Some(config), _) | (_, Some(config))
11941 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11942 {
11943 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11944 }
11945 (Some(config), _) | (_, Some(config))
11946 if buffer.contains_str_at(indent_end, &config.prefix) =>
11947 {
11948 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11949 }
11950 (_, _) => language_scope
11951 .line_comment_prefixes()
11952 .iter()
11953 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11954 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11955 }
11956 } else {
11957 // we not in an overridden comment node, but we may
11958 // be within a non-overridden line comment node
11959 language_scope
11960 .line_comment_prefixes()
11961 .iter()
11962 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11963 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11964 };
11965
11966 let rewrap_prefix = language_scope
11967 .rewrap_prefixes()
11968 .iter()
11969 .find_map(|prefix_regex| {
11970 prefix_regex.find(&line_text_after_indent).map(|mat| {
11971 if mat.start() == 0 {
11972 Some(mat.as_str().to_string())
11973 } else {
11974 None
11975 }
11976 })
11977 })
11978 .flatten();
11979 (comment_delimiters, rewrap_prefix)
11980 } else {
11981 (None, None)
11982 };
11983 (indent, comment_prefix, rewrap_prefix)
11984 };
11985
11986 let mut ranges = Vec::new();
11987 let from_empty_selection = selection.is_empty();
11988
11989 let mut current_range_start = first_row;
11990 let mut prev_row = first_row;
11991 let (
11992 mut current_range_indent,
11993 mut current_range_comment_delimiters,
11994 mut current_range_rewrap_prefix,
11995 ) = indent_and_prefix_for_row(first_row);
11996
11997 for row in non_blank_rows_iter.skip(1) {
11998 let has_paragraph_break = row > prev_row + 1;
11999
12000 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12001 indent_and_prefix_for_row(row);
12002
12003 let has_indent_change = row_indent != current_range_indent;
12004 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12005
12006 let has_boundary_change = has_comment_change
12007 || row_rewrap_prefix.is_some()
12008 || (has_indent_change && current_range_comment_delimiters.is_some());
12009
12010 if has_paragraph_break || has_boundary_change {
12011 ranges.push((
12012 language_settings.clone(),
12013 Point::new(current_range_start, 0)
12014 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12015 current_range_indent,
12016 current_range_comment_delimiters.clone(),
12017 current_range_rewrap_prefix.clone(),
12018 from_empty_selection,
12019 ));
12020 current_range_start = row;
12021 current_range_indent = row_indent;
12022 current_range_comment_delimiters = row_comment_delimiters;
12023 current_range_rewrap_prefix = row_rewrap_prefix;
12024 }
12025 prev_row = row;
12026 }
12027
12028 ranges.push((
12029 language_settings.clone(),
12030 Point::new(current_range_start, 0)
12031 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12032 current_range_indent,
12033 current_range_comment_delimiters,
12034 current_range_rewrap_prefix,
12035 from_empty_selection,
12036 ));
12037
12038 ranges
12039 });
12040
12041 let mut edits = Vec::new();
12042 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12043
12044 for (
12045 language_settings,
12046 wrap_range,
12047 mut indent_size,
12048 comment_prefix,
12049 rewrap_prefix,
12050 from_empty_selection,
12051 ) in wrap_ranges
12052 {
12053 let mut start_row = wrap_range.start.row;
12054 let mut end_row = wrap_range.end.row;
12055
12056 // Skip selections that overlap with a range that has already been rewrapped.
12057 let selection_range = start_row..end_row;
12058 if rewrapped_row_ranges
12059 .iter()
12060 .any(|range| range.overlaps(&selection_range))
12061 {
12062 continue;
12063 }
12064
12065 let tab_size = language_settings.tab_size;
12066
12067 let (line_prefix, inside_comment) = match &comment_prefix {
12068 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12069 (Some(prefix.as_str()), true)
12070 }
12071 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12072 (Some(prefix.as_ref()), true)
12073 }
12074 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12075 start: _,
12076 end: _,
12077 prefix,
12078 tab_size,
12079 })) => {
12080 indent_size.len += tab_size;
12081 (Some(prefix.as_ref()), true)
12082 }
12083 None => (None, false),
12084 };
12085 let indent_prefix = indent_size.chars().collect::<String>();
12086 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12087
12088 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12089 RewrapBehavior::InComments => inside_comment,
12090 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12091 RewrapBehavior::Anywhere => true,
12092 };
12093
12094 let should_rewrap = options.override_language_settings
12095 || allow_rewrap_based_on_language
12096 || self.hard_wrap.is_some();
12097 if !should_rewrap {
12098 continue;
12099 }
12100
12101 if from_empty_selection {
12102 'expand_upwards: while start_row > 0 {
12103 let prev_row = start_row - 1;
12104 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12105 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12106 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12107 {
12108 start_row = prev_row;
12109 } else {
12110 break 'expand_upwards;
12111 }
12112 }
12113
12114 'expand_downwards: while end_row < buffer.max_point().row {
12115 let next_row = end_row + 1;
12116 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12117 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12118 && !buffer.is_line_blank(MultiBufferRow(next_row))
12119 {
12120 end_row = next_row;
12121 } else {
12122 break 'expand_downwards;
12123 }
12124 }
12125 }
12126
12127 let start = Point::new(start_row, 0);
12128 let start_offset = start.to_offset(&buffer);
12129 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12130 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12131 let mut first_line_delimiter = None;
12132 let mut last_line_delimiter = None;
12133 let Some(lines_without_prefixes) = selection_text
12134 .lines()
12135 .enumerate()
12136 .map(|(ix, line)| {
12137 let line_trimmed = line.trim_start();
12138 if rewrap_prefix.is_some() && ix > 0 {
12139 Ok(line_trimmed)
12140 } else if let Some(
12141 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12142 start,
12143 prefix,
12144 end,
12145 tab_size,
12146 })
12147 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12148 start,
12149 prefix,
12150 end,
12151 tab_size,
12152 }),
12153 ) = &comment_prefix
12154 {
12155 let line_trimmed = line_trimmed
12156 .strip_prefix(start.as_ref())
12157 .map(|s| {
12158 let mut indent_size = indent_size;
12159 indent_size.len -= tab_size;
12160 let indent_prefix: String = indent_size.chars().collect();
12161 first_line_delimiter = Some((indent_prefix, start));
12162 s.trim_start()
12163 })
12164 .unwrap_or(line_trimmed);
12165 let line_trimmed = line_trimmed
12166 .strip_suffix(end.as_ref())
12167 .map(|s| {
12168 last_line_delimiter = Some(end);
12169 s.trim_end()
12170 })
12171 .unwrap_or(line_trimmed);
12172 let line_trimmed = line_trimmed
12173 .strip_prefix(prefix.as_ref())
12174 .unwrap_or(line_trimmed);
12175 Ok(line_trimmed)
12176 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12177 line_trimmed.strip_prefix(prefix).with_context(|| {
12178 format!("line did not start with prefix {prefix:?}: {line:?}")
12179 })
12180 } else {
12181 line_trimmed
12182 .strip_prefix(&line_prefix.trim_start())
12183 .with_context(|| {
12184 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12185 })
12186 }
12187 })
12188 .collect::<Result<Vec<_>, _>>()
12189 .log_err()
12190 else {
12191 continue;
12192 };
12193
12194 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12195 buffer
12196 .language_settings_at(Point::new(start_row, 0), cx)
12197 .preferred_line_length as usize
12198 });
12199
12200 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12201 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12202 } else {
12203 line_prefix.clone()
12204 };
12205
12206 let wrapped_text = {
12207 let mut wrapped_text = wrap_with_prefix(
12208 line_prefix,
12209 subsequent_lines_prefix,
12210 lines_without_prefixes.join("\n"),
12211 wrap_column,
12212 tab_size,
12213 options.preserve_existing_whitespace,
12214 );
12215
12216 if let Some((indent, delimiter)) = first_line_delimiter {
12217 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12218 }
12219 if let Some(last_line) = last_line_delimiter {
12220 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12221 }
12222
12223 wrapped_text
12224 };
12225
12226 // TODO: should always use char-based diff while still supporting cursor behavior that
12227 // matches vim.
12228 let mut diff_options = DiffOptions::default();
12229 if options.override_language_settings {
12230 diff_options.max_word_diff_len = 0;
12231 diff_options.max_word_diff_line_count = 0;
12232 } else {
12233 diff_options.max_word_diff_len = usize::MAX;
12234 diff_options.max_word_diff_line_count = usize::MAX;
12235 }
12236
12237 for (old_range, new_text) in
12238 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12239 {
12240 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12241 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12242 edits.push((edit_start..edit_end, new_text));
12243 }
12244
12245 rewrapped_row_ranges.push(start_row..=end_row);
12246 }
12247
12248 self.buffer
12249 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12250 }
12251
12252 pub fn cut_common(
12253 &mut self,
12254 cut_no_selection_line: bool,
12255 window: &mut Window,
12256 cx: &mut Context<Self>,
12257 ) -> ClipboardItem {
12258 let mut text = String::new();
12259 let buffer = self.buffer.read(cx).snapshot(cx);
12260 let mut selections = self.selections.all::<Point>(cx);
12261 let mut clipboard_selections = Vec::with_capacity(selections.len());
12262 {
12263 let max_point = buffer.max_point();
12264 let mut is_first = true;
12265 for selection in &mut selections {
12266 let is_entire_line =
12267 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode;
12268 if is_entire_line {
12269 selection.start = Point::new(selection.start.row, 0);
12270 if !selection.is_empty() && selection.end.column == 0 {
12271 selection.end = cmp::min(max_point, selection.end);
12272 } else {
12273 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12274 }
12275 selection.goal = SelectionGoal::None;
12276 }
12277 if is_first {
12278 is_first = false;
12279 } else {
12280 text += "\n";
12281 }
12282 let mut len = 0;
12283 for chunk in buffer.text_for_range(selection.start..selection.end) {
12284 text.push_str(chunk);
12285 len += chunk.len();
12286 }
12287 clipboard_selections.push(ClipboardSelection {
12288 len,
12289 is_entire_line,
12290 first_line_indent: buffer
12291 .indent_size_for_line(MultiBufferRow(selection.start.row))
12292 .len,
12293 });
12294 }
12295 }
12296
12297 self.transact(window, cx, |this, window, cx| {
12298 this.change_selections(Default::default(), window, cx, |s| {
12299 s.select(selections);
12300 });
12301 this.insert("", window, cx);
12302 });
12303 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12304 }
12305
12306 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12307 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12308 let item = self.cut_common(true, window, cx);
12309 cx.write_to_clipboard(item);
12310 }
12311
12312 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12313 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12314 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12315 s.move_with(|snapshot, sel| {
12316 if sel.is_empty() {
12317 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12318 }
12319 if sel.is_empty() {
12320 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12321 }
12322 });
12323 });
12324 let item = self.cut_common(true, window, cx);
12325 cx.set_global(KillRing(item))
12326 }
12327
12328 pub fn kill_ring_yank(
12329 &mut self,
12330 _: &KillRingYank,
12331 window: &mut Window,
12332 cx: &mut Context<Self>,
12333 ) {
12334 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12335 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12336 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12337 (kill_ring.text().to_string(), kill_ring.metadata_json())
12338 } else {
12339 return;
12340 }
12341 } else {
12342 return;
12343 };
12344 self.do_paste(&text, metadata, false, window, cx);
12345 }
12346
12347 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12348 self.do_copy(true, cx);
12349 }
12350
12351 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12352 self.do_copy(false, cx);
12353 }
12354
12355 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12356 let selections = self.selections.all::<Point>(cx);
12357 let buffer = self.buffer.read(cx).read(cx);
12358 let mut text = String::new();
12359
12360 let mut clipboard_selections = Vec::with_capacity(selections.len());
12361 {
12362 let max_point = buffer.max_point();
12363 let mut is_first = true;
12364 for selection in &selections {
12365 let mut start = selection.start;
12366 let mut end = selection.end;
12367 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12368 if is_entire_line {
12369 start = Point::new(start.row, 0);
12370 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12371 }
12372
12373 let mut trimmed_selections = Vec::new();
12374 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12375 let row = MultiBufferRow(start.row);
12376 let first_indent = buffer.indent_size_for_line(row);
12377 if first_indent.len == 0 || start.column > first_indent.len {
12378 trimmed_selections.push(start..end);
12379 } else {
12380 trimmed_selections.push(
12381 Point::new(row.0, first_indent.len)
12382 ..Point::new(row.0, buffer.line_len(row)),
12383 );
12384 for row in start.row + 1..=end.row {
12385 let mut line_len = buffer.line_len(MultiBufferRow(row));
12386 if row == end.row {
12387 line_len = end.column;
12388 }
12389 if line_len == 0 {
12390 trimmed_selections
12391 .push(Point::new(row, 0)..Point::new(row, line_len));
12392 continue;
12393 }
12394 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12395 if row_indent_size.len >= first_indent.len {
12396 trimmed_selections.push(
12397 Point::new(row, first_indent.len)..Point::new(row, line_len),
12398 );
12399 } else {
12400 trimmed_selections.clear();
12401 trimmed_selections.push(start..end);
12402 break;
12403 }
12404 }
12405 }
12406 } else {
12407 trimmed_selections.push(start..end);
12408 }
12409
12410 for trimmed_range in trimmed_selections {
12411 if is_first {
12412 is_first = false;
12413 } else {
12414 text += "\n";
12415 }
12416 let mut len = 0;
12417 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12418 text.push_str(chunk);
12419 len += chunk.len();
12420 }
12421 clipboard_selections.push(ClipboardSelection {
12422 len,
12423 is_entire_line,
12424 first_line_indent: buffer
12425 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12426 .len,
12427 });
12428 }
12429 }
12430 }
12431
12432 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12433 text,
12434 clipboard_selections,
12435 ));
12436 }
12437
12438 pub fn do_paste(
12439 &mut self,
12440 text: &String,
12441 clipboard_selections: Option<Vec<ClipboardSelection>>,
12442 handle_entire_lines: bool,
12443 window: &mut Window,
12444 cx: &mut Context<Self>,
12445 ) {
12446 if self.read_only(cx) {
12447 return;
12448 }
12449
12450 let clipboard_text = Cow::Borrowed(text);
12451
12452 self.transact(window, cx, |this, window, cx| {
12453 let had_active_edit_prediction = this.has_active_edit_prediction();
12454
12455 if let Some(mut clipboard_selections) = clipboard_selections {
12456 let old_selections = this.selections.all::<usize>(cx);
12457 let all_selections_were_entire_line =
12458 clipboard_selections.iter().all(|s| s.is_entire_line);
12459 let first_selection_indent_column =
12460 clipboard_selections.first().map(|s| s.first_line_indent);
12461 if clipboard_selections.len() != old_selections.len() {
12462 clipboard_selections.drain(..);
12463 }
12464 let cursor_offset = this.selections.last::<usize>(cx).head();
12465 let mut auto_indent_on_paste = true;
12466
12467 this.buffer.update(cx, |buffer, cx| {
12468 let snapshot = buffer.read(cx);
12469 auto_indent_on_paste = snapshot
12470 .language_settings_at(cursor_offset, cx)
12471 .auto_indent_on_paste;
12472
12473 let mut start_offset = 0;
12474 let mut edits = Vec::new();
12475 let mut original_indent_columns = Vec::new();
12476 for (ix, selection) in old_selections.iter().enumerate() {
12477 let to_insert;
12478 let entire_line;
12479 let original_indent_column;
12480 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12481 let end_offset = start_offset + clipboard_selection.len;
12482 to_insert = &clipboard_text[start_offset..end_offset];
12483 entire_line = clipboard_selection.is_entire_line;
12484 start_offset = end_offset + 1;
12485 original_indent_column = Some(clipboard_selection.first_line_indent);
12486 } else {
12487 to_insert = clipboard_text.as_str();
12488 entire_line = all_selections_were_entire_line;
12489 original_indent_column = first_selection_indent_column
12490 }
12491
12492 // If the corresponding selection was empty when this slice of the
12493 // clipboard text was written, then the entire line containing the
12494 // selection was copied. If this selection is also currently empty,
12495 // then paste the line before the current line of the buffer.
12496 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12497 let column = selection.start.to_point(&snapshot).column as usize;
12498 let line_start = selection.start - column;
12499 line_start..line_start
12500 } else {
12501 selection.range()
12502 };
12503
12504 edits.push((range, to_insert));
12505 original_indent_columns.push(original_indent_column);
12506 }
12507 drop(snapshot);
12508
12509 buffer.edit(
12510 edits,
12511 if auto_indent_on_paste {
12512 Some(AutoindentMode::Block {
12513 original_indent_columns,
12514 })
12515 } else {
12516 None
12517 },
12518 cx,
12519 );
12520 });
12521
12522 let selections = this.selections.all::<usize>(cx);
12523 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12524 } else {
12525 this.insert(&clipboard_text, window, cx);
12526 }
12527
12528 let trigger_in_words =
12529 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12530
12531 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12532 });
12533 }
12534
12535 pub fn diff_clipboard_with_selection(
12536 &mut self,
12537 _: &DiffClipboardWithSelection,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 ) {
12541 let selections = self.selections.all::<usize>(cx);
12542
12543 if selections.is_empty() {
12544 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12545 return;
12546 };
12547
12548 let clipboard_text = match cx.read_from_clipboard() {
12549 Some(item) => match item.entries().first() {
12550 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12551 _ => None,
12552 },
12553 None => None,
12554 };
12555
12556 let Some(clipboard_text) = clipboard_text else {
12557 log::warn!("Clipboard doesn't contain text.");
12558 return;
12559 };
12560
12561 window.dispatch_action(
12562 Box::new(DiffClipboardWithSelectionData {
12563 clipboard_text,
12564 editor: cx.entity(),
12565 }),
12566 cx,
12567 );
12568 }
12569
12570 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12571 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12572 if let Some(item) = cx.read_from_clipboard() {
12573 let entries = item.entries();
12574
12575 match entries.first() {
12576 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12577 // of all the pasted entries.
12578 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12579 .do_paste(
12580 clipboard_string.text(),
12581 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12582 true,
12583 window,
12584 cx,
12585 ),
12586 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12587 }
12588 }
12589 }
12590
12591 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12592 if self.read_only(cx) {
12593 return;
12594 }
12595
12596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12597
12598 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12599 if let Some((selections, _)) =
12600 self.selection_history.transaction(transaction_id).cloned()
12601 {
12602 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12603 s.select_anchors(selections.to_vec());
12604 });
12605 } else {
12606 log::error!(
12607 "No entry in selection_history found for undo. \
12608 This may correspond to a bug where undo does not update the selection. \
12609 If this is occurring, please add details to \
12610 https://github.com/zed-industries/zed/issues/22692"
12611 );
12612 }
12613 self.request_autoscroll(Autoscroll::fit(), cx);
12614 self.unmark_text(window, cx);
12615 self.refresh_edit_prediction(true, false, window, cx);
12616 cx.emit(EditorEvent::Edited { transaction_id });
12617 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12618 }
12619 }
12620
12621 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12622 if self.read_only(cx) {
12623 return;
12624 }
12625
12626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12627
12628 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12629 if let Some((_, Some(selections))) =
12630 self.selection_history.transaction(transaction_id).cloned()
12631 {
12632 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12633 s.select_anchors(selections.to_vec());
12634 });
12635 } else {
12636 log::error!(
12637 "No entry in selection_history found for redo. \
12638 This may correspond to a bug where undo does not update the selection. \
12639 If this is occurring, please add details to \
12640 https://github.com/zed-industries/zed/issues/22692"
12641 );
12642 }
12643 self.request_autoscroll(Autoscroll::fit(), cx);
12644 self.unmark_text(window, cx);
12645 self.refresh_edit_prediction(true, false, window, cx);
12646 cx.emit(EditorEvent::Edited { transaction_id });
12647 }
12648 }
12649
12650 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12651 self.buffer
12652 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12653 }
12654
12655 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12656 self.buffer
12657 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12658 }
12659
12660 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12662 self.change_selections(Default::default(), window, cx, |s| {
12663 s.move_with(|map, selection| {
12664 let cursor = if selection.is_empty() {
12665 movement::left(map, selection.start)
12666 } else {
12667 selection.start
12668 };
12669 selection.collapse_to(cursor, SelectionGoal::None);
12670 });
12671 })
12672 }
12673
12674 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12675 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12676 self.change_selections(Default::default(), window, cx, |s| {
12677 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12678 })
12679 }
12680
12681 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12683 self.change_selections(Default::default(), window, cx, |s| {
12684 s.move_with(|map, selection| {
12685 let cursor = if selection.is_empty() {
12686 movement::right(map, selection.end)
12687 } else {
12688 selection.end
12689 };
12690 selection.collapse_to(cursor, SelectionGoal::None)
12691 });
12692 })
12693 }
12694
12695 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12696 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12697 self.change_selections(Default::default(), window, cx, |s| {
12698 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12699 })
12700 }
12701
12702 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12703 if self.take_rename(true, window, cx).is_some() {
12704 return;
12705 }
12706
12707 if self.mode.is_single_line() {
12708 cx.propagate();
12709 return;
12710 }
12711
12712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12713
12714 let text_layout_details = &self.text_layout_details(window);
12715 let selection_count = self.selections.count();
12716 let first_selection = self.selections.first_anchor();
12717
12718 self.change_selections(Default::default(), window, cx, |s| {
12719 s.move_with(|map, selection| {
12720 if !selection.is_empty() {
12721 selection.goal = SelectionGoal::None;
12722 }
12723 let (cursor, goal) = movement::up(
12724 map,
12725 selection.start,
12726 selection.goal,
12727 false,
12728 text_layout_details,
12729 );
12730 selection.collapse_to(cursor, goal);
12731 });
12732 });
12733
12734 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12735 {
12736 cx.propagate();
12737 }
12738 }
12739
12740 pub fn move_up_by_lines(
12741 &mut self,
12742 action: &MoveUpByLines,
12743 window: &mut Window,
12744 cx: &mut Context<Self>,
12745 ) {
12746 if self.take_rename(true, window, cx).is_some() {
12747 return;
12748 }
12749
12750 if self.mode.is_single_line() {
12751 cx.propagate();
12752 return;
12753 }
12754
12755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12756
12757 let text_layout_details = &self.text_layout_details(window);
12758
12759 self.change_selections(Default::default(), window, cx, |s| {
12760 s.move_with(|map, selection| {
12761 if !selection.is_empty() {
12762 selection.goal = SelectionGoal::None;
12763 }
12764 let (cursor, goal) = movement::up_by_rows(
12765 map,
12766 selection.start,
12767 action.lines,
12768 selection.goal,
12769 false,
12770 text_layout_details,
12771 );
12772 selection.collapse_to(cursor, goal);
12773 });
12774 })
12775 }
12776
12777 pub fn move_down_by_lines(
12778 &mut self,
12779 action: &MoveDownByLines,
12780 window: &mut Window,
12781 cx: &mut Context<Self>,
12782 ) {
12783 if self.take_rename(true, window, cx).is_some() {
12784 return;
12785 }
12786
12787 if self.mode.is_single_line() {
12788 cx.propagate();
12789 return;
12790 }
12791
12792 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12793
12794 let text_layout_details = &self.text_layout_details(window);
12795
12796 self.change_selections(Default::default(), window, cx, |s| {
12797 s.move_with(|map, selection| {
12798 if !selection.is_empty() {
12799 selection.goal = SelectionGoal::None;
12800 }
12801 let (cursor, goal) = movement::down_by_rows(
12802 map,
12803 selection.start,
12804 action.lines,
12805 selection.goal,
12806 false,
12807 text_layout_details,
12808 );
12809 selection.collapse_to(cursor, goal);
12810 });
12811 })
12812 }
12813
12814 pub fn select_down_by_lines(
12815 &mut self,
12816 action: &SelectDownByLines,
12817 window: &mut Window,
12818 cx: &mut Context<Self>,
12819 ) {
12820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12821 let text_layout_details = &self.text_layout_details(window);
12822 self.change_selections(Default::default(), window, cx, |s| {
12823 s.move_heads_with(|map, head, goal| {
12824 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12825 })
12826 })
12827 }
12828
12829 pub fn select_up_by_lines(
12830 &mut self,
12831 action: &SelectUpByLines,
12832 window: &mut Window,
12833 cx: &mut Context<Self>,
12834 ) {
12835 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12836 let text_layout_details = &self.text_layout_details(window);
12837 self.change_selections(Default::default(), window, cx, |s| {
12838 s.move_heads_with(|map, head, goal| {
12839 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12840 })
12841 })
12842 }
12843
12844 pub fn select_page_up(
12845 &mut self,
12846 _: &SelectPageUp,
12847 window: &mut Window,
12848 cx: &mut Context<Self>,
12849 ) {
12850 let Some(row_count) = self.visible_row_count() else {
12851 return;
12852 };
12853
12854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12855
12856 let text_layout_details = &self.text_layout_details(window);
12857
12858 self.change_selections(Default::default(), window, cx, |s| {
12859 s.move_heads_with(|map, head, goal| {
12860 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12861 })
12862 })
12863 }
12864
12865 pub fn move_page_up(
12866 &mut self,
12867 action: &MovePageUp,
12868 window: &mut Window,
12869 cx: &mut Context<Self>,
12870 ) {
12871 if self.take_rename(true, window, cx).is_some() {
12872 return;
12873 }
12874
12875 if self
12876 .context_menu
12877 .borrow_mut()
12878 .as_mut()
12879 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12880 .unwrap_or(false)
12881 {
12882 return;
12883 }
12884
12885 if matches!(self.mode, EditorMode::SingleLine) {
12886 cx.propagate();
12887 return;
12888 }
12889
12890 let Some(row_count) = self.visible_row_count() else {
12891 return;
12892 };
12893
12894 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12895
12896 let effects = if action.center_cursor {
12897 SelectionEffects::scroll(Autoscroll::center())
12898 } else {
12899 SelectionEffects::default()
12900 };
12901
12902 let text_layout_details = &self.text_layout_details(window);
12903
12904 self.change_selections(effects, window, cx, |s| {
12905 s.move_with(|map, selection| {
12906 if !selection.is_empty() {
12907 selection.goal = SelectionGoal::None;
12908 }
12909 let (cursor, goal) = movement::up_by_rows(
12910 map,
12911 selection.end,
12912 row_count,
12913 selection.goal,
12914 false,
12915 text_layout_details,
12916 );
12917 selection.collapse_to(cursor, goal);
12918 });
12919 });
12920 }
12921
12922 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12923 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12924 let text_layout_details = &self.text_layout_details(window);
12925 self.change_selections(Default::default(), window, cx, |s| {
12926 s.move_heads_with(|map, head, goal| {
12927 movement::up(map, head, goal, false, text_layout_details)
12928 })
12929 })
12930 }
12931
12932 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12933 self.take_rename(true, window, cx);
12934
12935 if self.mode.is_single_line() {
12936 cx.propagate();
12937 return;
12938 }
12939
12940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12941
12942 let text_layout_details = &self.text_layout_details(window);
12943 let selection_count = self.selections.count();
12944 let first_selection = self.selections.first_anchor();
12945
12946 self.change_selections(Default::default(), window, cx, |s| {
12947 s.move_with(|map, selection| {
12948 if !selection.is_empty() {
12949 selection.goal = SelectionGoal::None;
12950 }
12951 let (cursor, goal) = movement::down(
12952 map,
12953 selection.end,
12954 selection.goal,
12955 false,
12956 text_layout_details,
12957 );
12958 selection.collapse_to(cursor, goal);
12959 });
12960 });
12961
12962 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12963 {
12964 cx.propagate();
12965 }
12966 }
12967
12968 pub fn select_page_down(
12969 &mut self,
12970 _: &SelectPageDown,
12971 window: &mut Window,
12972 cx: &mut Context<Self>,
12973 ) {
12974 let Some(row_count) = self.visible_row_count() else {
12975 return;
12976 };
12977
12978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12979
12980 let text_layout_details = &self.text_layout_details(window);
12981
12982 self.change_selections(Default::default(), window, cx, |s| {
12983 s.move_heads_with(|map, head, goal| {
12984 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12985 })
12986 })
12987 }
12988
12989 pub fn move_page_down(
12990 &mut self,
12991 action: &MovePageDown,
12992 window: &mut Window,
12993 cx: &mut Context<Self>,
12994 ) {
12995 if self.take_rename(true, window, cx).is_some() {
12996 return;
12997 }
12998
12999 if self
13000 .context_menu
13001 .borrow_mut()
13002 .as_mut()
13003 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13004 .unwrap_or(false)
13005 {
13006 return;
13007 }
13008
13009 if matches!(self.mode, EditorMode::SingleLine) {
13010 cx.propagate();
13011 return;
13012 }
13013
13014 let Some(row_count) = self.visible_row_count() else {
13015 return;
13016 };
13017
13018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13019
13020 let effects = if action.center_cursor {
13021 SelectionEffects::scroll(Autoscroll::center())
13022 } else {
13023 SelectionEffects::default()
13024 };
13025
13026 let text_layout_details = &self.text_layout_details(window);
13027 self.change_selections(effects, window, cx, |s| {
13028 s.move_with(|map, selection| {
13029 if !selection.is_empty() {
13030 selection.goal = SelectionGoal::None;
13031 }
13032 let (cursor, goal) = movement::down_by_rows(
13033 map,
13034 selection.end,
13035 row_count,
13036 selection.goal,
13037 false,
13038 text_layout_details,
13039 );
13040 selection.collapse_to(cursor, goal);
13041 });
13042 });
13043 }
13044
13045 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13046 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13047 let text_layout_details = &self.text_layout_details(window);
13048 self.change_selections(Default::default(), window, cx, |s| {
13049 s.move_heads_with(|map, head, goal| {
13050 movement::down(map, head, goal, false, text_layout_details)
13051 })
13052 });
13053 }
13054
13055 pub fn context_menu_first(
13056 &mut self,
13057 _: &ContextMenuFirst,
13058 window: &mut Window,
13059 cx: &mut Context<Self>,
13060 ) {
13061 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13062 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13063 }
13064 }
13065
13066 pub fn context_menu_prev(
13067 &mut self,
13068 _: &ContextMenuPrevious,
13069 window: &mut Window,
13070 cx: &mut Context<Self>,
13071 ) {
13072 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13073 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13074 }
13075 }
13076
13077 pub fn context_menu_next(
13078 &mut self,
13079 _: &ContextMenuNext,
13080 window: &mut Window,
13081 cx: &mut Context<Self>,
13082 ) {
13083 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13084 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13085 }
13086 }
13087
13088 pub fn context_menu_last(
13089 &mut self,
13090 _: &ContextMenuLast,
13091 window: &mut Window,
13092 cx: &mut Context<Self>,
13093 ) {
13094 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13095 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13096 }
13097 }
13098
13099 pub fn signature_help_prev(
13100 &mut self,
13101 _: &SignatureHelpPrevious,
13102 _: &mut Window,
13103 cx: &mut Context<Self>,
13104 ) {
13105 if let Some(popover) = self.signature_help_state.popover_mut() {
13106 if popover.current_signature == 0 {
13107 popover.current_signature = popover.signatures.len() - 1;
13108 } else {
13109 popover.current_signature -= 1;
13110 }
13111 cx.notify();
13112 }
13113 }
13114
13115 pub fn signature_help_next(
13116 &mut self,
13117 _: &SignatureHelpNext,
13118 _: &mut Window,
13119 cx: &mut Context<Self>,
13120 ) {
13121 if let Some(popover) = self.signature_help_state.popover_mut() {
13122 if popover.current_signature + 1 == popover.signatures.len() {
13123 popover.current_signature = 0;
13124 } else {
13125 popover.current_signature += 1;
13126 }
13127 cx.notify();
13128 }
13129 }
13130
13131 pub fn move_to_previous_word_start(
13132 &mut self,
13133 _: &MoveToPreviousWordStart,
13134 window: &mut Window,
13135 cx: &mut Context<Self>,
13136 ) {
13137 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13138 self.change_selections(Default::default(), window, cx, |s| {
13139 s.move_cursors_with(|map, head, _| {
13140 (
13141 movement::previous_word_start(map, head),
13142 SelectionGoal::None,
13143 )
13144 });
13145 })
13146 }
13147
13148 pub fn move_to_previous_subword_start(
13149 &mut self,
13150 _: &MoveToPreviousSubwordStart,
13151 window: &mut Window,
13152 cx: &mut Context<Self>,
13153 ) {
13154 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13155 self.change_selections(Default::default(), window, cx, |s| {
13156 s.move_cursors_with(|map, head, _| {
13157 (
13158 movement::previous_subword_start(map, head),
13159 SelectionGoal::None,
13160 )
13161 });
13162 })
13163 }
13164
13165 pub fn select_to_previous_word_start(
13166 &mut self,
13167 _: &SelectToPreviousWordStart,
13168 window: &mut Window,
13169 cx: &mut Context<Self>,
13170 ) {
13171 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13172 self.change_selections(Default::default(), window, cx, |s| {
13173 s.move_heads_with(|map, head, _| {
13174 (
13175 movement::previous_word_start(map, head),
13176 SelectionGoal::None,
13177 )
13178 });
13179 })
13180 }
13181
13182 pub fn select_to_previous_subword_start(
13183 &mut self,
13184 _: &SelectToPreviousSubwordStart,
13185 window: &mut Window,
13186 cx: &mut Context<Self>,
13187 ) {
13188 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13189 self.change_selections(Default::default(), window, cx, |s| {
13190 s.move_heads_with(|map, head, _| {
13191 (
13192 movement::previous_subword_start(map, head),
13193 SelectionGoal::None,
13194 )
13195 });
13196 })
13197 }
13198
13199 pub fn delete_to_previous_word_start(
13200 &mut self,
13201 action: &DeleteToPreviousWordStart,
13202 window: &mut Window,
13203 cx: &mut Context<Self>,
13204 ) {
13205 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13206 self.transact(window, cx, |this, window, cx| {
13207 this.select_autoclose_pair(window, cx);
13208 this.change_selections(Default::default(), window, cx, |s| {
13209 s.move_with(|map, selection| {
13210 if selection.is_empty() {
13211 let mut cursor = if action.ignore_newlines {
13212 movement::previous_word_start(map, selection.head())
13213 } else {
13214 movement::previous_word_start_or_newline(map, selection.head())
13215 };
13216 cursor = movement::adjust_greedy_deletion(
13217 map,
13218 selection.head(),
13219 cursor,
13220 action.ignore_brackets,
13221 );
13222 selection.set_head(cursor, SelectionGoal::None);
13223 }
13224 });
13225 });
13226 this.insert("", window, cx);
13227 });
13228 }
13229
13230 pub fn delete_to_previous_subword_start(
13231 &mut self,
13232 _: &DeleteToPreviousSubwordStart,
13233 window: &mut Window,
13234 cx: &mut Context<Self>,
13235 ) {
13236 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13237 self.transact(window, cx, |this, window, cx| {
13238 this.select_autoclose_pair(window, cx);
13239 this.change_selections(Default::default(), window, cx, |s| {
13240 s.move_with(|map, selection| {
13241 if selection.is_empty() {
13242 let mut cursor = movement::previous_subword_start(map, selection.head());
13243 cursor =
13244 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13245 selection.set_head(cursor, SelectionGoal::None);
13246 }
13247 });
13248 });
13249 this.insert("", window, cx);
13250 });
13251 }
13252
13253 pub fn move_to_next_word_end(
13254 &mut self,
13255 _: &MoveToNextWordEnd,
13256 window: &mut Window,
13257 cx: &mut Context<Self>,
13258 ) {
13259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13260 self.change_selections(Default::default(), window, cx, |s| {
13261 s.move_cursors_with(|map, head, _| {
13262 (movement::next_word_end(map, head), SelectionGoal::None)
13263 });
13264 })
13265 }
13266
13267 pub fn move_to_next_subword_end(
13268 &mut self,
13269 _: &MoveToNextSubwordEnd,
13270 window: &mut Window,
13271 cx: &mut Context<Self>,
13272 ) {
13273 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13274 self.change_selections(Default::default(), window, cx, |s| {
13275 s.move_cursors_with(|map, head, _| {
13276 (movement::next_subword_end(map, head), SelectionGoal::None)
13277 });
13278 })
13279 }
13280
13281 pub fn select_to_next_word_end(
13282 &mut self,
13283 _: &SelectToNextWordEnd,
13284 window: &mut Window,
13285 cx: &mut Context<Self>,
13286 ) {
13287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13288 self.change_selections(Default::default(), window, cx, |s| {
13289 s.move_heads_with(|map, head, _| {
13290 (movement::next_word_end(map, head), SelectionGoal::None)
13291 });
13292 })
13293 }
13294
13295 pub fn select_to_next_subword_end(
13296 &mut self,
13297 _: &SelectToNextSubwordEnd,
13298 window: &mut Window,
13299 cx: &mut Context<Self>,
13300 ) {
13301 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13302 self.change_selections(Default::default(), window, cx, |s| {
13303 s.move_heads_with(|map, head, _| {
13304 (movement::next_subword_end(map, head), SelectionGoal::None)
13305 });
13306 })
13307 }
13308
13309 pub fn delete_to_next_word_end(
13310 &mut self,
13311 action: &DeleteToNextWordEnd,
13312 window: &mut Window,
13313 cx: &mut Context<Self>,
13314 ) {
13315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13316 self.transact(window, cx, |this, window, cx| {
13317 this.change_selections(Default::default(), window, cx, |s| {
13318 s.move_with(|map, selection| {
13319 if selection.is_empty() {
13320 let mut cursor = if action.ignore_newlines {
13321 movement::next_word_end(map, selection.head())
13322 } else {
13323 movement::next_word_end_or_newline(map, selection.head())
13324 };
13325 cursor = movement::adjust_greedy_deletion(
13326 map,
13327 selection.head(),
13328 cursor,
13329 action.ignore_brackets,
13330 );
13331 selection.set_head(cursor, SelectionGoal::None);
13332 }
13333 });
13334 });
13335 this.insert("", window, cx);
13336 });
13337 }
13338
13339 pub fn delete_to_next_subword_end(
13340 &mut self,
13341 _: &DeleteToNextSubwordEnd,
13342 window: &mut Window,
13343 cx: &mut Context<Self>,
13344 ) {
13345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13346 self.transact(window, cx, |this, window, cx| {
13347 this.change_selections(Default::default(), window, cx, |s| {
13348 s.move_with(|map, selection| {
13349 if selection.is_empty() {
13350 let mut cursor = movement::next_subword_end(map, selection.head());
13351 cursor =
13352 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13353 selection.set_head(cursor, SelectionGoal::None);
13354 }
13355 });
13356 });
13357 this.insert("", window, cx);
13358 });
13359 }
13360
13361 pub fn move_to_beginning_of_line(
13362 &mut self,
13363 action: &MoveToBeginningOfLine,
13364 window: &mut Window,
13365 cx: &mut Context<Self>,
13366 ) {
13367 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13368 self.change_selections(Default::default(), window, cx, |s| {
13369 s.move_cursors_with(|map, head, _| {
13370 (
13371 movement::indented_line_beginning(
13372 map,
13373 head,
13374 action.stop_at_soft_wraps,
13375 action.stop_at_indent,
13376 ),
13377 SelectionGoal::None,
13378 )
13379 });
13380 })
13381 }
13382
13383 pub fn select_to_beginning_of_line(
13384 &mut self,
13385 action: &SelectToBeginningOfLine,
13386 window: &mut Window,
13387 cx: &mut Context<Self>,
13388 ) {
13389 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13390 self.change_selections(Default::default(), window, cx, |s| {
13391 s.move_heads_with(|map, head, _| {
13392 (
13393 movement::indented_line_beginning(
13394 map,
13395 head,
13396 action.stop_at_soft_wraps,
13397 action.stop_at_indent,
13398 ),
13399 SelectionGoal::None,
13400 )
13401 });
13402 });
13403 }
13404
13405 pub fn delete_to_beginning_of_line(
13406 &mut self,
13407 action: &DeleteToBeginningOfLine,
13408 window: &mut Window,
13409 cx: &mut Context<Self>,
13410 ) {
13411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13412 self.transact(window, cx, |this, window, cx| {
13413 this.change_selections(Default::default(), window, cx, |s| {
13414 s.move_with(|_, selection| {
13415 selection.reversed = true;
13416 });
13417 });
13418
13419 this.select_to_beginning_of_line(
13420 &SelectToBeginningOfLine {
13421 stop_at_soft_wraps: false,
13422 stop_at_indent: action.stop_at_indent,
13423 },
13424 window,
13425 cx,
13426 );
13427 this.backspace(&Backspace, window, cx);
13428 });
13429 }
13430
13431 pub fn move_to_end_of_line(
13432 &mut self,
13433 action: &MoveToEndOfLine,
13434 window: &mut Window,
13435 cx: &mut Context<Self>,
13436 ) {
13437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13438 self.change_selections(Default::default(), window, cx, |s| {
13439 s.move_cursors_with(|map, head, _| {
13440 (
13441 movement::line_end(map, head, action.stop_at_soft_wraps),
13442 SelectionGoal::None,
13443 )
13444 });
13445 })
13446 }
13447
13448 pub fn select_to_end_of_line(
13449 &mut self,
13450 action: &SelectToEndOfLine,
13451 window: &mut Window,
13452 cx: &mut Context<Self>,
13453 ) {
13454 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13455 self.change_selections(Default::default(), window, cx, |s| {
13456 s.move_heads_with(|map, head, _| {
13457 (
13458 movement::line_end(map, head, action.stop_at_soft_wraps),
13459 SelectionGoal::None,
13460 )
13461 });
13462 })
13463 }
13464
13465 pub fn delete_to_end_of_line(
13466 &mut self,
13467 _: &DeleteToEndOfLine,
13468 window: &mut Window,
13469 cx: &mut Context<Self>,
13470 ) {
13471 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13472 self.transact(window, cx, |this, window, cx| {
13473 this.select_to_end_of_line(
13474 &SelectToEndOfLine {
13475 stop_at_soft_wraps: false,
13476 },
13477 window,
13478 cx,
13479 );
13480 this.delete(&Delete, window, cx);
13481 });
13482 }
13483
13484 pub fn cut_to_end_of_line(
13485 &mut self,
13486 action: &CutToEndOfLine,
13487 window: &mut Window,
13488 cx: &mut Context<Self>,
13489 ) {
13490 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13491 self.transact(window, cx, |this, window, cx| {
13492 this.select_to_end_of_line(
13493 &SelectToEndOfLine {
13494 stop_at_soft_wraps: false,
13495 },
13496 window,
13497 cx,
13498 );
13499 if !action.stop_at_newlines {
13500 this.change_selections(Default::default(), window, cx, |s| {
13501 s.move_with(|_, sel| {
13502 if sel.is_empty() {
13503 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13504 }
13505 });
13506 });
13507 }
13508 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13509 let item = this.cut_common(false, window, cx);
13510 cx.write_to_clipboard(item);
13511 });
13512 }
13513
13514 pub fn move_to_start_of_paragraph(
13515 &mut self,
13516 _: &MoveToStartOfParagraph,
13517 window: &mut Window,
13518 cx: &mut Context<Self>,
13519 ) {
13520 if matches!(self.mode, EditorMode::SingleLine) {
13521 cx.propagate();
13522 return;
13523 }
13524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13525 self.change_selections(Default::default(), window, cx, |s| {
13526 s.move_with(|map, selection| {
13527 selection.collapse_to(
13528 movement::start_of_paragraph(map, selection.head(), 1),
13529 SelectionGoal::None,
13530 )
13531 });
13532 })
13533 }
13534
13535 pub fn move_to_end_of_paragraph(
13536 &mut self,
13537 _: &MoveToEndOfParagraph,
13538 window: &mut Window,
13539 cx: &mut Context<Self>,
13540 ) {
13541 if matches!(self.mode, EditorMode::SingleLine) {
13542 cx.propagate();
13543 return;
13544 }
13545 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13546 self.change_selections(Default::default(), window, cx, |s| {
13547 s.move_with(|map, selection| {
13548 selection.collapse_to(
13549 movement::end_of_paragraph(map, selection.head(), 1),
13550 SelectionGoal::None,
13551 )
13552 });
13553 })
13554 }
13555
13556 pub fn select_to_start_of_paragraph(
13557 &mut self,
13558 _: &SelectToStartOfParagraph,
13559 window: &mut Window,
13560 cx: &mut Context<Self>,
13561 ) {
13562 if matches!(self.mode, EditorMode::SingleLine) {
13563 cx.propagate();
13564 return;
13565 }
13566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13567 self.change_selections(Default::default(), window, cx, |s| {
13568 s.move_heads_with(|map, head, _| {
13569 (
13570 movement::start_of_paragraph(map, head, 1),
13571 SelectionGoal::None,
13572 )
13573 });
13574 })
13575 }
13576
13577 pub fn select_to_end_of_paragraph(
13578 &mut self,
13579 _: &SelectToEndOfParagraph,
13580 window: &mut Window,
13581 cx: &mut Context<Self>,
13582 ) {
13583 if matches!(self.mode, EditorMode::SingleLine) {
13584 cx.propagate();
13585 return;
13586 }
13587 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13588 self.change_selections(Default::default(), window, cx, |s| {
13589 s.move_heads_with(|map, head, _| {
13590 (
13591 movement::end_of_paragraph(map, head, 1),
13592 SelectionGoal::None,
13593 )
13594 });
13595 })
13596 }
13597
13598 pub fn move_to_start_of_excerpt(
13599 &mut self,
13600 _: &MoveToStartOfExcerpt,
13601 window: &mut Window,
13602 cx: &mut Context<Self>,
13603 ) {
13604 if matches!(self.mode, EditorMode::SingleLine) {
13605 cx.propagate();
13606 return;
13607 }
13608 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13609 self.change_selections(Default::default(), window, cx, |s| {
13610 s.move_with(|map, selection| {
13611 selection.collapse_to(
13612 movement::start_of_excerpt(
13613 map,
13614 selection.head(),
13615 workspace::searchable::Direction::Prev,
13616 ),
13617 SelectionGoal::None,
13618 )
13619 });
13620 })
13621 }
13622
13623 pub fn move_to_start_of_next_excerpt(
13624 &mut self,
13625 _: &MoveToStartOfNextExcerpt,
13626 window: &mut Window,
13627 cx: &mut Context<Self>,
13628 ) {
13629 if matches!(self.mode, EditorMode::SingleLine) {
13630 cx.propagate();
13631 return;
13632 }
13633
13634 self.change_selections(Default::default(), window, cx, |s| {
13635 s.move_with(|map, selection| {
13636 selection.collapse_to(
13637 movement::start_of_excerpt(
13638 map,
13639 selection.head(),
13640 workspace::searchable::Direction::Next,
13641 ),
13642 SelectionGoal::None,
13643 )
13644 });
13645 })
13646 }
13647
13648 pub fn move_to_end_of_excerpt(
13649 &mut self,
13650 _: &MoveToEndOfExcerpt,
13651 window: &mut Window,
13652 cx: &mut Context<Self>,
13653 ) {
13654 if matches!(self.mode, EditorMode::SingleLine) {
13655 cx.propagate();
13656 return;
13657 }
13658 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13659 self.change_selections(Default::default(), window, cx, |s| {
13660 s.move_with(|map, selection| {
13661 selection.collapse_to(
13662 movement::end_of_excerpt(
13663 map,
13664 selection.head(),
13665 workspace::searchable::Direction::Next,
13666 ),
13667 SelectionGoal::None,
13668 )
13669 });
13670 })
13671 }
13672
13673 pub fn move_to_end_of_previous_excerpt(
13674 &mut self,
13675 _: &MoveToEndOfPreviousExcerpt,
13676 window: &mut Window,
13677 cx: &mut Context<Self>,
13678 ) {
13679 if matches!(self.mode, EditorMode::SingleLine) {
13680 cx.propagate();
13681 return;
13682 }
13683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13684 self.change_selections(Default::default(), window, cx, |s| {
13685 s.move_with(|map, selection| {
13686 selection.collapse_to(
13687 movement::end_of_excerpt(
13688 map,
13689 selection.head(),
13690 workspace::searchable::Direction::Prev,
13691 ),
13692 SelectionGoal::None,
13693 )
13694 });
13695 })
13696 }
13697
13698 pub fn select_to_start_of_excerpt(
13699 &mut self,
13700 _: &SelectToStartOfExcerpt,
13701 window: &mut Window,
13702 cx: &mut Context<Self>,
13703 ) {
13704 if matches!(self.mode, EditorMode::SingleLine) {
13705 cx.propagate();
13706 return;
13707 }
13708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13709 self.change_selections(Default::default(), window, cx, |s| {
13710 s.move_heads_with(|map, head, _| {
13711 (
13712 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13713 SelectionGoal::None,
13714 )
13715 });
13716 })
13717 }
13718
13719 pub fn select_to_start_of_next_excerpt(
13720 &mut self,
13721 _: &SelectToStartOfNextExcerpt,
13722 window: &mut Window,
13723 cx: &mut Context<Self>,
13724 ) {
13725 if matches!(self.mode, EditorMode::SingleLine) {
13726 cx.propagate();
13727 return;
13728 }
13729 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13730 self.change_selections(Default::default(), window, cx, |s| {
13731 s.move_heads_with(|map, head, _| {
13732 (
13733 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13734 SelectionGoal::None,
13735 )
13736 });
13737 })
13738 }
13739
13740 pub fn select_to_end_of_excerpt(
13741 &mut self,
13742 _: &SelectToEndOfExcerpt,
13743 window: &mut Window,
13744 cx: &mut Context<Self>,
13745 ) {
13746 if matches!(self.mode, EditorMode::SingleLine) {
13747 cx.propagate();
13748 return;
13749 }
13750 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13751 self.change_selections(Default::default(), window, cx, |s| {
13752 s.move_heads_with(|map, head, _| {
13753 (
13754 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13755 SelectionGoal::None,
13756 )
13757 });
13758 })
13759 }
13760
13761 pub fn select_to_end_of_previous_excerpt(
13762 &mut self,
13763 _: &SelectToEndOfPreviousExcerpt,
13764 window: &mut Window,
13765 cx: &mut Context<Self>,
13766 ) {
13767 if matches!(self.mode, EditorMode::SingleLine) {
13768 cx.propagate();
13769 return;
13770 }
13771 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13772 self.change_selections(Default::default(), window, cx, |s| {
13773 s.move_heads_with(|map, head, _| {
13774 (
13775 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13776 SelectionGoal::None,
13777 )
13778 });
13779 })
13780 }
13781
13782 pub fn move_to_beginning(
13783 &mut self,
13784 _: &MoveToBeginning,
13785 window: &mut Window,
13786 cx: &mut Context<Self>,
13787 ) {
13788 if matches!(self.mode, EditorMode::SingleLine) {
13789 cx.propagate();
13790 return;
13791 }
13792 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13793 self.change_selections(Default::default(), window, cx, |s| {
13794 s.select_ranges(vec![0..0]);
13795 });
13796 }
13797
13798 pub fn select_to_beginning(
13799 &mut self,
13800 _: &SelectToBeginning,
13801 window: &mut Window,
13802 cx: &mut Context<Self>,
13803 ) {
13804 let mut selection = self.selections.last::<Point>(cx);
13805 selection.set_head(Point::zero(), SelectionGoal::None);
13806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13807 self.change_selections(Default::default(), window, cx, |s| {
13808 s.select(vec![selection]);
13809 });
13810 }
13811
13812 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13813 if matches!(self.mode, EditorMode::SingleLine) {
13814 cx.propagate();
13815 return;
13816 }
13817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13818 let cursor = self.buffer.read(cx).read(cx).len();
13819 self.change_selections(Default::default(), window, cx, |s| {
13820 s.select_ranges(vec![cursor..cursor])
13821 });
13822 }
13823
13824 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13825 self.nav_history = nav_history;
13826 }
13827
13828 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13829 self.nav_history.as_ref()
13830 }
13831
13832 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13833 self.push_to_nav_history(
13834 self.selections.newest_anchor().head(),
13835 None,
13836 false,
13837 true,
13838 cx,
13839 );
13840 }
13841
13842 fn push_to_nav_history(
13843 &mut self,
13844 cursor_anchor: Anchor,
13845 new_position: Option<Point>,
13846 is_deactivate: bool,
13847 always: bool,
13848 cx: &mut Context<Self>,
13849 ) {
13850 if let Some(nav_history) = self.nav_history.as_mut() {
13851 let buffer = self.buffer.read(cx).read(cx);
13852 let cursor_position = cursor_anchor.to_point(&buffer);
13853 let scroll_state = self.scroll_manager.anchor();
13854 let scroll_top_row = scroll_state.top_row(&buffer);
13855 drop(buffer);
13856
13857 if let Some(new_position) = new_position {
13858 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13859 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13860 return;
13861 }
13862 }
13863
13864 nav_history.push(
13865 Some(NavigationData {
13866 cursor_anchor,
13867 cursor_position,
13868 scroll_anchor: scroll_state,
13869 scroll_top_row,
13870 }),
13871 cx,
13872 );
13873 cx.emit(EditorEvent::PushedToNavHistory {
13874 anchor: cursor_anchor,
13875 is_deactivate,
13876 })
13877 }
13878 }
13879
13880 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13882 let buffer = self.buffer.read(cx).snapshot(cx);
13883 let mut selection = self.selections.first::<usize>(cx);
13884 selection.set_head(buffer.len(), SelectionGoal::None);
13885 self.change_selections(Default::default(), window, cx, |s| {
13886 s.select(vec![selection]);
13887 });
13888 }
13889
13890 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13891 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13892 let end = self.buffer.read(cx).read(cx).len();
13893 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13894 s.select_ranges(vec![0..end]);
13895 });
13896 }
13897
13898 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13899 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13900 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13901 let mut selections = self.selections.all::<Point>(cx);
13902 let max_point = display_map.buffer_snapshot.max_point();
13903 for selection in &mut selections {
13904 let rows = selection.spanned_rows(true, &display_map);
13905 selection.start = Point::new(rows.start.0, 0);
13906 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13907 selection.reversed = false;
13908 }
13909 self.change_selections(Default::default(), window, cx, |s| {
13910 s.select(selections);
13911 });
13912 }
13913
13914 pub fn split_selection_into_lines(
13915 &mut self,
13916 action: &SplitSelectionIntoLines,
13917 window: &mut Window,
13918 cx: &mut Context<Self>,
13919 ) {
13920 let selections = self
13921 .selections
13922 .all::<Point>(cx)
13923 .into_iter()
13924 .map(|selection| selection.start..selection.end)
13925 .collect::<Vec<_>>();
13926 self.unfold_ranges(&selections, true, true, cx);
13927
13928 let mut new_selection_ranges = Vec::new();
13929 {
13930 let buffer = self.buffer.read(cx).read(cx);
13931 for selection in selections {
13932 for row in selection.start.row..selection.end.row {
13933 let line_start = Point::new(row, 0);
13934 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13935
13936 if action.keep_selections {
13937 // Keep the selection range for each line
13938 let selection_start = if row == selection.start.row {
13939 selection.start
13940 } else {
13941 line_start
13942 };
13943 new_selection_ranges.push(selection_start..line_end);
13944 } else {
13945 // Collapse to cursor at end of line
13946 new_selection_ranges.push(line_end..line_end);
13947 }
13948 }
13949
13950 let is_multiline_selection = selection.start.row != selection.end.row;
13951 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13952 // so this action feels more ergonomic when paired with other selection operations
13953 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13954 if !should_skip_last {
13955 if action.keep_selections {
13956 if is_multiline_selection {
13957 let line_start = Point::new(selection.end.row, 0);
13958 new_selection_ranges.push(line_start..selection.end);
13959 } else {
13960 new_selection_ranges.push(selection.start..selection.end);
13961 }
13962 } else {
13963 new_selection_ranges.push(selection.end..selection.end);
13964 }
13965 }
13966 }
13967 }
13968 self.change_selections(Default::default(), window, cx, |s| {
13969 s.select_ranges(new_selection_ranges);
13970 });
13971 }
13972
13973 pub fn add_selection_above(
13974 &mut self,
13975 _: &AddSelectionAbove,
13976 window: &mut Window,
13977 cx: &mut Context<Self>,
13978 ) {
13979 self.add_selection(true, window, cx);
13980 }
13981
13982 pub fn add_selection_below(
13983 &mut self,
13984 _: &AddSelectionBelow,
13985 window: &mut Window,
13986 cx: &mut Context<Self>,
13987 ) {
13988 self.add_selection(false, window, cx);
13989 }
13990
13991 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13992 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13993
13994 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13995 let all_selections = self.selections.all::<Point>(cx);
13996 let text_layout_details = self.text_layout_details(window);
13997
13998 let (mut columnar_selections, new_selections_to_columnarize) = {
13999 if let Some(state) = self.add_selections_state.as_ref() {
14000 let columnar_selection_ids: HashSet<_> = state
14001 .groups
14002 .iter()
14003 .flat_map(|group| group.stack.iter())
14004 .copied()
14005 .collect();
14006
14007 all_selections
14008 .into_iter()
14009 .partition(|s| columnar_selection_ids.contains(&s.id))
14010 } else {
14011 (Vec::new(), all_selections)
14012 }
14013 };
14014
14015 let mut state = self
14016 .add_selections_state
14017 .take()
14018 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14019
14020 for selection in new_selections_to_columnarize {
14021 let range = selection.display_range(&display_map).sorted();
14022 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14023 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14024 let positions = start_x.min(end_x)..start_x.max(end_x);
14025 let mut stack = Vec::new();
14026 for row in range.start.row().0..=range.end.row().0 {
14027 if let Some(selection) = self.selections.build_columnar_selection(
14028 &display_map,
14029 DisplayRow(row),
14030 &positions,
14031 selection.reversed,
14032 &text_layout_details,
14033 ) {
14034 stack.push(selection.id);
14035 columnar_selections.push(selection);
14036 }
14037 }
14038 if !stack.is_empty() {
14039 if above {
14040 stack.reverse();
14041 }
14042 state.groups.push(AddSelectionsGroup { above, stack });
14043 }
14044 }
14045
14046 let mut final_selections = Vec::new();
14047 let end_row = if above {
14048 DisplayRow(0)
14049 } else {
14050 display_map.max_point().row()
14051 };
14052
14053 let mut last_added_item_per_group = HashMap::default();
14054 for group in state.groups.iter_mut() {
14055 if let Some(last_id) = group.stack.last() {
14056 last_added_item_per_group.insert(*last_id, group);
14057 }
14058 }
14059
14060 for selection in columnar_selections {
14061 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14062 if above == group.above {
14063 let range = selection.display_range(&display_map).sorted();
14064 debug_assert_eq!(range.start.row(), range.end.row());
14065 let mut row = range.start.row();
14066 let positions =
14067 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14068 px(start)..px(end)
14069 } else {
14070 let start_x =
14071 display_map.x_for_display_point(range.start, &text_layout_details);
14072 let end_x =
14073 display_map.x_for_display_point(range.end, &text_layout_details);
14074 start_x.min(end_x)..start_x.max(end_x)
14075 };
14076
14077 let mut maybe_new_selection = None;
14078 while row != end_row {
14079 if above {
14080 row.0 -= 1;
14081 } else {
14082 row.0 += 1;
14083 }
14084 if let Some(new_selection) = self.selections.build_columnar_selection(
14085 &display_map,
14086 row,
14087 &positions,
14088 selection.reversed,
14089 &text_layout_details,
14090 ) {
14091 maybe_new_selection = Some(new_selection);
14092 break;
14093 }
14094 }
14095
14096 if let Some(new_selection) = maybe_new_selection {
14097 group.stack.push(new_selection.id);
14098 if above {
14099 final_selections.push(new_selection);
14100 final_selections.push(selection);
14101 } else {
14102 final_selections.push(selection);
14103 final_selections.push(new_selection);
14104 }
14105 } else {
14106 final_selections.push(selection);
14107 }
14108 } else {
14109 group.stack.pop();
14110 }
14111 } else {
14112 final_selections.push(selection);
14113 }
14114 }
14115
14116 self.change_selections(Default::default(), window, cx, |s| {
14117 s.select(final_selections);
14118 });
14119
14120 let final_selection_ids: HashSet<_> = self
14121 .selections
14122 .all::<Point>(cx)
14123 .iter()
14124 .map(|s| s.id)
14125 .collect();
14126 state.groups.retain_mut(|group| {
14127 // selections might get merged above so we remove invalid items from stacks
14128 group.stack.retain(|id| final_selection_ids.contains(id));
14129
14130 // single selection in stack can be treated as initial state
14131 group.stack.len() > 1
14132 });
14133
14134 if !state.groups.is_empty() {
14135 self.add_selections_state = Some(state);
14136 }
14137 }
14138
14139 fn select_match_ranges(
14140 &mut self,
14141 range: Range<usize>,
14142 reversed: bool,
14143 replace_newest: bool,
14144 auto_scroll: Option<Autoscroll>,
14145 window: &mut Window,
14146 cx: &mut Context<Editor>,
14147 ) {
14148 self.unfold_ranges(
14149 std::slice::from_ref(&range),
14150 false,
14151 auto_scroll.is_some(),
14152 cx,
14153 );
14154 let effects = if let Some(scroll) = auto_scroll {
14155 SelectionEffects::scroll(scroll)
14156 } else {
14157 SelectionEffects::no_scroll()
14158 };
14159 self.change_selections(effects, window, cx, |s| {
14160 if replace_newest {
14161 s.delete(s.newest_anchor().id);
14162 }
14163 if reversed {
14164 s.insert_range(range.end..range.start);
14165 } else {
14166 s.insert_range(range);
14167 }
14168 });
14169 }
14170
14171 pub fn select_next_match_internal(
14172 &mut self,
14173 display_map: &DisplaySnapshot,
14174 replace_newest: bool,
14175 autoscroll: Option<Autoscroll>,
14176 window: &mut Window,
14177 cx: &mut Context<Self>,
14178 ) -> Result<()> {
14179 let buffer = &display_map.buffer_snapshot;
14180 let mut selections = self.selections.all::<usize>(cx);
14181 if let Some(mut select_next_state) = self.select_next_state.take() {
14182 let query = &select_next_state.query;
14183 if !select_next_state.done {
14184 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14185 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14186 let mut next_selected_range = None;
14187
14188 let bytes_after_last_selection =
14189 buffer.bytes_in_range(last_selection.end..buffer.len());
14190 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14191 let query_matches = query
14192 .stream_find_iter(bytes_after_last_selection)
14193 .map(|result| (last_selection.end, result))
14194 .chain(
14195 query
14196 .stream_find_iter(bytes_before_first_selection)
14197 .map(|result| (0, result)),
14198 );
14199
14200 for (start_offset, query_match) in query_matches {
14201 let query_match = query_match.unwrap(); // can only fail due to I/O
14202 let offset_range =
14203 start_offset + query_match.start()..start_offset + query_match.end();
14204
14205 if !select_next_state.wordwise
14206 || (!buffer.is_inside_word(offset_range.start, false)
14207 && !buffer.is_inside_word(offset_range.end, false))
14208 {
14209 // TODO: This is n^2, because we might check all the selections
14210 if !selections
14211 .iter()
14212 .any(|selection| selection.range().overlaps(&offset_range))
14213 {
14214 next_selected_range = Some(offset_range);
14215 break;
14216 }
14217 }
14218 }
14219
14220 if let Some(next_selected_range) = next_selected_range {
14221 self.select_match_ranges(
14222 next_selected_range,
14223 last_selection.reversed,
14224 replace_newest,
14225 autoscroll,
14226 window,
14227 cx,
14228 );
14229 } else {
14230 select_next_state.done = true;
14231 }
14232 }
14233
14234 self.select_next_state = Some(select_next_state);
14235 } else {
14236 let mut only_carets = true;
14237 let mut same_text_selected = true;
14238 let mut selected_text = None;
14239
14240 let mut selections_iter = selections.iter().peekable();
14241 while let Some(selection) = selections_iter.next() {
14242 if selection.start != selection.end {
14243 only_carets = false;
14244 }
14245
14246 if same_text_selected {
14247 if selected_text.is_none() {
14248 selected_text =
14249 Some(buffer.text_for_range(selection.range()).collect::<String>());
14250 }
14251
14252 if let Some(next_selection) = selections_iter.peek() {
14253 if next_selection.range().len() == selection.range().len() {
14254 let next_selected_text = buffer
14255 .text_for_range(next_selection.range())
14256 .collect::<String>();
14257 if Some(next_selected_text) != selected_text {
14258 same_text_selected = false;
14259 selected_text = None;
14260 }
14261 } else {
14262 same_text_selected = false;
14263 selected_text = None;
14264 }
14265 }
14266 }
14267 }
14268
14269 if only_carets {
14270 for selection in &mut selections {
14271 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14272 selection.start = word_range.start;
14273 selection.end = word_range.end;
14274 selection.goal = SelectionGoal::None;
14275 selection.reversed = false;
14276 self.select_match_ranges(
14277 selection.start..selection.end,
14278 selection.reversed,
14279 replace_newest,
14280 autoscroll,
14281 window,
14282 cx,
14283 );
14284 }
14285
14286 if selections.len() == 1 {
14287 let selection = selections
14288 .last()
14289 .expect("ensured that there's only one selection");
14290 let query = buffer
14291 .text_for_range(selection.start..selection.end)
14292 .collect::<String>();
14293 let is_empty = query.is_empty();
14294 let select_state = SelectNextState {
14295 query: AhoCorasick::new(&[query])?,
14296 wordwise: true,
14297 done: is_empty,
14298 };
14299 self.select_next_state = Some(select_state);
14300 } else {
14301 self.select_next_state = None;
14302 }
14303 } else if let Some(selected_text) = selected_text {
14304 self.select_next_state = Some(SelectNextState {
14305 query: AhoCorasick::new(&[selected_text])?,
14306 wordwise: false,
14307 done: false,
14308 });
14309 self.select_next_match_internal(
14310 display_map,
14311 replace_newest,
14312 autoscroll,
14313 window,
14314 cx,
14315 )?;
14316 }
14317 }
14318 Ok(())
14319 }
14320
14321 pub fn select_all_matches(
14322 &mut self,
14323 _action: &SelectAllMatches,
14324 window: &mut Window,
14325 cx: &mut Context<Self>,
14326 ) -> Result<()> {
14327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14328
14329 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14330
14331 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14332 let Some(select_next_state) = self.select_next_state.as_mut() else {
14333 return Ok(());
14334 };
14335 if select_next_state.done {
14336 return Ok(());
14337 }
14338
14339 let mut new_selections = Vec::new();
14340
14341 let reversed = self.selections.oldest::<usize>(cx).reversed;
14342 let buffer = &display_map.buffer_snapshot;
14343 let query_matches = select_next_state
14344 .query
14345 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14346
14347 for query_match in query_matches.into_iter() {
14348 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14349 let offset_range = if reversed {
14350 query_match.end()..query_match.start()
14351 } else {
14352 query_match.start()..query_match.end()
14353 };
14354
14355 if !select_next_state.wordwise
14356 || (!buffer.is_inside_word(offset_range.start, false)
14357 && !buffer.is_inside_word(offset_range.end, false))
14358 {
14359 new_selections.push(offset_range.start..offset_range.end);
14360 }
14361 }
14362
14363 select_next_state.done = true;
14364
14365 if new_selections.is_empty() {
14366 log::error!("bug: new_selections is empty in select_all_matches");
14367 return Ok(());
14368 }
14369
14370 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14371 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14372 selections.select_ranges(new_selections)
14373 });
14374
14375 Ok(())
14376 }
14377
14378 pub fn select_next(
14379 &mut self,
14380 action: &SelectNext,
14381 window: &mut Window,
14382 cx: &mut Context<Self>,
14383 ) -> Result<()> {
14384 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14385 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14386 self.select_next_match_internal(
14387 &display_map,
14388 action.replace_newest,
14389 Some(Autoscroll::newest()),
14390 window,
14391 cx,
14392 )?;
14393 Ok(())
14394 }
14395
14396 pub fn select_previous(
14397 &mut self,
14398 action: &SelectPrevious,
14399 window: &mut Window,
14400 cx: &mut Context<Self>,
14401 ) -> Result<()> {
14402 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14403 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14404 let buffer = &display_map.buffer_snapshot;
14405 let mut selections = self.selections.all::<usize>(cx);
14406 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14407 let query = &select_prev_state.query;
14408 if !select_prev_state.done {
14409 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14410 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14411 let mut next_selected_range = None;
14412 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14413 let bytes_before_last_selection =
14414 buffer.reversed_bytes_in_range(0..last_selection.start);
14415 let bytes_after_first_selection =
14416 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14417 let query_matches = query
14418 .stream_find_iter(bytes_before_last_selection)
14419 .map(|result| (last_selection.start, result))
14420 .chain(
14421 query
14422 .stream_find_iter(bytes_after_first_selection)
14423 .map(|result| (buffer.len(), result)),
14424 );
14425 for (end_offset, query_match) in query_matches {
14426 let query_match = query_match.unwrap(); // can only fail due to I/O
14427 let offset_range =
14428 end_offset - query_match.end()..end_offset - query_match.start();
14429
14430 if !select_prev_state.wordwise
14431 || (!buffer.is_inside_word(offset_range.start, false)
14432 && !buffer.is_inside_word(offset_range.end, false))
14433 {
14434 next_selected_range = Some(offset_range);
14435 break;
14436 }
14437 }
14438
14439 if let Some(next_selected_range) = next_selected_range {
14440 self.select_match_ranges(
14441 next_selected_range,
14442 last_selection.reversed,
14443 action.replace_newest,
14444 Some(Autoscroll::newest()),
14445 window,
14446 cx,
14447 );
14448 } else {
14449 select_prev_state.done = true;
14450 }
14451 }
14452
14453 self.select_prev_state = Some(select_prev_state);
14454 } else {
14455 let mut only_carets = true;
14456 let mut same_text_selected = true;
14457 let mut selected_text = None;
14458
14459 let mut selections_iter = selections.iter().peekable();
14460 while let Some(selection) = selections_iter.next() {
14461 if selection.start != selection.end {
14462 only_carets = false;
14463 }
14464
14465 if same_text_selected {
14466 if selected_text.is_none() {
14467 selected_text =
14468 Some(buffer.text_for_range(selection.range()).collect::<String>());
14469 }
14470
14471 if let Some(next_selection) = selections_iter.peek() {
14472 if next_selection.range().len() == selection.range().len() {
14473 let next_selected_text = buffer
14474 .text_for_range(next_selection.range())
14475 .collect::<String>();
14476 if Some(next_selected_text) != selected_text {
14477 same_text_selected = false;
14478 selected_text = None;
14479 }
14480 } else {
14481 same_text_selected = false;
14482 selected_text = None;
14483 }
14484 }
14485 }
14486 }
14487
14488 if only_carets {
14489 for selection in &mut selections {
14490 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14491 selection.start = word_range.start;
14492 selection.end = word_range.end;
14493 selection.goal = SelectionGoal::None;
14494 selection.reversed = false;
14495 self.select_match_ranges(
14496 selection.start..selection.end,
14497 selection.reversed,
14498 action.replace_newest,
14499 Some(Autoscroll::newest()),
14500 window,
14501 cx,
14502 );
14503 }
14504 if selections.len() == 1 {
14505 let selection = selections
14506 .last()
14507 .expect("ensured that there's only one selection");
14508 let query = buffer
14509 .text_for_range(selection.start..selection.end)
14510 .collect::<String>();
14511 let is_empty = query.is_empty();
14512 let select_state = SelectNextState {
14513 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14514 wordwise: true,
14515 done: is_empty,
14516 };
14517 self.select_prev_state = Some(select_state);
14518 } else {
14519 self.select_prev_state = None;
14520 }
14521 } else if let Some(selected_text) = selected_text {
14522 self.select_prev_state = Some(SelectNextState {
14523 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14524 wordwise: false,
14525 done: false,
14526 });
14527 self.select_previous(action, window, cx)?;
14528 }
14529 }
14530 Ok(())
14531 }
14532
14533 pub fn find_next_match(
14534 &mut self,
14535 _: &FindNextMatch,
14536 window: &mut Window,
14537 cx: &mut Context<Self>,
14538 ) -> Result<()> {
14539 let selections = self.selections.disjoint_anchors();
14540 match selections.first() {
14541 Some(first) if selections.len() >= 2 => {
14542 self.change_selections(Default::default(), window, cx, |s| {
14543 s.select_ranges([first.range()]);
14544 });
14545 }
14546 _ => self.select_next(
14547 &SelectNext {
14548 replace_newest: true,
14549 },
14550 window,
14551 cx,
14552 )?,
14553 }
14554 Ok(())
14555 }
14556
14557 pub fn find_previous_match(
14558 &mut self,
14559 _: &FindPreviousMatch,
14560 window: &mut Window,
14561 cx: &mut Context<Self>,
14562 ) -> Result<()> {
14563 let selections = self.selections.disjoint_anchors();
14564 match selections.last() {
14565 Some(last) if selections.len() >= 2 => {
14566 self.change_selections(Default::default(), window, cx, |s| {
14567 s.select_ranges([last.range()]);
14568 });
14569 }
14570 _ => self.select_previous(
14571 &SelectPrevious {
14572 replace_newest: true,
14573 },
14574 window,
14575 cx,
14576 )?,
14577 }
14578 Ok(())
14579 }
14580
14581 pub fn toggle_comments(
14582 &mut self,
14583 action: &ToggleComments,
14584 window: &mut Window,
14585 cx: &mut Context<Self>,
14586 ) {
14587 if self.read_only(cx) {
14588 return;
14589 }
14590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14591 let text_layout_details = &self.text_layout_details(window);
14592 self.transact(window, cx, |this, window, cx| {
14593 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14594 let mut edits = Vec::new();
14595 let mut selection_edit_ranges = Vec::new();
14596 let mut last_toggled_row = None;
14597 let snapshot = this.buffer.read(cx).read(cx);
14598 let empty_str: Arc<str> = Arc::default();
14599 let mut suffixes_inserted = Vec::new();
14600 let ignore_indent = action.ignore_indent;
14601
14602 fn comment_prefix_range(
14603 snapshot: &MultiBufferSnapshot,
14604 row: MultiBufferRow,
14605 comment_prefix: &str,
14606 comment_prefix_whitespace: &str,
14607 ignore_indent: bool,
14608 ) -> Range<Point> {
14609 let indent_size = if ignore_indent {
14610 0
14611 } else {
14612 snapshot.indent_size_for_line(row).len
14613 };
14614
14615 let start = Point::new(row.0, indent_size);
14616
14617 let mut line_bytes = snapshot
14618 .bytes_in_range(start..snapshot.max_point())
14619 .flatten()
14620 .copied();
14621
14622 // If this line currently begins with the line comment prefix, then record
14623 // the range containing the prefix.
14624 if line_bytes
14625 .by_ref()
14626 .take(comment_prefix.len())
14627 .eq(comment_prefix.bytes())
14628 {
14629 // Include any whitespace that matches the comment prefix.
14630 let matching_whitespace_len = line_bytes
14631 .zip(comment_prefix_whitespace.bytes())
14632 .take_while(|(a, b)| a == b)
14633 .count() as u32;
14634 let end = Point::new(
14635 start.row,
14636 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14637 );
14638 start..end
14639 } else {
14640 start..start
14641 }
14642 }
14643
14644 fn comment_suffix_range(
14645 snapshot: &MultiBufferSnapshot,
14646 row: MultiBufferRow,
14647 comment_suffix: &str,
14648 comment_suffix_has_leading_space: bool,
14649 ) -> Range<Point> {
14650 let end = Point::new(row.0, snapshot.line_len(row));
14651 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14652
14653 let mut line_end_bytes = snapshot
14654 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14655 .flatten()
14656 .copied();
14657
14658 let leading_space_len = if suffix_start_column > 0
14659 && line_end_bytes.next() == Some(b' ')
14660 && comment_suffix_has_leading_space
14661 {
14662 1
14663 } else {
14664 0
14665 };
14666
14667 // If this line currently begins with the line comment prefix, then record
14668 // the range containing the prefix.
14669 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14670 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14671 start..end
14672 } else {
14673 end..end
14674 }
14675 }
14676
14677 // TODO: Handle selections that cross excerpts
14678 for selection in &mut selections {
14679 let start_column = snapshot
14680 .indent_size_for_line(MultiBufferRow(selection.start.row))
14681 .len;
14682 let language = if let Some(language) =
14683 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14684 {
14685 language
14686 } else {
14687 continue;
14688 };
14689
14690 selection_edit_ranges.clear();
14691
14692 // If multiple selections contain a given row, avoid processing that
14693 // row more than once.
14694 let mut start_row = MultiBufferRow(selection.start.row);
14695 if last_toggled_row == Some(start_row) {
14696 start_row = start_row.next_row();
14697 }
14698 let end_row =
14699 if selection.end.row > selection.start.row && selection.end.column == 0 {
14700 MultiBufferRow(selection.end.row - 1)
14701 } else {
14702 MultiBufferRow(selection.end.row)
14703 };
14704 last_toggled_row = Some(end_row);
14705
14706 if start_row > end_row {
14707 continue;
14708 }
14709
14710 // If the language has line comments, toggle those.
14711 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14712
14713 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14714 if ignore_indent {
14715 full_comment_prefixes = full_comment_prefixes
14716 .into_iter()
14717 .map(|s| Arc::from(s.trim_end()))
14718 .collect();
14719 }
14720
14721 if !full_comment_prefixes.is_empty() {
14722 let first_prefix = full_comment_prefixes
14723 .first()
14724 .expect("prefixes is non-empty");
14725 let prefix_trimmed_lengths = full_comment_prefixes
14726 .iter()
14727 .map(|p| p.trim_end_matches(' ').len())
14728 .collect::<SmallVec<[usize; 4]>>();
14729
14730 let mut all_selection_lines_are_comments = true;
14731
14732 for row in start_row.0..=end_row.0 {
14733 let row = MultiBufferRow(row);
14734 if start_row < end_row && snapshot.is_line_blank(row) {
14735 continue;
14736 }
14737
14738 let prefix_range = full_comment_prefixes
14739 .iter()
14740 .zip(prefix_trimmed_lengths.iter().copied())
14741 .map(|(prefix, trimmed_prefix_len)| {
14742 comment_prefix_range(
14743 snapshot.deref(),
14744 row,
14745 &prefix[..trimmed_prefix_len],
14746 &prefix[trimmed_prefix_len..],
14747 ignore_indent,
14748 )
14749 })
14750 .max_by_key(|range| range.end.column - range.start.column)
14751 .expect("prefixes is non-empty");
14752
14753 if prefix_range.is_empty() {
14754 all_selection_lines_are_comments = false;
14755 }
14756
14757 selection_edit_ranges.push(prefix_range);
14758 }
14759
14760 if all_selection_lines_are_comments {
14761 edits.extend(
14762 selection_edit_ranges
14763 .iter()
14764 .cloned()
14765 .map(|range| (range, empty_str.clone())),
14766 );
14767 } else {
14768 let min_column = selection_edit_ranges
14769 .iter()
14770 .map(|range| range.start.column)
14771 .min()
14772 .unwrap_or(0);
14773 edits.extend(selection_edit_ranges.iter().map(|range| {
14774 let position = Point::new(range.start.row, min_column);
14775 (position..position, first_prefix.clone())
14776 }));
14777 }
14778 } else if let Some(BlockCommentConfig {
14779 start: full_comment_prefix,
14780 end: comment_suffix,
14781 ..
14782 }) = language.block_comment()
14783 {
14784 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14785 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14786 let prefix_range = comment_prefix_range(
14787 snapshot.deref(),
14788 start_row,
14789 comment_prefix,
14790 comment_prefix_whitespace,
14791 ignore_indent,
14792 );
14793 let suffix_range = comment_suffix_range(
14794 snapshot.deref(),
14795 end_row,
14796 comment_suffix.trim_start_matches(' '),
14797 comment_suffix.starts_with(' '),
14798 );
14799
14800 if prefix_range.is_empty() || suffix_range.is_empty() {
14801 edits.push((
14802 prefix_range.start..prefix_range.start,
14803 full_comment_prefix.clone(),
14804 ));
14805 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14806 suffixes_inserted.push((end_row, comment_suffix.len()));
14807 } else {
14808 edits.push((prefix_range, empty_str.clone()));
14809 edits.push((suffix_range, empty_str.clone()));
14810 }
14811 } else {
14812 continue;
14813 }
14814 }
14815
14816 drop(snapshot);
14817 this.buffer.update(cx, |buffer, cx| {
14818 buffer.edit(edits, None, cx);
14819 });
14820
14821 // Adjust selections so that they end before any comment suffixes that
14822 // were inserted.
14823 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14824 let mut selections = this.selections.all::<Point>(cx);
14825 let snapshot = this.buffer.read(cx).read(cx);
14826 for selection in &mut selections {
14827 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14828 match row.cmp(&MultiBufferRow(selection.end.row)) {
14829 Ordering::Less => {
14830 suffixes_inserted.next();
14831 continue;
14832 }
14833 Ordering::Greater => break,
14834 Ordering::Equal => {
14835 if selection.end.column == snapshot.line_len(row) {
14836 if selection.is_empty() {
14837 selection.start.column -= suffix_len as u32;
14838 }
14839 selection.end.column -= suffix_len as u32;
14840 }
14841 break;
14842 }
14843 }
14844 }
14845 }
14846
14847 drop(snapshot);
14848 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14849
14850 let selections = this.selections.all::<Point>(cx);
14851 let selections_on_single_row = selections.windows(2).all(|selections| {
14852 selections[0].start.row == selections[1].start.row
14853 && selections[0].end.row == selections[1].end.row
14854 && selections[0].start.row == selections[0].end.row
14855 });
14856 let selections_selecting = selections
14857 .iter()
14858 .any(|selection| selection.start != selection.end);
14859 let advance_downwards = action.advance_downwards
14860 && selections_on_single_row
14861 && !selections_selecting
14862 && !matches!(this.mode, EditorMode::SingleLine);
14863
14864 if advance_downwards {
14865 let snapshot = this.buffer.read(cx).snapshot(cx);
14866
14867 this.change_selections(Default::default(), window, cx, |s| {
14868 s.move_cursors_with(|display_snapshot, display_point, _| {
14869 let mut point = display_point.to_point(display_snapshot);
14870 point.row += 1;
14871 point = snapshot.clip_point(point, Bias::Left);
14872 let display_point = point.to_display_point(display_snapshot);
14873 let goal = SelectionGoal::HorizontalPosition(
14874 display_snapshot
14875 .x_for_display_point(display_point, text_layout_details)
14876 .into(),
14877 );
14878 (display_point, goal)
14879 })
14880 });
14881 }
14882 });
14883 }
14884
14885 pub fn select_enclosing_symbol(
14886 &mut self,
14887 _: &SelectEnclosingSymbol,
14888 window: &mut Window,
14889 cx: &mut Context<Self>,
14890 ) {
14891 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14892
14893 let buffer = self.buffer.read(cx).snapshot(cx);
14894 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14895
14896 fn update_selection(
14897 selection: &Selection<usize>,
14898 buffer_snap: &MultiBufferSnapshot,
14899 ) -> Option<Selection<usize>> {
14900 let cursor = selection.head();
14901 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14902 for symbol in symbols.iter().rev() {
14903 let start = symbol.range.start.to_offset(buffer_snap);
14904 let end = symbol.range.end.to_offset(buffer_snap);
14905 let new_range = start..end;
14906 if start < selection.start || end > selection.end {
14907 return Some(Selection {
14908 id: selection.id,
14909 start: new_range.start,
14910 end: new_range.end,
14911 goal: SelectionGoal::None,
14912 reversed: selection.reversed,
14913 });
14914 }
14915 }
14916 None
14917 }
14918
14919 let mut selected_larger_symbol = false;
14920 let new_selections = old_selections
14921 .iter()
14922 .map(|selection| match update_selection(selection, &buffer) {
14923 Some(new_selection) => {
14924 if new_selection.range() != selection.range() {
14925 selected_larger_symbol = true;
14926 }
14927 new_selection
14928 }
14929 None => selection.clone(),
14930 })
14931 .collect::<Vec<_>>();
14932
14933 if selected_larger_symbol {
14934 self.change_selections(Default::default(), window, cx, |s| {
14935 s.select(new_selections);
14936 });
14937 }
14938 }
14939
14940 pub fn select_larger_syntax_node(
14941 &mut self,
14942 _: &SelectLargerSyntaxNode,
14943 window: &mut Window,
14944 cx: &mut Context<Self>,
14945 ) {
14946 let Some(visible_row_count) = self.visible_row_count() else {
14947 return;
14948 };
14949 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14950 if old_selections.is_empty() {
14951 return;
14952 }
14953
14954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14955
14956 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14957 let buffer = self.buffer.read(cx).snapshot(cx);
14958
14959 let mut selected_larger_node = false;
14960 let mut new_selections = old_selections
14961 .iter()
14962 .map(|selection| {
14963 let old_range = selection.start..selection.end;
14964
14965 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14966 // manually select word at selection
14967 if ["string_content", "inline"].contains(&node.kind()) {
14968 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14969 // ignore if word is already selected
14970 if !word_range.is_empty() && old_range != word_range {
14971 let (last_word_range, _) =
14972 buffer.surrounding_word(old_range.end, false);
14973 // only select word if start and end point belongs to same word
14974 if word_range == last_word_range {
14975 selected_larger_node = true;
14976 return Selection {
14977 id: selection.id,
14978 start: word_range.start,
14979 end: word_range.end,
14980 goal: SelectionGoal::None,
14981 reversed: selection.reversed,
14982 };
14983 }
14984 }
14985 }
14986 }
14987
14988 let mut new_range = old_range.clone();
14989 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
14990 {
14991 new_range = match containing_range {
14992 MultiOrSingleBufferOffsetRange::Single(_) => break,
14993 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14994 };
14995 if !node.is_named() {
14996 continue;
14997 }
14998 if !display_map.intersects_fold(new_range.start)
14999 && !display_map.intersects_fold(new_range.end)
15000 {
15001 break;
15002 }
15003 }
15004
15005 selected_larger_node |= new_range != old_range;
15006 Selection {
15007 id: selection.id,
15008 start: new_range.start,
15009 end: new_range.end,
15010 goal: SelectionGoal::None,
15011 reversed: selection.reversed,
15012 }
15013 })
15014 .collect::<Vec<_>>();
15015
15016 if !selected_larger_node {
15017 return; // don't put this call in the history
15018 }
15019
15020 // scroll based on transformation done to the last selection created by the user
15021 let (last_old, last_new) = old_selections
15022 .last()
15023 .zip(new_selections.last().cloned())
15024 .expect("old_selections isn't empty");
15025
15026 // revert selection
15027 let is_selection_reversed = {
15028 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15029 new_selections.last_mut().expect("checked above").reversed =
15030 should_newest_selection_be_reversed;
15031 should_newest_selection_be_reversed
15032 };
15033
15034 if selected_larger_node {
15035 self.select_syntax_node_history.disable_clearing = true;
15036 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15037 s.select(new_selections.clone());
15038 });
15039 self.select_syntax_node_history.disable_clearing = false;
15040 }
15041
15042 let start_row = last_new.start.to_display_point(&display_map).row().0;
15043 let end_row = last_new.end.to_display_point(&display_map).row().0;
15044 let selection_height = end_row - start_row + 1;
15045 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15046
15047 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15048 let scroll_behavior = if fits_on_the_screen {
15049 self.request_autoscroll(Autoscroll::fit(), cx);
15050 SelectSyntaxNodeScrollBehavior::FitSelection
15051 } else if is_selection_reversed {
15052 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15053 SelectSyntaxNodeScrollBehavior::CursorTop
15054 } else {
15055 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15056 SelectSyntaxNodeScrollBehavior::CursorBottom
15057 };
15058
15059 self.select_syntax_node_history.push((
15060 old_selections,
15061 scroll_behavior,
15062 is_selection_reversed,
15063 ));
15064 }
15065
15066 pub fn select_smaller_syntax_node(
15067 &mut self,
15068 _: &SelectSmallerSyntaxNode,
15069 window: &mut Window,
15070 cx: &mut Context<Self>,
15071 ) {
15072 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15073
15074 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15075 self.select_syntax_node_history.pop()
15076 {
15077 if let Some(selection) = selections.last_mut() {
15078 selection.reversed = is_selection_reversed;
15079 }
15080
15081 self.select_syntax_node_history.disable_clearing = true;
15082 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15083 s.select(selections.to_vec());
15084 });
15085 self.select_syntax_node_history.disable_clearing = false;
15086
15087 match scroll_behavior {
15088 SelectSyntaxNodeScrollBehavior::CursorTop => {
15089 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15090 }
15091 SelectSyntaxNodeScrollBehavior::FitSelection => {
15092 self.request_autoscroll(Autoscroll::fit(), cx);
15093 }
15094 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15095 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15096 }
15097 }
15098 }
15099 }
15100
15101 pub fn unwrap_syntax_node(
15102 &mut self,
15103 _: &UnwrapSyntaxNode,
15104 window: &mut Window,
15105 cx: &mut Context<Self>,
15106 ) {
15107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15108
15109 let buffer = self.buffer.read(cx).snapshot(cx);
15110 let selections = self
15111 .selections
15112 .all::<usize>(cx)
15113 .into_iter()
15114 // subtracting the offset requires sorting
15115 .sorted_by_key(|i| i.start);
15116
15117 let full_edits = selections
15118 .into_iter()
15119 .filter_map(|selection| {
15120 // Only requires two branches once if-let-chains stabilize (#53667)
15121 let child = if !selection.is_empty() {
15122 selection.range()
15123 } else if let Some((_, ancestor_range)) =
15124 buffer.syntax_ancestor(selection.start..selection.end)
15125 {
15126 match ancestor_range {
15127 MultiOrSingleBufferOffsetRange::Single(range) => range,
15128 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15129 }
15130 } else {
15131 selection.range()
15132 };
15133
15134 let mut parent = child.clone();
15135 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15136 parent = match ancestor_range {
15137 MultiOrSingleBufferOffsetRange::Single(range) => range,
15138 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15139 };
15140 if parent.start < child.start || parent.end > child.end {
15141 break;
15142 }
15143 }
15144
15145 if parent == child {
15146 return None;
15147 }
15148 let text = buffer.text_for_range(child).collect::<String>();
15149 Some((selection.id, parent, text))
15150 })
15151 .collect::<Vec<_>>();
15152
15153 self.transact(window, cx, |this, window, cx| {
15154 this.buffer.update(cx, |buffer, cx| {
15155 buffer.edit(
15156 full_edits
15157 .iter()
15158 .map(|(_, p, t)| (p.clone(), t.clone()))
15159 .collect::<Vec<_>>(),
15160 None,
15161 cx,
15162 );
15163 });
15164 this.change_selections(Default::default(), window, cx, |s| {
15165 let mut offset = 0;
15166 let mut selections = vec![];
15167 for (id, parent, text) in full_edits {
15168 let start = parent.start - offset;
15169 offset += parent.len() - text.len();
15170 selections.push(Selection {
15171 id,
15172 start,
15173 end: start + text.len(),
15174 reversed: false,
15175 goal: Default::default(),
15176 });
15177 }
15178 s.select(selections);
15179 });
15180 });
15181 }
15182
15183 pub fn select_next_syntax_node(
15184 &mut self,
15185 _: &SelectNextSyntaxNode,
15186 window: &mut Window,
15187 cx: &mut Context<Self>,
15188 ) {
15189 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15190 if old_selections.is_empty() {
15191 return;
15192 }
15193
15194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15195
15196 let buffer = self.buffer.read(cx).snapshot(cx);
15197 let mut selected_sibling = false;
15198
15199 let new_selections = old_selections
15200 .iter()
15201 .map(|selection| {
15202 let old_range = selection.start..selection.end;
15203
15204 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15205 let new_range = node.byte_range();
15206 selected_sibling = true;
15207 Selection {
15208 id: selection.id,
15209 start: new_range.start,
15210 end: new_range.end,
15211 goal: SelectionGoal::None,
15212 reversed: selection.reversed,
15213 }
15214 } else {
15215 selection.clone()
15216 }
15217 })
15218 .collect::<Vec<_>>();
15219
15220 if selected_sibling {
15221 self.change_selections(
15222 SelectionEffects::scroll(Autoscroll::fit()),
15223 window,
15224 cx,
15225 |s| {
15226 s.select(new_selections);
15227 },
15228 );
15229 }
15230 }
15231
15232 pub fn select_prev_syntax_node(
15233 &mut self,
15234 _: &SelectPreviousSyntaxNode,
15235 window: &mut Window,
15236 cx: &mut Context<Self>,
15237 ) {
15238 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15239 if old_selections.is_empty() {
15240 return;
15241 }
15242
15243 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15244
15245 let buffer = self.buffer.read(cx).snapshot(cx);
15246 let mut selected_sibling = false;
15247
15248 let new_selections = old_selections
15249 .iter()
15250 .map(|selection| {
15251 let old_range = selection.start..selection.end;
15252
15253 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15254 let new_range = node.byte_range();
15255 selected_sibling = true;
15256 Selection {
15257 id: selection.id,
15258 start: new_range.start,
15259 end: new_range.end,
15260 goal: SelectionGoal::None,
15261 reversed: selection.reversed,
15262 }
15263 } else {
15264 selection.clone()
15265 }
15266 })
15267 .collect::<Vec<_>>();
15268
15269 if selected_sibling {
15270 self.change_selections(
15271 SelectionEffects::scroll(Autoscroll::fit()),
15272 window,
15273 cx,
15274 |s| {
15275 s.select(new_selections);
15276 },
15277 );
15278 }
15279 }
15280
15281 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15282 if !EditorSettings::get_global(cx).gutter.runnables {
15283 self.clear_tasks();
15284 return Task::ready(());
15285 }
15286 let project = self.project().map(Entity::downgrade);
15287 let task_sources = self.lsp_task_sources(cx);
15288 let multi_buffer = self.buffer.downgrade();
15289 cx.spawn_in(window, async move |editor, cx| {
15290 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15291 let Some(project) = project.and_then(|p| p.upgrade()) else {
15292 return;
15293 };
15294 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15295 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15296 }) else {
15297 return;
15298 };
15299
15300 let hide_runnables = project
15301 .update(cx, |project, _| project.is_via_collab())
15302 .unwrap_or(true);
15303 if hide_runnables {
15304 return;
15305 }
15306 let new_rows =
15307 cx.background_spawn({
15308 let snapshot = display_snapshot.clone();
15309 async move {
15310 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15311 }
15312 })
15313 .await;
15314 let Ok(lsp_tasks) =
15315 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15316 else {
15317 return;
15318 };
15319 let lsp_tasks = lsp_tasks.await;
15320
15321 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15322 lsp_tasks
15323 .into_iter()
15324 .flat_map(|(kind, tasks)| {
15325 tasks.into_iter().filter_map(move |(location, task)| {
15326 Some((kind.clone(), location?, task))
15327 })
15328 })
15329 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15330 let buffer = location.target.buffer;
15331 let buffer_snapshot = buffer.read(cx).snapshot();
15332 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15333 |(excerpt_id, snapshot, _)| {
15334 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15335 display_snapshot
15336 .buffer_snapshot
15337 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15338 } else {
15339 None
15340 }
15341 },
15342 );
15343 if let Some(offset) = offset {
15344 let task_buffer_range =
15345 location.target.range.to_point(&buffer_snapshot);
15346 let context_buffer_range =
15347 task_buffer_range.to_offset(&buffer_snapshot);
15348 let context_range = BufferOffset(context_buffer_range.start)
15349 ..BufferOffset(context_buffer_range.end);
15350
15351 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15352 .or_insert_with(|| RunnableTasks {
15353 templates: Vec::new(),
15354 offset,
15355 column: task_buffer_range.start.column,
15356 extra_variables: HashMap::default(),
15357 context_range,
15358 })
15359 .templates
15360 .push((kind, task.original_task().clone()));
15361 }
15362
15363 acc
15364 })
15365 }) else {
15366 return;
15367 };
15368
15369 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15370 buffer.language_settings(cx).tasks.prefer_lsp
15371 }) else {
15372 return;
15373 };
15374
15375 let rows = Self::runnable_rows(
15376 project,
15377 display_snapshot,
15378 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15379 new_rows,
15380 cx.clone(),
15381 )
15382 .await;
15383 editor
15384 .update(cx, |editor, _| {
15385 editor.clear_tasks();
15386 for (key, mut value) in rows {
15387 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15388 value.templates.extend(lsp_tasks.templates);
15389 }
15390
15391 editor.insert_tasks(key, value);
15392 }
15393 for (key, value) in lsp_tasks_by_rows {
15394 editor.insert_tasks(key, value);
15395 }
15396 })
15397 .ok();
15398 })
15399 }
15400 fn fetch_runnable_ranges(
15401 snapshot: &DisplaySnapshot,
15402 range: Range<Anchor>,
15403 ) -> Vec<language::RunnableRange> {
15404 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15405 }
15406
15407 fn runnable_rows(
15408 project: Entity<Project>,
15409 snapshot: DisplaySnapshot,
15410 prefer_lsp: bool,
15411 runnable_ranges: Vec<RunnableRange>,
15412 cx: AsyncWindowContext,
15413 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15414 cx.spawn(async move |cx| {
15415 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15416 for mut runnable in runnable_ranges {
15417 let Some(tasks) = cx
15418 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15419 .ok()
15420 else {
15421 continue;
15422 };
15423 let mut tasks = tasks.await;
15424
15425 if prefer_lsp {
15426 tasks.retain(|(task_kind, _)| {
15427 !matches!(task_kind, TaskSourceKind::Language { .. })
15428 });
15429 }
15430 if tasks.is_empty() {
15431 continue;
15432 }
15433
15434 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15435 let Some(row) = snapshot
15436 .buffer_snapshot
15437 .buffer_line_for_row(MultiBufferRow(point.row))
15438 .map(|(_, range)| range.start.row)
15439 else {
15440 continue;
15441 };
15442
15443 let context_range =
15444 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15445 runnable_rows.push((
15446 (runnable.buffer_id, row),
15447 RunnableTasks {
15448 templates: tasks,
15449 offset: snapshot
15450 .buffer_snapshot
15451 .anchor_before(runnable.run_range.start),
15452 context_range,
15453 column: point.column,
15454 extra_variables: runnable.extra_captures,
15455 },
15456 ));
15457 }
15458 runnable_rows
15459 })
15460 }
15461
15462 fn templates_with_tags(
15463 project: &Entity<Project>,
15464 runnable: &mut Runnable,
15465 cx: &mut App,
15466 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15467 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15468 let (worktree_id, file) = project
15469 .buffer_for_id(runnable.buffer, cx)
15470 .and_then(|buffer| buffer.read(cx).file())
15471 .map(|file| (file.worktree_id(cx), file.clone()))
15472 .unzip();
15473
15474 (
15475 project.task_store().read(cx).task_inventory().cloned(),
15476 worktree_id,
15477 file,
15478 )
15479 });
15480
15481 let tags = mem::take(&mut runnable.tags);
15482 let language = runnable.language.clone();
15483 cx.spawn(async move |cx| {
15484 let mut templates_with_tags = Vec::new();
15485 if let Some(inventory) = inventory {
15486 for RunnableTag(tag) in tags {
15487 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15488 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15489 }) else {
15490 return templates_with_tags;
15491 };
15492 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15493 move |(_, template)| {
15494 template.tags.iter().any(|source_tag| source_tag == &tag)
15495 },
15496 ));
15497 }
15498 }
15499 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15500
15501 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15502 // Strongest source wins; if we have worktree tag binding, prefer that to
15503 // global and language bindings;
15504 // if we have a global binding, prefer that to language binding.
15505 let first_mismatch = templates_with_tags
15506 .iter()
15507 .position(|(tag_source, _)| tag_source != leading_tag_source);
15508 if let Some(index) = first_mismatch {
15509 templates_with_tags.truncate(index);
15510 }
15511 }
15512
15513 templates_with_tags
15514 })
15515 }
15516
15517 pub fn move_to_enclosing_bracket(
15518 &mut self,
15519 _: &MoveToEnclosingBracket,
15520 window: &mut Window,
15521 cx: &mut Context<Self>,
15522 ) {
15523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15524 self.change_selections(Default::default(), window, cx, |s| {
15525 s.move_offsets_with(|snapshot, selection| {
15526 let Some(enclosing_bracket_ranges) =
15527 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15528 else {
15529 return;
15530 };
15531
15532 let mut best_length = usize::MAX;
15533 let mut best_inside = false;
15534 let mut best_in_bracket_range = false;
15535 let mut best_destination = None;
15536 for (open, close) in enclosing_bracket_ranges {
15537 let close = close.to_inclusive();
15538 let length = close.end() - open.start;
15539 let inside = selection.start >= open.end && selection.end <= *close.start();
15540 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15541 || close.contains(&selection.head());
15542
15543 // If best is next to a bracket and current isn't, skip
15544 if !in_bracket_range && best_in_bracket_range {
15545 continue;
15546 }
15547
15548 // Prefer smaller lengths unless best is inside and current isn't
15549 if length > best_length && (best_inside || !inside) {
15550 continue;
15551 }
15552
15553 best_length = length;
15554 best_inside = inside;
15555 best_in_bracket_range = in_bracket_range;
15556 best_destination = Some(
15557 if close.contains(&selection.start) && close.contains(&selection.end) {
15558 if inside { open.end } else { open.start }
15559 } else if inside {
15560 *close.start()
15561 } else {
15562 *close.end()
15563 },
15564 );
15565 }
15566
15567 if let Some(destination) = best_destination {
15568 selection.collapse_to(destination, SelectionGoal::None);
15569 }
15570 })
15571 });
15572 }
15573
15574 pub fn undo_selection(
15575 &mut self,
15576 _: &UndoSelection,
15577 window: &mut Window,
15578 cx: &mut Context<Self>,
15579 ) {
15580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15581 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15582 self.selection_history.mode = SelectionHistoryMode::Undoing;
15583 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15584 this.end_selection(window, cx);
15585 this.change_selections(
15586 SelectionEffects::scroll(Autoscroll::newest()),
15587 window,
15588 cx,
15589 |s| s.select_anchors(entry.selections.to_vec()),
15590 );
15591 });
15592 self.selection_history.mode = SelectionHistoryMode::Normal;
15593
15594 self.select_next_state = entry.select_next_state;
15595 self.select_prev_state = entry.select_prev_state;
15596 self.add_selections_state = entry.add_selections_state;
15597 }
15598 }
15599
15600 pub fn redo_selection(
15601 &mut self,
15602 _: &RedoSelection,
15603 window: &mut Window,
15604 cx: &mut Context<Self>,
15605 ) {
15606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15607 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15608 self.selection_history.mode = SelectionHistoryMode::Redoing;
15609 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15610 this.end_selection(window, cx);
15611 this.change_selections(
15612 SelectionEffects::scroll(Autoscroll::newest()),
15613 window,
15614 cx,
15615 |s| s.select_anchors(entry.selections.to_vec()),
15616 );
15617 });
15618 self.selection_history.mode = SelectionHistoryMode::Normal;
15619
15620 self.select_next_state = entry.select_next_state;
15621 self.select_prev_state = entry.select_prev_state;
15622 self.add_selections_state = entry.add_selections_state;
15623 }
15624 }
15625
15626 pub fn expand_excerpts(
15627 &mut self,
15628 action: &ExpandExcerpts,
15629 _: &mut Window,
15630 cx: &mut Context<Self>,
15631 ) {
15632 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15633 }
15634
15635 pub fn expand_excerpts_down(
15636 &mut self,
15637 action: &ExpandExcerptsDown,
15638 _: &mut Window,
15639 cx: &mut Context<Self>,
15640 ) {
15641 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15642 }
15643
15644 pub fn expand_excerpts_up(
15645 &mut self,
15646 action: &ExpandExcerptsUp,
15647 _: &mut Window,
15648 cx: &mut Context<Self>,
15649 ) {
15650 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15651 }
15652
15653 pub fn expand_excerpts_for_direction(
15654 &mut self,
15655 lines: u32,
15656 direction: ExpandExcerptDirection,
15657
15658 cx: &mut Context<Self>,
15659 ) {
15660 let selections = self.selections.disjoint_anchors();
15661
15662 let lines = if lines == 0 {
15663 EditorSettings::get_global(cx).expand_excerpt_lines
15664 } else {
15665 lines
15666 };
15667
15668 self.buffer.update(cx, |buffer, cx| {
15669 let snapshot = buffer.snapshot(cx);
15670 let mut excerpt_ids = selections
15671 .iter()
15672 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15673 .collect::<Vec<_>>();
15674 excerpt_ids.sort();
15675 excerpt_ids.dedup();
15676 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15677 })
15678 }
15679
15680 pub fn expand_excerpt(
15681 &mut self,
15682 excerpt: ExcerptId,
15683 direction: ExpandExcerptDirection,
15684 window: &mut Window,
15685 cx: &mut Context<Self>,
15686 ) {
15687 let current_scroll_position = self.scroll_position(cx);
15688 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15689 let mut should_scroll_up = false;
15690
15691 if direction == ExpandExcerptDirection::Down {
15692 let multi_buffer = self.buffer.read(cx);
15693 let snapshot = multi_buffer.snapshot(cx);
15694 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15695 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15696 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15697 {
15698 let buffer_snapshot = buffer.read(cx).snapshot();
15699 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15700 let last_row = buffer_snapshot.max_point().row;
15701 let lines_below = last_row.saturating_sub(excerpt_end_row);
15702 should_scroll_up = lines_below >= lines_to_expand;
15703 }
15704 }
15705
15706 self.buffer.update(cx, |buffer, cx| {
15707 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15708 });
15709
15710 if should_scroll_up {
15711 let new_scroll_position =
15712 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15713 self.set_scroll_position(new_scroll_position, window, cx);
15714 }
15715 }
15716
15717 pub fn go_to_singleton_buffer_point(
15718 &mut self,
15719 point: Point,
15720 window: &mut Window,
15721 cx: &mut Context<Self>,
15722 ) {
15723 self.go_to_singleton_buffer_range(point..point, window, cx);
15724 }
15725
15726 pub fn go_to_singleton_buffer_range(
15727 &mut self,
15728 range: Range<Point>,
15729 window: &mut Window,
15730 cx: &mut Context<Self>,
15731 ) {
15732 let multibuffer = self.buffer().read(cx);
15733 let Some(buffer) = multibuffer.as_singleton() else {
15734 return;
15735 };
15736 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15737 return;
15738 };
15739 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15740 return;
15741 };
15742 self.change_selections(
15743 SelectionEffects::default().nav_history(true),
15744 window,
15745 cx,
15746 |s| s.select_anchor_ranges([start..end]),
15747 );
15748 }
15749
15750 pub fn go_to_diagnostic(
15751 &mut self,
15752 action: &GoToDiagnostic,
15753 window: &mut Window,
15754 cx: &mut Context<Self>,
15755 ) {
15756 if !self.diagnostics_enabled() {
15757 return;
15758 }
15759 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15760 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15761 }
15762
15763 pub fn go_to_prev_diagnostic(
15764 &mut self,
15765 action: &GoToPreviousDiagnostic,
15766 window: &mut Window,
15767 cx: &mut Context<Self>,
15768 ) {
15769 if !self.diagnostics_enabled() {
15770 return;
15771 }
15772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15773 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15774 }
15775
15776 pub fn go_to_diagnostic_impl(
15777 &mut self,
15778 direction: Direction,
15779 severity: GoToDiagnosticSeverityFilter,
15780 window: &mut Window,
15781 cx: &mut Context<Self>,
15782 ) {
15783 let buffer = self.buffer.read(cx).snapshot(cx);
15784 let selection = self.selections.newest::<usize>(cx);
15785
15786 let mut active_group_id = None;
15787 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15788 && active_group.active_range.start.to_offset(&buffer) == selection.start
15789 {
15790 active_group_id = Some(active_group.group_id);
15791 }
15792
15793 fn filtered(
15794 snapshot: EditorSnapshot,
15795 severity: GoToDiagnosticSeverityFilter,
15796 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15797 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15798 diagnostics
15799 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15800 .filter(|entry| entry.range.start != entry.range.end)
15801 .filter(|entry| !entry.diagnostic.is_unnecessary)
15802 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15803 }
15804
15805 let snapshot = self.snapshot(window, cx);
15806 let before = filtered(
15807 snapshot.clone(),
15808 severity,
15809 buffer
15810 .diagnostics_in_range(0..selection.start)
15811 .filter(|entry| entry.range.start <= selection.start),
15812 );
15813 let after = filtered(
15814 snapshot,
15815 severity,
15816 buffer
15817 .diagnostics_in_range(selection.start..buffer.len())
15818 .filter(|entry| entry.range.start >= selection.start),
15819 );
15820
15821 let mut found: Option<DiagnosticEntry<usize>> = None;
15822 if direction == Direction::Prev {
15823 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15824 {
15825 for diagnostic in prev_diagnostics.into_iter().rev() {
15826 if diagnostic.range.start != selection.start
15827 || active_group_id
15828 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15829 {
15830 found = Some(diagnostic);
15831 break 'outer;
15832 }
15833 }
15834 }
15835 } else {
15836 for diagnostic in after.chain(before) {
15837 if diagnostic.range.start != selection.start
15838 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15839 {
15840 found = Some(diagnostic);
15841 break;
15842 }
15843 }
15844 }
15845 let Some(next_diagnostic) = found else {
15846 return;
15847 };
15848
15849 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15850 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15851 return;
15852 };
15853 self.change_selections(Default::default(), window, cx, |s| {
15854 s.select_ranges(vec![
15855 next_diagnostic.range.start..next_diagnostic.range.start,
15856 ])
15857 });
15858 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15859 self.refresh_edit_prediction(false, true, window, cx);
15860 }
15861
15862 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15864 let snapshot = self.snapshot(window, cx);
15865 let selection = self.selections.newest::<Point>(cx);
15866 self.go_to_hunk_before_or_after_position(
15867 &snapshot,
15868 selection.head(),
15869 Direction::Next,
15870 window,
15871 cx,
15872 );
15873 }
15874
15875 pub fn go_to_hunk_before_or_after_position(
15876 &mut self,
15877 snapshot: &EditorSnapshot,
15878 position: Point,
15879 direction: Direction,
15880 window: &mut Window,
15881 cx: &mut Context<Editor>,
15882 ) {
15883 let row = if direction == Direction::Next {
15884 self.hunk_after_position(snapshot, position)
15885 .map(|hunk| hunk.row_range.start)
15886 } else {
15887 self.hunk_before_position(snapshot, position)
15888 };
15889
15890 if let Some(row) = row {
15891 let destination = Point::new(row.0, 0);
15892 let autoscroll = Autoscroll::center();
15893
15894 self.unfold_ranges(&[destination..destination], false, false, cx);
15895 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15896 s.select_ranges([destination..destination]);
15897 });
15898 }
15899 }
15900
15901 fn hunk_after_position(
15902 &mut self,
15903 snapshot: &EditorSnapshot,
15904 position: Point,
15905 ) -> Option<MultiBufferDiffHunk> {
15906 snapshot
15907 .buffer_snapshot
15908 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15909 .find(|hunk| hunk.row_range.start.0 > position.row)
15910 .or_else(|| {
15911 snapshot
15912 .buffer_snapshot
15913 .diff_hunks_in_range(Point::zero()..position)
15914 .find(|hunk| hunk.row_range.end.0 < position.row)
15915 })
15916 }
15917
15918 fn go_to_prev_hunk(
15919 &mut self,
15920 _: &GoToPreviousHunk,
15921 window: &mut Window,
15922 cx: &mut Context<Self>,
15923 ) {
15924 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15925 let snapshot = self.snapshot(window, cx);
15926 let selection = self.selections.newest::<Point>(cx);
15927 self.go_to_hunk_before_or_after_position(
15928 &snapshot,
15929 selection.head(),
15930 Direction::Prev,
15931 window,
15932 cx,
15933 );
15934 }
15935
15936 fn hunk_before_position(
15937 &mut self,
15938 snapshot: &EditorSnapshot,
15939 position: Point,
15940 ) -> Option<MultiBufferRow> {
15941 snapshot
15942 .buffer_snapshot
15943 .diff_hunk_before(position)
15944 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15945 }
15946
15947 fn go_to_next_change(
15948 &mut self,
15949 _: &GoToNextChange,
15950 window: &mut Window,
15951 cx: &mut Context<Self>,
15952 ) {
15953 if let Some(selections) = self
15954 .change_list
15955 .next_change(1, Direction::Next)
15956 .map(|s| s.to_vec())
15957 {
15958 self.change_selections(Default::default(), window, cx, |s| {
15959 let map = s.display_map();
15960 s.select_display_ranges(selections.iter().map(|a| {
15961 let point = a.to_display_point(&map);
15962 point..point
15963 }))
15964 })
15965 }
15966 }
15967
15968 fn go_to_previous_change(
15969 &mut self,
15970 _: &GoToPreviousChange,
15971 window: &mut Window,
15972 cx: &mut Context<Self>,
15973 ) {
15974 if let Some(selections) = self
15975 .change_list
15976 .next_change(1, Direction::Prev)
15977 .map(|s| s.to_vec())
15978 {
15979 self.change_selections(Default::default(), window, cx, |s| {
15980 let map = s.display_map();
15981 s.select_display_ranges(selections.iter().map(|a| {
15982 let point = a.to_display_point(&map);
15983 point..point
15984 }))
15985 })
15986 }
15987 }
15988
15989 pub fn go_to_next_document_highlight(
15990 &mut self,
15991 _: &GoToNextDocumentHighlight,
15992 window: &mut Window,
15993 cx: &mut Context<Self>,
15994 ) {
15995 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
15996 }
15997
15998 pub fn go_to_prev_document_highlight(
15999 &mut self,
16000 _: &GoToPreviousDocumentHighlight,
16001 window: &mut Window,
16002 cx: &mut Context<Self>,
16003 ) {
16004 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16005 }
16006
16007 pub fn go_to_document_highlight_before_or_after_position(
16008 &mut self,
16009 direction: Direction,
16010 window: &mut Window,
16011 cx: &mut Context<Editor>,
16012 ) {
16013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16014 let snapshot = self.snapshot(window, cx);
16015 let buffer = &snapshot.buffer_snapshot;
16016 let position = self.selections.newest::<Point>(cx).head();
16017 let anchor_position = buffer.anchor_after(position);
16018
16019 // Get all document highlights (both read and write)
16020 let mut all_highlights = Vec::new();
16021
16022 if let Some((_, read_highlights)) = self
16023 .background_highlights
16024 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16025 {
16026 all_highlights.extend(read_highlights.iter());
16027 }
16028
16029 if let Some((_, write_highlights)) = self
16030 .background_highlights
16031 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16032 {
16033 all_highlights.extend(write_highlights.iter());
16034 }
16035
16036 if all_highlights.is_empty() {
16037 return;
16038 }
16039
16040 // Sort highlights by position
16041 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16042
16043 let target_highlight = match direction {
16044 Direction::Next => {
16045 // Find the first highlight after the current position
16046 all_highlights
16047 .iter()
16048 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16049 }
16050 Direction::Prev => {
16051 // Find the last highlight before the current position
16052 all_highlights
16053 .iter()
16054 .rev()
16055 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16056 }
16057 };
16058
16059 if let Some(highlight) = target_highlight {
16060 let destination = highlight.start.to_point(buffer);
16061 let autoscroll = Autoscroll::center();
16062
16063 self.unfold_ranges(&[destination..destination], false, false, cx);
16064 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16065 s.select_ranges([destination..destination]);
16066 });
16067 }
16068 }
16069
16070 fn go_to_line<T: 'static>(
16071 &mut self,
16072 position: Anchor,
16073 highlight_color: Option<Hsla>,
16074 window: &mut Window,
16075 cx: &mut Context<Self>,
16076 ) {
16077 let snapshot = self.snapshot(window, cx).display_snapshot;
16078 let position = position.to_point(&snapshot.buffer_snapshot);
16079 let start = snapshot
16080 .buffer_snapshot
16081 .clip_point(Point::new(position.row, 0), Bias::Left);
16082 let end = start + Point::new(1, 0);
16083 let start = snapshot.buffer_snapshot.anchor_before(start);
16084 let end = snapshot.buffer_snapshot.anchor_before(end);
16085
16086 self.highlight_rows::<T>(
16087 start..end,
16088 highlight_color
16089 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16090 Default::default(),
16091 cx,
16092 );
16093
16094 if self.buffer.read(cx).is_singleton() {
16095 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16096 }
16097 }
16098
16099 pub fn go_to_definition(
16100 &mut self,
16101 _: &GoToDefinition,
16102 window: &mut Window,
16103 cx: &mut Context<Self>,
16104 ) -> Task<Result<Navigated>> {
16105 let definition =
16106 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16107 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16108 cx.spawn_in(window, async move |editor, cx| {
16109 if definition.await? == Navigated::Yes {
16110 return Ok(Navigated::Yes);
16111 }
16112 match fallback_strategy {
16113 GoToDefinitionFallback::None => Ok(Navigated::No),
16114 GoToDefinitionFallback::FindAllReferences => {
16115 match editor.update_in(cx, |editor, window, cx| {
16116 editor.find_all_references(&FindAllReferences, window, cx)
16117 })? {
16118 Some(references) => references.await,
16119 None => Ok(Navigated::No),
16120 }
16121 }
16122 }
16123 })
16124 }
16125
16126 pub fn go_to_declaration(
16127 &mut self,
16128 _: &GoToDeclaration,
16129 window: &mut Window,
16130 cx: &mut Context<Self>,
16131 ) -> Task<Result<Navigated>> {
16132 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16133 }
16134
16135 pub fn go_to_declaration_split(
16136 &mut self,
16137 _: &GoToDeclaration,
16138 window: &mut Window,
16139 cx: &mut Context<Self>,
16140 ) -> Task<Result<Navigated>> {
16141 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16142 }
16143
16144 pub fn go_to_implementation(
16145 &mut self,
16146 _: &GoToImplementation,
16147 window: &mut Window,
16148 cx: &mut Context<Self>,
16149 ) -> Task<Result<Navigated>> {
16150 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16151 }
16152
16153 pub fn go_to_implementation_split(
16154 &mut self,
16155 _: &GoToImplementationSplit,
16156 window: &mut Window,
16157 cx: &mut Context<Self>,
16158 ) -> Task<Result<Navigated>> {
16159 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16160 }
16161
16162 pub fn go_to_type_definition(
16163 &mut self,
16164 _: &GoToTypeDefinition,
16165 window: &mut Window,
16166 cx: &mut Context<Self>,
16167 ) -> Task<Result<Navigated>> {
16168 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16169 }
16170
16171 pub fn go_to_definition_split(
16172 &mut self,
16173 _: &GoToDefinitionSplit,
16174 window: &mut Window,
16175 cx: &mut Context<Self>,
16176 ) -> Task<Result<Navigated>> {
16177 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16178 }
16179
16180 pub fn go_to_type_definition_split(
16181 &mut self,
16182 _: &GoToTypeDefinitionSplit,
16183 window: &mut Window,
16184 cx: &mut Context<Self>,
16185 ) -> Task<Result<Navigated>> {
16186 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16187 }
16188
16189 fn go_to_definition_of_kind(
16190 &mut self,
16191 kind: GotoDefinitionKind,
16192 split: bool,
16193 window: &mut Window,
16194 cx: &mut Context<Self>,
16195 ) -> Task<Result<Navigated>> {
16196 let Some(provider) = self.semantics_provider.clone() else {
16197 return Task::ready(Ok(Navigated::No));
16198 };
16199 let head = self.selections.newest::<usize>(cx).head();
16200 let buffer = self.buffer.read(cx);
16201 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16202 return Task::ready(Ok(Navigated::No));
16203 };
16204 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16205 return Task::ready(Ok(Navigated::No));
16206 };
16207
16208 cx.spawn_in(window, async move |editor, cx| {
16209 let Some(definitions) = definitions.await? else {
16210 return Ok(Navigated::No);
16211 };
16212 let navigated = editor
16213 .update_in(cx, |editor, window, cx| {
16214 editor.navigate_to_hover_links(
16215 Some(kind),
16216 definitions
16217 .into_iter()
16218 .filter(|location| {
16219 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16220 })
16221 .map(HoverLink::Text)
16222 .collect::<Vec<_>>(),
16223 split,
16224 window,
16225 cx,
16226 )
16227 })?
16228 .await?;
16229 anyhow::Ok(navigated)
16230 })
16231 }
16232
16233 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16234 let selection = self.selections.newest_anchor();
16235 let head = selection.head();
16236 let tail = selection.tail();
16237
16238 let Some((buffer, start_position)) =
16239 self.buffer.read(cx).text_anchor_for_position(head, cx)
16240 else {
16241 return;
16242 };
16243
16244 let end_position = if head != tail {
16245 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16246 return;
16247 };
16248 Some(pos)
16249 } else {
16250 None
16251 };
16252
16253 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16254 let url = if let Some(end_pos) = end_position {
16255 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16256 } else {
16257 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16258 };
16259
16260 if let Some(url) = url {
16261 editor.update(cx, |_, cx| {
16262 cx.open_url(&url);
16263 })
16264 } else {
16265 Ok(())
16266 }
16267 });
16268
16269 url_finder.detach();
16270 }
16271
16272 pub fn open_selected_filename(
16273 &mut self,
16274 _: &OpenSelectedFilename,
16275 window: &mut Window,
16276 cx: &mut Context<Self>,
16277 ) {
16278 let Some(workspace) = self.workspace() else {
16279 return;
16280 };
16281
16282 let position = self.selections.newest_anchor().head();
16283
16284 let Some((buffer, buffer_position)) =
16285 self.buffer.read(cx).text_anchor_for_position(position, cx)
16286 else {
16287 return;
16288 };
16289
16290 let project = self.project.clone();
16291
16292 cx.spawn_in(window, async move |_, cx| {
16293 let result = find_file(&buffer, project, buffer_position, cx).await;
16294
16295 if let Some((_, path)) = result {
16296 workspace
16297 .update_in(cx, |workspace, window, cx| {
16298 workspace.open_resolved_path(path, window, cx)
16299 })?
16300 .await?;
16301 }
16302 anyhow::Ok(())
16303 })
16304 .detach();
16305 }
16306
16307 pub(crate) fn navigate_to_hover_links(
16308 &mut self,
16309 kind: Option<GotoDefinitionKind>,
16310 definitions: Vec<HoverLink>,
16311 split: bool,
16312 window: &mut Window,
16313 cx: &mut Context<Editor>,
16314 ) -> Task<Result<Navigated>> {
16315 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16316 let mut first_url_or_file = None;
16317 let definitions: Vec<_> = definitions
16318 .into_iter()
16319 .filter_map(|def| match def {
16320 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16321 HoverLink::InlayHint(lsp_location, server_id) => {
16322 let computation =
16323 self.compute_target_location(lsp_location, server_id, window, cx);
16324 Some(cx.background_spawn(computation))
16325 }
16326 HoverLink::Url(url) => {
16327 first_url_or_file = Some(Either::Left(url));
16328 None
16329 }
16330 HoverLink::File(path) => {
16331 first_url_or_file = Some(Either::Right(path));
16332 None
16333 }
16334 })
16335 .collect();
16336
16337 let workspace = self.workspace();
16338
16339 cx.spawn_in(window, async move |editor, acx| {
16340 let mut locations: Vec<Location> = future::join_all(definitions)
16341 .await
16342 .into_iter()
16343 .filter_map(|location| location.transpose())
16344 .collect::<Result<_>>()
16345 .context("location tasks")?;
16346
16347 if locations.len() > 1 {
16348 let Some(workspace) = workspace else {
16349 return Ok(Navigated::No);
16350 };
16351
16352 let tab_kind = match kind {
16353 Some(GotoDefinitionKind::Implementation) => "Implementations",
16354 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16355 Some(GotoDefinitionKind::Declaration) => "Declarations",
16356 Some(GotoDefinitionKind::Type) => "Types",
16357 };
16358 let title = editor
16359 .update_in(acx, |_, _, cx| {
16360 let target = locations
16361 .iter()
16362 .map(|location| {
16363 location
16364 .buffer
16365 .read(cx)
16366 .text_for_range(location.range.clone())
16367 .collect::<String>()
16368 })
16369 .filter(|text| !text.contains('\n'))
16370 .unique()
16371 .take(3)
16372 .join(", ");
16373 if target.is_empty() {
16374 tab_kind.to_owned()
16375 } else {
16376 format!("{tab_kind} for {target}")
16377 }
16378 })
16379 .context("buffer title")?;
16380
16381 let opened = workspace
16382 .update_in(acx, |workspace, window, cx| {
16383 Self::open_locations_in_multibuffer(
16384 workspace,
16385 locations,
16386 title,
16387 split,
16388 MultibufferSelectionMode::First,
16389 window,
16390 cx,
16391 )
16392 })
16393 .is_ok();
16394
16395 anyhow::Ok(Navigated::from_bool(opened))
16396 } else if locations.is_empty() {
16397 // If there is one url or file, open it directly
16398 match first_url_or_file {
16399 Some(Either::Left(url)) => {
16400 acx.update(|_, cx| cx.open_url(&url))?;
16401 Ok(Navigated::Yes)
16402 }
16403 Some(Either::Right(path)) => {
16404 let Some(workspace) = workspace else {
16405 return Ok(Navigated::No);
16406 };
16407
16408 workspace
16409 .update_in(acx, |workspace, window, cx| {
16410 workspace.open_resolved_path(path, window, cx)
16411 })?
16412 .await?;
16413 Ok(Navigated::Yes)
16414 }
16415 None => Ok(Navigated::No),
16416 }
16417 } else {
16418 let Some(workspace) = workspace else {
16419 return Ok(Navigated::No);
16420 };
16421
16422 let target = locations.pop().unwrap();
16423 editor.update_in(acx, |editor, window, cx| {
16424 let range = target.range.to_point(target.buffer.read(cx));
16425 let range = editor.range_for_match(&range);
16426 let range = collapse_multiline_range(range);
16427
16428 if !split
16429 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16430 {
16431 editor.go_to_singleton_buffer_range(range, window, cx);
16432 } else {
16433 let pane = workspace.read(cx).active_pane().clone();
16434 window.defer(cx, move |window, cx| {
16435 let target_editor: Entity<Self> =
16436 workspace.update(cx, |workspace, cx| {
16437 let pane = if split {
16438 workspace.adjacent_pane(window, cx)
16439 } else {
16440 workspace.active_pane().clone()
16441 };
16442
16443 workspace.open_project_item(
16444 pane,
16445 target.buffer.clone(),
16446 true,
16447 true,
16448 window,
16449 cx,
16450 )
16451 });
16452 target_editor.update(cx, |target_editor, cx| {
16453 // When selecting a definition in a different buffer, disable the nav history
16454 // to avoid creating a history entry at the previous cursor location.
16455 pane.update(cx, |pane, _| pane.disable_history());
16456 target_editor.go_to_singleton_buffer_range(range, window, cx);
16457 pane.update(cx, |pane, _| pane.enable_history());
16458 });
16459 });
16460 }
16461 Navigated::Yes
16462 })
16463 }
16464 })
16465 }
16466
16467 fn compute_target_location(
16468 &self,
16469 lsp_location: lsp::Location,
16470 server_id: LanguageServerId,
16471 window: &mut Window,
16472 cx: &mut Context<Self>,
16473 ) -> Task<anyhow::Result<Option<Location>>> {
16474 let Some(project) = self.project.clone() else {
16475 return Task::ready(Ok(None));
16476 };
16477
16478 cx.spawn_in(window, async move |editor, cx| {
16479 let location_task = editor.update(cx, |_, cx| {
16480 project.update(cx, |project, cx| {
16481 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16482 })
16483 })?;
16484 let location = Some({
16485 let target_buffer_handle = location_task.await.context("open local buffer")?;
16486 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16487 let target_start = target_buffer
16488 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16489 let target_end = target_buffer
16490 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16491 target_buffer.anchor_after(target_start)
16492 ..target_buffer.anchor_before(target_end)
16493 })?;
16494 Location {
16495 buffer: target_buffer_handle,
16496 range,
16497 }
16498 });
16499 Ok(location)
16500 })
16501 }
16502
16503 pub fn find_all_references(
16504 &mut self,
16505 _: &FindAllReferences,
16506 window: &mut Window,
16507 cx: &mut Context<Self>,
16508 ) -> Option<Task<Result<Navigated>>> {
16509 let selection = self.selections.newest::<usize>(cx);
16510 let multi_buffer = self.buffer.read(cx);
16511 let head = selection.head();
16512
16513 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16514 let head_anchor = multi_buffer_snapshot.anchor_at(
16515 head,
16516 if head < selection.tail() {
16517 Bias::Right
16518 } else {
16519 Bias::Left
16520 },
16521 );
16522
16523 match self
16524 .find_all_references_task_sources
16525 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16526 {
16527 Ok(_) => {
16528 log::info!(
16529 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16530 );
16531 return None;
16532 }
16533 Err(i) => {
16534 self.find_all_references_task_sources.insert(i, head_anchor);
16535 }
16536 }
16537
16538 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16539 let workspace = self.workspace()?;
16540 let project = workspace.read(cx).project().clone();
16541 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16542 Some(cx.spawn_in(window, async move |editor, cx| {
16543 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16544 if let Ok(i) = editor
16545 .find_all_references_task_sources
16546 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16547 {
16548 editor.find_all_references_task_sources.remove(i);
16549 }
16550 });
16551
16552 let Some(locations) = references.await? else {
16553 return anyhow::Ok(Navigated::No);
16554 };
16555 if locations.is_empty() {
16556 return anyhow::Ok(Navigated::No);
16557 }
16558
16559 workspace.update_in(cx, |workspace, window, cx| {
16560 let target = locations
16561 .iter()
16562 .map(|location| {
16563 location
16564 .buffer
16565 .read(cx)
16566 .text_for_range(location.range.clone())
16567 .collect::<String>()
16568 })
16569 .filter(|text| !text.contains('\n'))
16570 .unique()
16571 .take(3)
16572 .join(", ");
16573 let title = if target.is_empty() {
16574 "References".to_owned()
16575 } else {
16576 format!("References to {target}")
16577 };
16578 Self::open_locations_in_multibuffer(
16579 workspace,
16580 locations,
16581 title,
16582 false,
16583 MultibufferSelectionMode::First,
16584 window,
16585 cx,
16586 );
16587 Navigated::Yes
16588 })
16589 }))
16590 }
16591
16592 /// Opens a multibuffer with the given project locations in it
16593 pub fn open_locations_in_multibuffer(
16594 workspace: &mut Workspace,
16595 mut locations: Vec<Location>,
16596 title: String,
16597 split: bool,
16598 multibuffer_selection_mode: MultibufferSelectionMode,
16599 window: &mut Window,
16600 cx: &mut Context<Workspace>,
16601 ) {
16602 if locations.is_empty() {
16603 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16604 return;
16605 }
16606
16607 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16608
16609 let mut locations = locations.into_iter().peekable();
16610 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16611 let capability = workspace.project().read(cx).capability();
16612
16613 // a key to find existing multibuffer editors with the same set of locations
16614 // to prevent us from opening more and more multibuffer tabs for searches and the like
16615 let mut key = (title.clone(), vec![]);
16616 let excerpt_buffer = cx.new(|cx| {
16617 let key = &mut key.1;
16618 let mut multibuffer = MultiBuffer::new(capability);
16619 while let Some(location) = locations.next() {
16620 let buffer = location.buffer.read(cx);
16621 let mut ranges_for_buffer = Vec::new();
16622 let range = location.range.to_point(buffer);
16623 ranges_for_buffer.push(range.clone());
16624
16625 while let Some(next_location) =
16626 locations.next_if(|next_location| next_location.buffer == location.buffer)
16627 {
16628 ranges_for_buffer.push(next_location.range.to_point(buffer));
16629 }
16630
16631 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16632 key.push((
16633 location.buffer.read(cx).remote_id(),
16634 ranges_for_buffer.clone(),
16635 ));
16636 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16637 PathKey::for_buffer(&location.buffer, cx),
16638 location.buffer.clone(),
16639 ranges_for_buffer,
16640 multibuffer_context_lines(cx),
16641 cx,
16642 );
16643 ranges.extend(new_ranges)
16644 }
16645
16646 multibuffer.with_title(title)
16647 });
16648 let existing = workspace.active_pane().update(cx, |pane, cx| {
16649 pane.items()
16650 .filter_map(|item| item.downcast::<Editor>())
16651 .find(|editor| {
16652 editor
16653 .read(cx)
16654 .lookup_key
16655 .as_ref()
16656 .and_then(|it| {
16657 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16658 })
16659 .is_some_and(|it| *it == key)
16660 })
16661 });
16662 let editor = existing.unwrap_or_else(|| {
16663 cx.new(|cx| {
16664 let mut editor = Editor::for_multibuffer(
16665 excerpt_buffer,
16666 Some(workspace.project().clone()),
16667 window,
16668 cx,
16669 );
16670 editor.lookup_key = Some(Box::new(key));
16671 editor
16672 })
16673 });
16674 editor.update(cx, |editor, cx| {
16675 match multibuffer_selection_mode {
16676 MultibufferSelectionMode::First => {
16677 if let Some(first_range) = ranges.first() {
16678 editor.change_selections(
16679 SelectionEffects::no_scroll(),
16680 window,
16681 cx,
16682 |selections| {
16683 selections.clear_disjoint();
16684 selections
16685 .select_anchor_ranges(std::iter::once(first_range.clone()));
16686 },
16687 );
16688 }
16689 editor.highlight_background::<Self>(
16690 &ranges,
16691 |theme| theme.colors().editor_highlighted_line_background,
16692 cx,
16693 );
16694 }
16695 MultibufferSelectionMode::All => {
16696 editor.change_selections(
16697 SelectionEffects::no_scroll(),
16698 window,
16699 cx,
16700 |selections| {
16701 selections.clear_disjoint();
16702 selections.select_anchor_ranges(ranges);
16703 },
16704 );
16705 }
16706 }
16707 editor.register_buffers_with_language_servers(cx);
16708 });
16709
16710 let item = Box::new(editor);
16711 let item_id = item.item_id();
16712
16713 if split {
16714 workspace.split_item(SplitDirection::Right, item, window, cx);
16715 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16716 let (preview_item_id, preview_item_idx) =
16717 workspace.active_pane().read_with(cx, |pane, _| {
16718 (pane.preview_item_id(), pane.preview_item_idx())
16719 });
16720
16721 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16722
16723 if let Some(preview_item_id) = preview_item_id {
16724 workspace.active_pane().update(cx, |pane, cx| {
16725 pane.remove_item(preview_item_id, false, false, window, cx);
16726 });
16727 }
16728 } else {
16729 workspace.add_item_to_active_pane(item, None, true, window, cx);
16730 }
16731 workspace.active_pane().update(cx, |pane, cx| {
16732 pane.set_preview_item_id(Some(item_id), cx);
16733 });
16734 }
16735
16736 pub fn rename(
16737 &mut self,
16738 _: &Rename,
16739 window: &mut Window,
16740 cx: &mut Context<Self>,
16741 ) -> Option<Task<Result<()>>> {
16742 use language::ToOffset as _;
16743
16744 let provider = self.semantics_provider.clone()?;
16745 let selection = self.selections.newest_anchor().clone();
16746 let (cursor_buffer, cursor_buffer_position) = self
16747 .buffer
16748 .read(cx)
16749 .text_anchor_for_position(selection.head(), cx)?;
16750 let (tail_buffer, cursor_buffer_position_end) = self
16751 .buffer
16752 .read(cx)
16753 .text_anchor_for_position(selection.tail(), cx)?;
16754 if tail_buffer != cursor_buffer {
16755 return None;
16756 }
16757
16758 let snapshot = cursor_buffer.read(cx).snapshot();
16759 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16760 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16761 let prepare_rename = provider
16762 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16763 .unwrap_or_else(|| Task::ready(Ok(None)));
16764 drop(snapshot);
16765
16766 Some(cx.spawn_in(window, async move |this, cx| {
16767 let rename_range = if let Some(range) = prepare_rename.await? {
16768 Some(range)
16769 } else {
16770 this.update(cx, |this, cx| {
16771 let buffer = this.buffer.read(cx).snapshot(cx);
16772 let mut buffer_highlights = this
16773 .document_highlights_for_position(selection.head(), &buffer)
16774 .filter(|highlight| {
16775 highlight.start.excerpt_id == selection.head().excerpt_id
16776 && highlight.end.excerpt_id == selection.head().excerpt_id
16777 });
16778 buffer_highlights
16779 .next()
16780 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16781 })?
16782 };
16783 if let Some(rename_range) = rename_range {
16784 this.update_in(cx, |this, window, cx| {
16785 let snapshot = cursor_buffer.read(cx).snapshot();
16786 let rename_buffer_range = rename_range.to_offset(&snapshot);
16787 let cursor_offset_in_rename_range =
16788 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16789 let cursor_offset_in_rename_range_end =
16790 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16791
16792 this.take_rename(false, window, cx);
16793 let buffer = this.buffer.read(cx).read(cx);
16794 let cursor_offset = selection.head().to_offset(&buffer);
16795 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16796 let rename_end = rename_start + rename_buffer_range.len();
16797 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16798 let mut old_highlight_id = None;
16799 let old_name: Arc<str> = buffer
16800 .chunks(rename_start..rename_end, true)
16801 .map(|chunk| {
16802 if old_highlight_id.is_none() {
16803 old_highlight_id = chunk.syntax_highlight_id;
16804 }
16805 chunk.text
16806 })
16807 .collect::<String>()
16808 .into();
16809
16810 drop(buffer);
16811
16812 // Position the selection in the rename editor so that it matches the current selection.
16813 this.show_local_selections = false;
16814 let rename_editor = cx.new(|cx| {
16815 let mut editor = Editor::single_line(window, cx);
16816 editor.buffer.update(cx, |buffer, cx| {
16817 buffer.edit([(0..0, old_name.clone())], None, cx)
16818 });
16819 let rename_selection_range = match cursor_offset_in_rename_range
16820 .cmp(&cursor_offset_in_rename_range_end)
16821 {
16822 Ordering::Equal => {
16823 editor.select_all(&SelectAll, window, cx);
16824 return editor;
16825 }
16826 Ordering::Less => {
16827 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16828 }
16829 Ordering::Greater => {
16830 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16831 }
16832 };
16833 if rename_selection_range.end > old_name.len() {
16834 editor.select_all(&SelectAll, window, cx);
16835 } else {
16836 editor.change_selections(Default::default(), window, cx, |s| {
16837 s.select_ranges([rename_selection_range]);
16838 });
16839 }
16840 editor
16841 });
16842 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16843 if e == &EditorEvent::Focused {
16844 cx.emit(EditorEvent::FocusedIn)
16845 }
16846 })
16847 .detach();
16848
16849 let write_highlights =
16850 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16851 let read_highlights =
16852 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16853 let ranges = write_highlights
16854 .iter()
16855 .flat_map(|(_, ranges)| ranges.iter())
16856 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16857 .cloned()
16858 .collect();
16859
16860 this.highlight_text::<Rename>(
16861 ranges,
16862 HighlightStyle {
16863 fade_out: Some(0.6),
16864 ..Default::default()
16865 },
16866 cx,
16867 );
16868 let rename_focus_handle = rename_editor.focus_handle(cx);
16869 window.focus(&rename_focus_handle);
16870 let block_id = this.insert_blocks(
16871 [BlockProperties {
16872 style: BlockStyle::Flex,
16873 placement: BlockPlacement::Below(range.start),
16874 height: Some(1),
16875 render: Arc::new({
16876 let rename_editor = rename_editor.clone();
16877 move |cx: &mut BlockContext| {
16878 let mut text_style = cx.editor_style.text.clone();
16879 if let Some(highlight_style) = old_highlight_id
16880 .and_then(|h| h.style(&cx.editor_style.syntax))
16881 {
16882 text_style = text_style.highlight(highlight_style);
16883 }
16884 div()
16885 .block_mouse_except_scroll()
16886 .pl(cx.anchor_x)
16887 .child(EditorElement::new(
16888 &rename_editor,
16889 EditorStyle {
16890 background: cx.theme().system().transparent,
16891 local_player: cx.editor_style.local_player,
16892 text: text_style,
16893 scrollbar_width: cx.editor_style.scrollbar_width,
16894 syntax: cx.editor_style.syntax.clone(),
16895 status: cx.editor_style.status.clone(),
16896 inlay_hints_style: HighlightStyle {
16897 font_weight: Some(FontWeight::BOLD),
16898 ..make_inlay_hints_style(cx.app)
16899 },
16900 edit_prediction_styles: make_suggestion_styles(
16901 cx.app,
16902 ),
16903 ..EditorStyle::default()
16904 },
16905 ))
16906 .into_any_element()
16907 }
16908 }),
16909 priority: 0,
16910 }],
16911 Some(Autoscroll::fit()),
16912 cx,
16913 )[0];
16914 this.pending_rename = Some(RenameState {
16915 range,
16916 old_name,
16917 editor: rename_editor,
16918 block_id,
16919 });
16920 })?;
16921 }
16922
16923 Ok(())
16924 }))
16925 }
16926
16927 pub fn confirm_rename(
16928 &mut self,
16929 _: &ConfirmRename,
16930 window: &mut Window,
16931 cx: &mut Context<Self>,
16932 ) -> Option<Task<Result<()>>> {
16933 let rename = self.take_rename(false, window, cx)?;
16934 let workspace = self.workspace()?.downgrade();
16935 let (buffer, start) = self
16936 .buffer
16937 .read(cx)
16938 .text_anchor_for_position(rename.range.start, cx)?;
16939 let (end_buffer, _) = self
16940 .buffer
16941 .read(cx)
16942 .text_anchor_for_position(rename.range.end, cx)?;
16943 if buffer != end_buffer {
16944 return None;
16945 }
16946
16947 let old_name = rename.old_name;
16948 let new_name = rename.editor.read(cx).text(cx);
16949
16950 let rename = self.semantics_provider.as_ref()?.perform_rename(
16951 &buffer,
16952 start,
16953 new_name.clone(),
16954 cx,
16955 )?;
16956
16957 Some(cx.spawn_in(window, async move |editor, cx| {
16958 let project_transaction = rename.await?;
16959 Self::open_project_transaction(
16960 &editor,
16961 workspace,
16962 project_transaction,
16963 format!("Rename: {} → {}", old_name, new_name),
16964 cx,
16965 )
16966 .await?;
16967
16968 editor.update(cx, |editor, cx| {
16969 editor.refresh_document_highlights(cx);
16970 })?;
16971 Ok(())
16972 }))
16973 }
16974
16975 fn take_rename(
16976 &mut self,
16977 moving_cursor: bool,
16978 window: &mut Window,
16979 cx: &mut Context<Self>,
16980 ) -> Option<RenameState> {
16981 let rename = self.pending_rename.take()?;
16982 if rename.editor.focus_handle(cx).is_focused(window) {
16983 window.focus(&self.focus_handle);
16984 }
16985
16986 self.remove_blocks(
16987 [rename.block_id].into_iter().collect(),
16988 Some(Autoscroll::fit()),
16989 cx,
16990 );
16991 self.clear_highlights::<Rename>(cx);
16992 self.show_local_selections = true;
16993
16994 if moving_cursor {
16995 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16996 editor.selections.newest::<usize>(cx).head()
16997 });
16998
16999 // Update the selection to match the position of the selection inside
17000 // the rename editor.
17001 let snapshot = self.buffer.read(cx).read(cx);
17002 let rename_range = rename.range.to_offset(&snapshot);
17003 let cursor_in_editor = snapshot
17004 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17005 .min(rename_range.end);
17006 drop(snapshot);
17007
17008 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17009 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17010 });
17011 } else {
17012 self.refresh_document_highlights(cx);
17013 }
17014
17015 Some(rename)
17016 }
17017
17018 pub fn pending_rename(&self) -> Option<&RenameState> {
17019 self.pending_rename.as_ref()
17020 }
17021
17022 fn format(
17023 &mut self,
17024 _: &Format,
17025 window: &mut Window,
17026 cx: &mut Context<Self>,
17027 ) -> Option<Task<Result<()>>> {
17028 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17029
17030 let project = match &self.project {
17031 Some(project) => project.clone(),
17032 None => return None,
17033 };
17034
17035 Some(self.perform_format(
17036 project,
17037 FormatTrigger::Manual,
17038 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17039 window,
17040 cx,
17041 ))
17042 }
17043
17044 fn format_selections(
17045 &mut self,
17046 _: &FormatSelections,
17047 window: &mut Window,
17048 cx: &mut Context<Self>,
17049 ) -> Option<Task<Result<()>>> {
17050 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17051
17052 let project = match &self.project {
17053 Some(project) => project.clone(),
17054 None => return None,
17055 };
17056
17057 let ranges = self
17058 .selections
17059 .all_adjusted(cx)
17060 .into_iter()
17061 .map(|selection| selection.range())
17062 .collect_vec();
17063
17064 Some(self.perform_format(
17065 project,
17066 FormatTrigger::Manual,
17067 FormatTarget::Ranges(ranges),
17068 window,
17069 cx,
17070 ))
17071 }
17072
17073 fn perform_format(
17074 &mut self,
17075 project: Entity<Project>,
17076 trigger: FormatTrigger,
17077 target: FormatTarget,
17078 window: &mut Window,
17079 cx: &mut Context<Self>,
17080 ) -> Task<Result<()>> {
17081 let buffer = self.buffer.clone();
17082 let (buffers, target) = match target {
17083 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17084 FormatTarget::Ranges(selection_ranges) => {
17085 let multi_buffer = buffer.read(cx);
17086 let snapshot = multi_buffer.read(cx);
17087 let mut buffers = HashSet::default();
17088 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17089 BTreeMap::new();
17090 for selection_range in selection_ranges {
17091 for (buffer, buffer_range, _) in
17092 snapshot.range_to_buffer_ranges(selection_range)
17093 {
17094 let buffer_id = buffer.remote_id();
17095 let start = buffer.anchor_before(buffer_range.start);
17096 let end = buffer.anchor_after(buffer_range.end);
17097 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17098 buffer_id_to_ranges
17099 .entry(buffer_id)
17100 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17101 .or_insert_with(|| vec![start..end]);
17102 }
17103 }
17104 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17105 }
17106 };
17107
17108 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17109 let selections_prev = transaction_id_prev
17110 .and_then(|transaction_id_prev| {
17111 // default to selections as they were after the last edit, if we have them,
17112 // instead of how they are now.
17113 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17114 // will take you back to where you made the last edit, instead of staying where you scrolled
17115 self.selection_history
17116 .transaction(transaction_id_prev)
17117 .map(|t| t.0.clone())
17118 })
17119 .unwrap_or_else(|| self.selections.disjoint_anchors());
17120
17121 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17122 let format = project.update(cx, |project, cx| {
17123 project.format(buffers, target, true, trigger, cx)
17124 });
17125
17126 cx.spawn_in(window, async move |editor, cx| {
17127 let transaction = futures::select_biased! {
17128 transaction = format.log_err().fuse() => transaction,
17129 () = timeout => {
17130 log::warn!("timed out waiting for formatting");
17131 None
17132 }
17133 };
17134
17135 buffer
17136 .update(cx, |buffer, cx| {
17137 if let Some(transaction) = transaction
17138 && !buffer.is_singleton()
17139 {
17140 buffer.push_transaction(&transaction.0, cx);
17141 }
17142 cx.notify();
17143 })
17144 .ok();
17145
17146 if let Some(transaction_id_now) =
17147 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17148 {
17149 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17150 if has_new_transaction {
17151 _ = editor.update(cx, |editor, _| {
17152 editor
17153 .selection_history
17154 .insert_transaction(transaction_id_now, selections_prev);
17155 });
17156 }
17157 }
17158
17159 Ok(())
17160 })
17161 }
17162
17163 fn organize_imports(
17164 &mut self,
17165 _: &OrganizeImports,
17166 window: &mut Window,
17167 cx: &mut Context<Self>,
17168 ) -> Option<Task<Result<()>>> {
17169 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17170 let project = match &self.project {
17171 Some(project) => project.clone(),
17172 None => return None,
17173 };
17174 Some(self.perform_code_action_kind(
17175 project,
17176 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17177 window,
17178 cx,
17179 ))
17180 }
17181
17182 fn perform_code_action_kind(
17183 &mut self,
17184 project: Entity<Project>,
17185 kind: CodeActionKind,
17186 window: &mut Window,
17187 cx: &mut Context<Self>,
17188 ) -> Task<Result<()>> {
17189 let buffer = self.buffer.clone();
17190 let buffers = buffer.read(cx).all_buffers();
17191 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17192 let apply_action = project.update(cx, |project, cx| {
17193 project.apply_code_action_kind(buffers, kind, true, cx)
17194 });
17195 cx.spawn_in(window, async move |_, cx| {
17196 let transaction = futures::select_biased! {
17197 () = timeout => {
17198 log::warn!("timed out waiting for executing code action");
17199 None
17200 }
17201 transaction = apply_action.log_err().fuse() => transaction,
17202 };
17203 buffer
17204 .update(cx, |buffer, cx| {
17205 // check if we need this
17206 if let Some(transaction) = transaction
17207 && !buffer.is_singleton()
17208 {
17209 buffer.push_transaction(&transaction.0, cx);
17210 }
17211 cx.notify();
17212 })
17213 .ok();
17214 Ok(())
17215 })
17216 }
17217
17218 pub fn restart_language_server(
17219 &mut self,
17220 _: &RestartLanguageServer,
17221 _: &mut Window,
17222 cx: &mut Context<Self>,
17223 ) {
17224 if let Some(project) = self.project.clone() {
17225 self.buffer.update(cx, |multi_buffer, cx| {
17226 project.update(cx, |project, cx| {
17227 project.restart_language_servers_for_buffers(
17228 multi_buffer.all_buffers().into_iter().collect(),
17229 HashSet::default(),
17230 cx,
17231 );
17232 });
17233 })
17234 }
17235 }
17236
17237 pub fn stop_language_server(
17238 &mut self,
17239 _: &StopLanguageServer,
17240 _: &mut Window,
17241 cx: &mut Context<Self>,
17242 ) {
17243 if let Some(project) = self.project.clone() {
17244 self.buffer.update(cx, |multi_buffer, cx| {
17245 project.update(cx, |project, cx| {
17246 project.stop_language_servers_for_buffers(
17247 multi_buffer.all_buffers().into_iter().collect(),
17248 HashSet::default(),
17249 cx,
17250 );
17251 cx.emit(project::Event::RefreshInlayHints);
17252 });
17253 });
17254 }
17255 }
17256
17257 fn cancel_language_server_work(
17258 workspace: &mut Workspace,
17259 _: &actions::CancelLanguageServerWork,
17260 _: &mut Window,
17261 cx: &mut Context<Workspace>,
17262 ) {
17263 let project = workspace.project();
17264 let buffers = workspace
17265 .active_item(cx)
17266 .and_then(|item| item.act_as::<Editor>(cx))
17267 .map_or(HashSet::default(), |editor| {
17268 editor.read(cx).buffer.read(cx).all_buffers()
17269 });
17270 project.update(cx, |project, cx| {
17271 project.cancel_language_server_work_for_buffers(buffers, cx);
17272 });
17273 }
17274
17275 fn show_character_palette(
17276 &mut self,
17277 _: &ShowCharacterPalette,
17278 window: &mut Window,
17279 _: &mut Context<Self>,
17280 ) {
17281 window.show_character_palette();
17282 }
17283
17284 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17285 if !self.diagnostics_enabled() {
17286 return;
17287 }
17288
17289 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17290 let buffer = self.buffer.read(cx).snapshot(cx);
17291 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17292 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17293 let is_valid = buffer
17294 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17295 .any(|entry| {
17296 entry.diagnostic.is_primary
17297 && !entry.range.is_empty()
17298 && entry.range.start == primary_range_start
17299 && entry.diagnostic.message == active_diagnostics.active_message
17300 });
17301
17302 if !is_valid {
17303 self.dismiss_diagnostics(cx);
17304 }
17305 }
17306 }
17307
17308 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17309 match &self.active_diagnostics {
17310 ActiveDiagnostic::Group(group) => Some(group),
17311 _ => None,
17312 }
17313 }
17314
17315 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17316 if !self.diagnostics_enabled() {
17317 return;
17318 }
17319 self.dismiss_diagnostics(cx);
17320 self.active_diagnostics = ActiveDiagnostic::All;
17321 }
17322
17323 fn activate_diagnostics(
17324 &mut self,
17325 buffer_id: BufferId,
17326 diagnostic: DiagnosticEntry<usize>,
17327 window: &mut Window,
17328 cx: &mut Context<Self>,
17329 ) {
17330 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17331 return;
17332 }
17333 self.dismiss_diagnostics(cx);
17334 let snapshot = self.snapshot(window, cx);
17335 let buffer = self.buffer.read(cx).snapshot(cx);
17336 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17337 return;
17338 };
17339
17340 let diagnostic_group = buffer
17341 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17342 .collect::<Vec<_>>();
17343
17344 let blocks =
17345 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17346
17347 let blocks = self.display_map.update(cx, |display_map, cx| {
17348 display_map.insert_blocks(blocks, cx).into_iter().collect()
17349 });
17350 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17351 active_range: buffer.anchor_before(diagnostic.range.start)
17352 ..buffer.anchor_after(diagnostic.range.end),
17353 active_message: diagnostic.diagnostic.message.clone(),
17354 group_id: diagnostic.diagnostic.group_id,
17355 blocks,
17356 });
17357 cx.notify();
17358 }
17359
17360 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17361 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17362 return;
17363 };
17364
17365 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17366 if let ActiveDiagnostic::Group(group) = prev {
17367 self.display_map.update(cx, |display_map, cx| {
17368 display_map.remove_blocks(group.blocks, cx);
17369 });
17370 cx.notify();
17371 }
17372 }
17373
17374 /// Disable inline diagnostics rendering for this editor.
17375 pub fn disable_inline_diagnostics(&mut self) {
17376 self.inline_diagnostics_enabled = false;
17377 self.inline_diagnostics_update = Task::ready(());
17378 self.inline_diagnostics.clear();
17379 }
17380
17381 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17382 self.diagnostics_enabled = false;
17383 self.dismiss_diagnostics(cx);
17384 self.inline_diagnostics_update = Task::ready(());
17385 self.inline_diagnostics.clear();
17386 }
17387
17388 pub fn disable_word_completions(&mut self) {
17389 self.word_completions_enabled = false;
17390 }
17391
17392 pub fn diagnostics_enabled(&self) -> bool {
17393 self.diagnostics_enabled && self.mode.is_full()
17394 }
17395
17396 pub fn inline_diagnostics_enabled(&self) -> bool {
17397 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17398 }
17399
17400 pub fn show_inline_diagnostics(&self) -> bool {
17401 self.show_inline_diagnostics
17402 }
17403
17404 pub fn toggle_inline_diagnostics(
17405 &mut self,
17406 _: &ToggleInlineDiagnostics,
17407 window: &mut Window,
17408 cx: &mut Context<Editor>,
17409 ) {
17410 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17411 self.refresh_inline_diagnostics(false, window, cx);
17412 }
17413
17414 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17415 self.diagnostics_max_severity = severity;
17416 self.display_map.update(cx, |display_map, _| {
17417 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17418 });
17419 }
17420
17421 pub fn toggle_diagnostics(
17422 &mut self,
17423 _: &ToggleDiagnostics,
17424 window: &mut Window,
17425 cx: &mut Context<Editor>,
17426 ) {
17427 if !self.diagnostics_enabled() {
17428 return;
17429 }
17430
17431 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17432 EditorSettings::get_global(cx)
17433 .diagnostics_max_severity
17434 .filter(|severity| severity != &DiagnosticSeverity::Off)
17435 .unwrap_or(DiagnosticSeverity::Hint)
17436 } else {
17437 DiagnosticSeverity::Off
17438 };
17439 self.set_max_diagnostics_severity(new_severity, cx);
17440 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17441 self.active_diagnostics = ActiveDiagnostic::None;
17442 self.inline_diagnostics_update = Task::ready(());
17443 self.inline_diagnostics.clear();
17444 } else {
17445 self.refresh_inline_diagnostics(false, window, cx);
17446 }
17447
17448 cx.notify();
17449 }
17450
17451 pub fn toggle_minimap(
17452 &mut self,
17453 _: &ToggleMinimap,
17454 window: &mut Window,
17455 cx: &mut Context<Editor>,
17456 ) {
17457 if self.supports_minimap(cx) {
17458 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17459 }
17460 }
17461
17462 fn refresh_inline_diagnostics(
17463 &mut self,
17464 debounce: bool,
17465 window: &mut Window,
17466 cx: &mut Context<Self>,
17467 ) {
17468 let max_severity = ProjectSettings::get_global(cx)
17469 .diagnostics
17470 .inline
17471 .max_severity
17472 .unwrap_or(self.diagnostics_max_severity);
17473
17474 if !self.inline_diagnostics_enabled()
17475 || !self.show_inline_diagnostics
17476 || max_severity == DiagnosticSeverity::Off
17477 {
17478 self.inline_diagnostics_update = Task::ready(());
17479 self.inline_diagnostics.clear();
17480 return;
17481 }
17482
17483 let debounce_ms = ProjectSettings::get_global(cx)
17484 .diagnostics
17485 .inline
17486 .update_debounce_ms;
17487 let debounce = if debounce && debounce_ms > 0 {
17488 Some(Duration::from_millis(debounce_ms))
17489 } else {
17490 None
17491 };
17492 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17493 if let Some(debounce) = debounce {
17494 cx.background_executor().timer(debounce).await;
17495 }
17496 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17497 editor
17498 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17499 .ok()
17500 }) else {
17501 return;
17502 };
17503
17504 let new_inline_diagnostics = cx
17505 .background_spawn(async move {
17506 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17507 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17508 let message = diagnostic_entry
17509 .diagnostic
17510 .message
17511 .split_once('\n')
17512 .map(|(line, _)| line)
17513 .map(SharedString::new)
17514 .unwrap_or_else(|| {
17515 SharedString::from(diagnostic_entry.diagnostic.message)
17516 });
17517 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17518 let (Ok(i) | Err(i)) = inline_diagnostics
17519 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17520 inline_diagnostics.insert(
17521 i,
17522 (
17523 start_anchor,
17524 InlineDiagnostic {
17525 message,
17526 group_id: diagnostic_entry.diagnostic.group_id,
17527 start: diagnostic_entry.range.start.to_point(&snapshot),
17528 is_primary: diagnostic_entry.diagnostic.is_primary,
17529 severity: diagnostic_entry.diagnostic.severity,
17530 },
17531 ),
17532 );
17533 }
17534 inline_diagnostics
17535 })
17536 .await;
17537
17538 editor
17539 .update(cx, |editor, cx| {
17540 editor.inline_diagnostics = new_inline_diagnostics;
17541 cx.notify();
17542 })
17543 .ok();
17544 });
17545 }
17546
17547 fn pull_diagnostics(
17548 &mut self,
17549 buffer_id: Option<BufferId>,
17550 window: &Window,
17551 cx: &mut Context<Self>,
17552 ) -> Option<()> {
17553 if !self.mode().is_full() {
17554 return None;
17555 }
17556 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17557 .diagnostics
17558 .lsp_pull_diagnostics;
17559 if !pull_diagnostics_settings.enabled {
17560 return None;
17561 }
17562 let project = self.project()?.downgrade();
17563 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17564 let mut buffers = self.buffer.read(cx).all_buffers();
17565 if let Some(buffer_id) = buffer_id {
17566 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17567 }
17568
17569 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17570 cx.background_executor().timer(debounce).await;
17571
17572 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17573 buffers
17574 .into_iter()
17575 .filter_map(|buffer| {
17576 project
17577 .update(cx, |project, cx| {
17578 project.lsp_store().update(cx, |lsp_store, cx| {
17579 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17580 })
17581 })
17582 .ok()
17583 })
17584 .collect::<FuturesUnordered<_>>()
17585 }) else {
17586 return;
17587 };
17588
17589 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17590 match pull_task {
17591 Ok(()) => {
17592 if editor
17593 .update_in(cx, |editor, window, cx| {
17594 editor.update_diagnostics_state(window, cx);
17595 })
17596 .is_err()
17597 {
17598 return;
17599 }
17600 }
17601 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17602 }
17603 }
17604 });
17605
17606 Some(())
17607 }
17608
17609 pub fn set_selections_from_remote(
17610 &mut self,
17611 selections: Vec<Selection<Anchor>>,
17612 pending_selection: Option<Selection<Anchor>>,
17613 window: &mut Window,
17614 cx: &mut Context<Self>,
17615 ) {
17616 let old_cursor_position = self.selections.newest_anchor().head();
17617 self.selections.change_with(cx, |s| {
17618 s.select_anchors(selections);
17619 if let Some(pending_selection) = pending_selection {
17620 s.set_pending(pending_selection, SelectMode::Character);
17621 } else {
17622 s.clear_pending();
17623 }
17624 });
17625 self.selections_did_change(
17626 false,
17627 &old_cursor_position,
17628 SelectionEffects::default(),
17629 window,
17630 cx,
17631 );
17632 }
17633
17634 pub fn transact(
17635 &mut self,
17636 window: &mut Window,
17637 cx: &mut Context<Self>,
17638 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17639 ) -> Option<TransactionId> {
17640 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17641 this.start_transaction_at(Instant::now(), window, cx);
17642 update(this, window, cx);
17643 this.end_transaction_at(Instant::now(), cx)
17644 })
17645 }
17646
17647 pub fn start_transaction_at(
17648 &mut self,
17649 now: Instant,
17650 window: &mut Window,
17651 cx: &mut Context<Self>,
17652 ) -> Option<TransactionId> {
17653 self.end_selection(window, cx);
17654 if let Some(tx_id) = self
17655 .buffer
17656 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17657 {
17658 self.selection_history
17659 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17660 cx.emit(EditorEvent::TransactionBegun {
17661 transaction_id: tx_id,
17662 });
17663 Some(tx_id)
17664 } else {
17665 None
17666 }
17667 }
17668
17669 pub fn end_transaction_at(
17670 &mut self,
17671 now: Instant,
17672 cx: &mut Context<Self>,
17673 ) -> Option<TransactionId> {
17674 if let Some(transaction_id) = self
17675 .buffer
17676 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17677 {
17678 if let Some((_, end_selections)) =
17679 self.selection_history.transaction_mut(transaction_id)
17680 {
17681 *end_selections = Some(self.selections.disjoint_anchors());
17682 } else {
17683 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17684 }
17685
17686 cx.emit(EditorEvent::Edited { transaction_id });
17687 Some(transaction_id)
17688 } else {
17689 None
17690 }
17691 }
17692
17693 pub fn modify_transaction_selection_history(
17694 &mut self,
17695 transaction_id: TransactionId,
17696 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17697 ) -> bool {
17698 self.selection_history
17699 .transaction_mut(transaction_id)
17700 .map(modify)
17701 .is_some()
17702 }
17703
17704 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17705 if self.selection_mark_mode {
17706 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17707 s.move_with(|_, sel| {
17708 sel.collapse_to(sel.head(), SelectionGoal::None);
17709 });
17710 })
17711 }
17712 self.selection_mark_mode = true;
17713 cx.notify();
17714 }
17715
17716 pub fn swap_selection_ends(
17717 &mut self,
17718 _: &actions::SwapSelectionEnds,
17719 window: &mut Window,
17720 cx: &mut Context<Self>,
17721 ) {
17722 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17723 s.move_with(|_, sel| {
17724 if sel.start != sel.end {
17725 sel.reversed = !sel.reversed
17726 }
17727 });
17728 });
17729 self.request_autoscroll(Autoscroll::newest(), cx);
17730 cx.notify();
17731 }
17732
17733 pub fn toggle_focus(
17734 workspace: &mut Workspace,
17735 _: &actions::ToggleFocus,
17736 window: &mut Window,
17737 cx: &mut Context<Workspace>,
17738 ) {
17739 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17740 return;
17741 };
17742 workspace.activate_item(&item, true, true, window, cx);
17743 }
17744
17745 pub fn toggle_fold(
17746 &mut self,
17747 _: &actions::ToggleFold,
17748 window: &mut Window,
17749 cx: &mut Context<Self>,
17750 ) {
17751 if self.is_singleton(cx) {
17752 let selection = self.selections.newest::<Point>(cx);
17753
17754 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17755 let range = if selection.is_empty() {
17756 let point = selection.head().to_display_point(&display_map);
17757 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17758 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17759 .to_point(&display_map);
17760 start..end
17761 } else {
17762 selection.range()
17763 };
17764 if display_map.folds_in_range(range).next().is_some() {
17765 self.unfold_lines(&Default::default(), window, cx)
17766 } else {
17767 self.fold(&Default::default(), window, cx)
17768 }
17769 } else {
17770 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17771 let buffer_ids: HashSet<_> = self
17772 .selections
17773 .disjoint_anchor_ranges()
17774 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17775 .collect();
17776
17777 let should_unfold = buffer_ids
17778 .iter()
17779 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17780
17781 for buffer_id in buffer_ids {
17782 if should_unfold {
17783 self.unfold_buffer(buffer_id, cx);
17784 } else {
17785 self.fold_buffer(buffer_id, cx);
17786 }
17787 }
17788 }
17789 }
17790
17791 pub fn toggle_fold_recursive(
17792 &mut self,
17793 _: &actions::ToggleFoldRecursive,
17794 window: &mut Window,
17795 cx: &mut Context<Self>,
17796 ) {
17797 let selection = self.selections.newest::<Point>(cx);
17798
17799 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17800 let range = if selection.is_empty() {
17801 let point = selection.head().to_display_point(&display_map);
17802 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17803 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17804 .to_point(&display_map);
17805 start..end
17806 } else {
17807 selection.range()
17808 };
17809 if display_map.folds_in_range(range).next().is_some() {
17810 self.unfold_recursive(&Default::default(), window, cx)
17811 } else {
17812 self.fold_recursive(&Default::default(), window, cx)
17813 }
17814 }
17815
17816 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17817 if self.is_singleton(cx) {
17818 let mut to_fold = Vec::new();
17819 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17820 let selections = self.selections.all_adjusted(cx);
17821
17822 for selection in selections {
17823 let range = selection.range().sorted();
17824 let buffer_start_row = range.start.row;
17825
17826 if range.start.row != range.end.row {
17827 let mut found = false;
17828 let mut row = range.start.row;
17829 while row <= range.end.row {
17830 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17831 {
17832 found = true;
17833 row = crease.range().end.row + 1;
17834 to_fold.push(crease);
17835 } else {
17836 row += 1
17837 }
17838 }
17839 if found {
17840 continue;
17841 }
17842 }
17843
17844 for row in (0..=range.start.row).rev() {
17845 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17846 && crease.range().end.row >= buffer_start_row
17847 {
17848 to_fold.push(crease);
17849 if row <= range.start.row {
17850 break;
17851 }
17852 }
17853 }
17854 }
17855
17856 self.fold_creases(to_fold, true, window, cx);
17857 } else {
17858 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17859 let buffer_ids = self
17860 .selections
17861 .disjoint_anchor_ranges()
17862 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17863 .collect::<HashSet<_>>();
17864 for buffer_id in buffer_ids {
17865 self.fold_buffer(buffer_id, cx);
17866 }
17867 }
17868 }
17869
17870 pub fn toggle_fold_all(
17871 &mut self,
17872 _: &actions::ToggleFoldAll,
17873 window: &mut Window,
17874 cx: &mut Context<Self>,
17875 ) {
17876 if self.buffer.read(cx).is_singleton() {
17877 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17878 let has_folds = display_map
17879 .folds_in_range(0..display_map.buffer_snapshot.len())
17880 .next()
17881 .is_some();
17882
17883 if has_folds {
17884 self.unfold_all(&actions::UnfoldAll, window, cx);
17885 } else {
17886 self.fold_all(&actions::FoldAll, window, cx);
17887 }
17888 } else {
17889 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17890 let should_unfold = buffer_ids
17891 .iter()
17892 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17893
17894 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17895 editor
17896 .update_in(cx, |editor, _, cx| {
17897 for buffer_id in buffer_ids {
17898 if should_unfold {
17899 editor.unfold_buffer(buffer_id, cx);
17900 } else {
17901 editor.fold_buffer(buffer_id, cx);
17902 }
17903 }
17904 })
17905 .ok();
17906 });
17907 }
17908 }
17909
17910 fn fold_at_level(
17911 &mut self,
17912 fold_at: &FoldAtLevel,
17913 window: &mut Window,
17914 cx: &mut Context<Self>,
17915 ) {
17916 if !self.buffer.read(cx).is_singleton() {
17917 return;
17918 }
17919
17920 let fold_at_level = fold_at.0;
17921 let snapshot = self.buffer.read(cx).snapshot(cx);
17922 let mut to_fold = Vec::new();
17923 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17924
17925 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17926 while start_row < end_row {
17927 match self
17928 .snapshot(window, cx)
17929 .crease_for_buffer_row(MultiBufferRow(start_row))
17930 {
17931 Some(crease) => {
17932 let nested_start_row = crease.range().start.row + 1;
17933 let nested_end_row = crease.range().end.row;
17934
17935 if current_level < fold_at_level {
17936 stack.push((nested_start_row, nested_end_row, current_level + 1));
17937 } else if current_level == fold_at_level {
17938 to_fold.push(crease);
17939 }
17940
17941 start_row = nested_end_row + 1;
17942 }
17943 None => start_row += 1,
17944 }
17945 }
17946 }
17947
17948 self.fold_creases(to_fold, true, window, cx);
17949 }
17950
17951 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17952 if self.buffer.read(cx).is_singleton() {
17953 let mut fold_ranges = Vec::new();
17954 let snapshot = self.buffer.read(cx).snapshot(cx);
17955
17956 for row in 0..snapshot.max_row().0 {
17957 if let Some(foldable_range) = self
17958 .snapshot(window, cx)
17959 .crease_for_buffer_row(MultiBufferRow(row))
17960 {
17961 fold_ranges.push(foldable_range);
17962 }
17963 }
17964
17965 self.fold_creases(fold_ranges, true, window, cx);
17966 } else {
17967 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17968 editor
17969 .update_in(cx, |editor, _, cx| {
17970 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17971 editor.fold_buffer(buffer_id, cx);
17972 }
17973 })
17974 .ok();
17975 });
17976 }
17977 }
17978
17979 pub fn fold_function_bodies(
17980 &mut self,
17981 _: &actions::FoldFunctionBodies,
17982 window: &mut Window,
17983 cx: &mut Context<Self>,
17984 ) {
17985 let snapshot = self.buffer.read(cx).snapshot(cx);
17986
17987 let ranges = snapshot
17988 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17989 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17990 .collect::<Vec<_>>();
17991
17992 let creases = ranges
17993 .into_iter()
17994 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17995 .collect();
17996
17997 self.fold_creases(creases, true, window, cx);
17998 }
17999
18000 pub fn fold_recursive(
18001 &mut self,
18002 _: &actions::FoldRecursive,
18003 window: &mut Window,
18004 cx: &mut Context<Self>,
18005 ) {
18006 let mut to_fold = Vec::new();
18007 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18008 let selections = self.selections.all_adjusted(cx);
18009
18010 for selection in selections {
18011 let range = selection.range().sorted();
18012 let buffer_start_row = range.start.row;
18013
18014 if range.start.row != range.end.row {
18015 let mut found = false;
18016 for row in range.start.row..=range.end.row {
18017 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18018 found = true;
18019 to_fold.push(crease);
18020 }
18021 }
18022 if found {
18023 continue;
18024 }
18025 }
18026
18027 for row in (0..=range.start.row).rev() {
18028 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18029 if crease.range().end.row >= buffer_start_row {
18030 to_fold.push(crease);
18031 } else {
18032 break;
18033 }
18034 }
18035 }
18036 }
18037
18038 self.fold_creases(to_fold, true, window, cx);
18039 }
18040
18041 pub fn fold_at(
18042 &mut self,
18043 buffer_row: MultiBufferRow,
18044 window: &mut Window,
18045 cx: &mut Context<Self>,
18046 ) {
18047 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18048
18049 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18050 let autoscroll = self
18051 .selections
18052 .all::<Point>(cx)
18053 .iter()
18054 .any(|selection| crease.range().overlaps(&selection.range()));
18055
18056 self.fold_creases(vec![crease], autoscroll, window, cx);
18057 }
18058 }
18059
18060 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18061 if self.is_singleton(cx) {
18062 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18063 let buffer = &display_map.buffer_snapshot;
18064 let selections = self.selections.all::<Point>(cx);
18065 let ranges = selections
18066 .iter()
18067 .map(|s| {
18068 let range = s.display_range(&display_map).sorted();
18069 let mut start = range.start.to_point(&display_map);
18070 let mut end = range.end.to_point(&display_map);
18071 start.column = 0;
18072 end.column = buffer.line_len(MultiBufferRow(end.row));
18073 start..end
18074 })
18075 .collect::<Vec<_>>();
18076
18077 self.unfold_ranges(&ranges, true, true, cx);
18078 } else {
18079 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18080 let buffer_ids = self
18081 .selections
18082 .disjoint_anchor_ranges()
18083 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18084 .collect::<HashSet<_>>();
18085 for buffer_id in buffer_ids {
18086 self.unfold_buffer(buffer_id, cx);
18087 }
18088 }
18089 }
18090
18091 pub fn unfold_recursive(
18092 &mut self,
18093 _: &UnfoldRecursive,
18094 _window: &mut Window,
18095 cx: &mut Context<Self>,
18096 ) {
18097 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18098 let selections = self.selections.all::<Point>(cx);
18099 let ranges = selections
18100 .iter()
18101 .map(|s| {
18102 let mut range = s.display_range(&display_map).sorted();
18103 *range.start.column_mut() = 0;
18104 *range.end.column_mut() = display_map.line_len(range.end.row());
18105 let start = range.start.to_point(&display_map);
18106 let end = range.end.to_point(&display_map);
18107 start..end
18108 })
18109 .collect::<Vec<_>>();
18110
18111 self.unfold_ranges(&ranges, true, true, cx);
18112 }
18113
18114 pub fn unfold_at(
18115 &mut self,
18116 buffer_row: MultiBufferRow,
18117 _window: &mut Window,
18118 cx: &mut Context<Self>,
18119 ) {
18120 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18121
18122 let intersection_range = Point::new(buffer_row.0, 0)
18123 ..Point::new(
18124 buffer_row.0,
18125 display_map.buffer_snapshot.line_len(buffer_row),
18126 );
18127
18128 let autoscroll = self
18129 .selections
18130 .all::<Point>(cx)
18131 .iter()
18132 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18133
18134 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18135 }
18136
18137 pub fn unfold_all(
18138 &mut self,
18139 _: &actions::UnfoldAll,
18140 _window: &mut Window,
18141 cx: &mut Context<Self>,
18142 ) {
18143 if self.buffer.read(cx).is_singleton() {
18144 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18145 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18146 } else {
18147 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18148 editor
18149 .update(cx, |editor, cx| {
18150 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18151 editor.unfold_buffer(buffer_id, cx);
18152 }
18153 })
18154 .ok();
18155 });
18156 }
18157 }
18158
18159 pub fn fold_selected_ranges(
18160 &mut self,
18161 _: &FoldSelectedRanges,
18162 window: &mut Window,
18163 cx: &mut Context<Self>,
18164 ) {
18165 let selections = self.selections.all_adjusted(cx);
18166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18167 let ranges = selections
18168 .into_iter()
18169 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18170 .collect::<Vec<_>>();
18171 self.fold_creases(ranges, true, window, cx);
18172 }
18173
18174 pub fn fold_ranges<T: ToOffset + Clone>(
18175 &mut self,
18176 ranges: Vec<Range<T>>,
18177 auto_scroll: bool,
18178 window: &mut Window,
18179 cx: &mut Context<Self>,
18180 ) {
18181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18182 let ranges = ranges
18183 .into_iter()
18184 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18185 .collect::<Vec<_>>();
18186 self.fold_creases(ranges, auto_scroll, window, cx);
18187 }
18188
18189 pub fn fold_creases<T: ToOffset + Clone>(
18190 &mut self,
18191 creases: Vec<Crease<T>>,
18192 auto_scroll: bool,
18193 _window: &mut Window,
18194 cx: &mut Context<Self>,
18195 ) {
18196 if creases.is_empty() {
18197 return;
18198 }
18199
18200 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18201
18202 if auto_scroll {
18203 self.request_autoscroll(Autoscroll::fit(), cx);
18204 }
18205
18206 cx.notify();
18207
18208 self.scrollbar_marker_state.dirty = true;
18209 self.folds_did_change(cx);
18210 }
18211
18212 /// Removes any folds whose ranges intersect any of the given ranges.
18213 pub fn unfold_ranges<T: ToOffset + Clone>(
18214 &mut self,
18215 ranges: &[Range<T>],
18216 inclusive: bool,
18217 auto_scroll: bool,
18218 cx: &mut Context<Self>,
18219 ) {
18220 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18221 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18222 });
18223 self.folds_did_change(cx);
18224 }
18225
18226 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18227 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18228 return;
18229 }
18230 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18231 self.display_map.update(cx, |display_map, cx| {
18232 display_map.fold_buffers([buffer_id], cx)
18233 });
18234 cx.emit(EditorEvent::BufferFoldToggled {
18235 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18236 folded: true,
18237 });
18238 cx.notify();
18239 }
18240
18241 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18242 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18243 return;
18244 }
18245 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18246 self.display_map.update(cx, |display_map, cx| {
18247 display_map.unfold_buffers([buffer_id], cx);
18248 });
18249 cx.emit(EditorEvent::BufferFoldToggled {
18250 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18251 folded: false,
18252 });
18253 cx.notify();
18254 }
18255
18256 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18257 self.display_map.read(cx).is_buffer_folded(buffer)
18258 }
18259
18260 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18261 self.display_map.read(cx).folded_buffers()
18262 }
18263
18264 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18265 self.display_map.update(cx, |display_map, cx| {
18266 display_map.disable_header_for_buffer(buffer_id, cx);
18267 });
18268 cx.notify();
18269 }
18270
18271 /// Removes any folds with the given ranges.
18272 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18273 &mut self,
18274 ranges: &[Range<T>],
18275 type_id: TypeId,
18276 auto_scroll: bool,
18277 cx: &mut Context<Self>,
18278 ) {
18279 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18280 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18281 });
18282 self.folds_did_change(cx);
18283 }
18284
18285 fn remove_folds_with<T: ToOffset + Clone>(
18286 &mut self,
18287 ranges: &[Range<T>],
18288 auto_scroll: bool,
18289 cx: &mut Context<Self>,
18290 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18291 ) {
18292 if ranges.is_empty() {
18293 return;
18294 }
18295
18296 let mut buffers_affected = HashSet::default();
18297 let multi_buffer = self.buffer().read(cx);
18298 for range in ranges {
18299 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18300 buffers_affected.insert(buffer.read(cx).remote_id());
18301 };
18302 }
18303
18304 self.display_map.update(cx, update);
18305
18306 if auto_scroll {
18307 self.request_autoscroll(Autoscroll::fit(), cx);
18308 }
18309
18310 cx.notify();
18311 self.scrollbar_marker_state.dirty = true;
18312 self.active_indent_guides_state.dirty = true;
18313 }
18314
18315 pub fn update_renderer_widths(
18316 &mut self,
18317 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18318 cx: &mut Context<Self>,
18319 ) -> bool {
18320 self.display_map
18321 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18322 }
18323
18324 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18325 self.display_map.read(cx).fold_placeholder.clone()
18326 }
18327
18328 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18329 self.buffer.update(cx, |buffer, cx| {
18330 buffer.set_all_diff_hunks_expanded(cx);
18331 });
18332 }
18333
18334 pub fn expand_all_diff_hunks(
18335 &mut self,
18336 _: &ExpandAllDiffHunks,
18337 _window: &mut Window,
18338 cx: &mut Context<Self>,
18339 ) {
18340 self.buffer.update(cx, |buffer, cx| {
18341 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18342 });
18343 }
18344
18345 pub fn toggle_selected_diff_hunks(
18346 &mut self,
18347 _: &ToggleSelectedDiffHunks,
18348 _window: &mut Window,
18349 cx: &mut Context<Self>,
18350 ) {
18351 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18352 self.toggle_diff_hunks_in_ranges(ranges, cx);
18353 }
18354
18355 pub fn diff_hunks_in_ranges<'a>(
18356 &'a self,
18357 ranges: &'a [Range<Anchor>],
18358 buffer: &'a MultiBufferSnapshot,
18359 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18360 ranges.iter().flat_map(move |range| {
18361 let end_excerpt_id = range.end.excerpt_id;
18362 let range = range.to_point(buffer);
18363 let mut peek_end = range.end;
18364 if range.end.row < buffer.max_row().0 {
18365 peek_end = Point::new(range.end.row + 1, 0);
18366 }
18367 buffer
18368 .diff_hunks_in_range(range.start..peek_end)
18369 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18370 })
18371 }
18372
18373 pub fn has_stageable_diff_hunks_in_ranges(
18374 &self,
18375 ranges: &[Range<Anchor>],
18376 snapshot: &MultiBufferSnapshot,
18377 ) -> bool {
18378 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18379 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18380 }
18381
18382 pub fn toggle_staged_selected_diff_hunks(
18383 &mut self,
18384 _: &::git::ToggleStaged,
18385 _: &mut Window,
18386 cx: &mut Context<Self>,
18387 ) {
18388 let snapshot = self.buffer.read(cx).snapshot(cx);
18389 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18390 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18391 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18392 }
18393
18394 pub fn set_render_diff_hunk_controls(
18395 &mut self,
18396 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18397 cx: &mut Context<Self>,
18398 ) {
18399 self.render_diff_hunk_controls = render_diff_hunk_controls;
18400 cx.notify();
18401 }
18402
18403 pub fn stage_and_next(
18404 &mut self,
18405 _: &::git::StageAndNext,
18406 window: &mut Window,
18407 cx: &mut Context<Self>,
18408 ) {
18409 self.do_stage_or_unstage_and_next(true, window, cx);
18410 }
18411
18412 pub fn unstage_and_next(
18413 &mut self,
18414 _: &::git::UnstageAndNext,
18415 window: &mut Window,
18416 cx: &mut Context<Self>,
18417 ) {
18418 self.do_stage_or_unstage_and_next(false, window, cx);
18419 }
18420
18421 pub fn stage_or_unstage_diff_hunks(
18422 &mut self,
18423 stage: bool,
18424 ranges: Vec<Range<Anchor>>,
18425 cx: &mut Context<Self>,
18426 ) {
18427 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18428 cx.spawn(async move |this, cx| {
18429 task.await?;
18430 this.update(cx, |this, cx| {
18431 let snapshot = this.buffer.read(cx).snapshot(cx);
18432 let chunk_by = this
18433 .diff_hunks_in_ranges(&ranges, &snapshot)
18434 .chunk_by(|hunk| hunk.buffer_id);
18435 for (buffer_id, hunks) in &chunk_by {
18436 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18437 }
18438 })
18439 })
18440 .detach_and_log_err(cx);
18441 }
18442
18443 fn save_buffers_for_ranges_if_needed(
18444 &mut self,
18445 ranges: &[Range<Anchor>],
18446 cx: &mut Context<Editor>,
18447 ) -> Task<Result<()>> {
18448 let multibuffer = self.buffer.read(cx);
18449 let snapshot = multibuffer.read(cx);
18450 let buffer_ids: HashSet<_> = ranges
18451 .iter()
18452 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18453 .collect();
18454 drop(snapshot);
18455
18456 let mut buffers = HashSet::default();
18457 for buffer_id in buffer_ids {
18458 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18459 let buffer = buffer_entity.read(cx);
18460 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18461 {
18462 buffers.insert(buffer_entity);
18463 }
18464 }
18465 }
18466
18467 if let Some(project) = &self.project {
18468 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18469 } else {
18470 Task::ready(Ok(()))
18471 }
18472 }
18473
18474 fn do_stage_or_unstage_and_next(
18475 &mut self,
18476 stage: bool,
18477 window: &mut Window,
18478 cx: &mut Context<Self>,
18479 ) {
18480 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18481
18482 if ranges.iter().any(|range| range.start != range.end) {
18483 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18484 return;
18485 }
18486
18487 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18488 let snapshot = self.snapshot(window, cx);
18489 let position = self.selections.newest::<Point>(cx).head();
18490 let mut row = snapshot
18491 .buffer_snapshot
18492 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18493 .find(|hunk| hunk.row_range.start.0 > position.row)
18494 .map(|hunk| hunk.row_range.start);
18495
18496 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18497 // Outside of the project diff editor, wrap around to the beginning.
18498 if !all_diff_hunks_expanded {
18499 row = row.or_else(|| {
18500 snapshot
18501 .buffer_snapshot
18502 .diff_hunks_in_range(Point::zero()..position)
18503 .find(|hunk| hunk.row_range.end.0 < position.row)
18504 .map(|hunk| hunk.row_range.start)
18505 });
18506 }
18507
18508 if let Some(row) = row {
18509 let destination = Point::new(row.0, 0);
18510 let autoscroll = Autoscroll::center();
18511
18512 self.unfold_ranges(&[destination..destination], false, false, cx);
18513 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18514 s.select_ranges([destination..destination]);
18515 });
18516 }
18517 }
18518
18519 fn do_stage_or_unstage(
18520 &self,
18521 stage: bool,
18522 buffer_id: BufferId,
18523 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18524 cx: &mut App,
18525 ) -> Option<()> {
18526 let project = self.project()?;
18527 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18528 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18529 let buffer_snapshot = buffer.read(cx).snapshot();
18530 let file_exists = buffer_snapshot
18531 .file()
18532 .is_some_and(|file| file.disk_state().exists());
18533 diff.update(cx, |diff, cx| {
18534 diff.stage_or_unstage_hunks(
18535 stage,
18536 &hunks
18537 .map(|hunk| buffer_diff::DiffHunk {
18538 buffer_range: hunk.buffer_range,
18539 diff_base_byte_range: hunk.diff_base_byte_range,
18540 secondary_status: hunk.secondary_status,
18541 range: Point::zero()..Point::zero(), // unused
18542 })
18543 .collect::<Vec<_>>(),
18544 &buffer_snapshot,
18545 file_exists,
18546 cx,
18547 )
18548 });
18549 None
18550 }
18551
18552 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18553 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18554 self.buffer
18555 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18556 }
18557
18558 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18559 self.buffer.update(cx, |buffer, cx| {
18560 let ranges = vec![Anchor::min()..Anchor::max()];
18561 if !buffer.all_diff_hunks_expanded()
18562 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18563 {
18564 buffer.collapse_diff_hunks(ranges, cx);
18565 true
18566 } else {
18567 false
18568 }
18569 })
18570 }
18571
18572 fn toggle_diff_hunks_in_ranges(
18573 &mut self,
18574 ranges: Vec<Range<Anchor>>,
18575 cx: &mut Context<Editor>,
18576 ) {
18577 self.buffer.update(cx, |buffer, cx| {
18578 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18579 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18580 })
18581 }
18582
18583 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18584 self.buffer.update(cx, |buffer, cx| {
18585 let snapshot = buffer.snapshot(cx);
18586 let excerpt_id = range.end.excerpt_id;
18587 let point_range = range.to_point(&snapshot);
18588 let expand = !buffer.single_hunk_is_expanded(range, cx);
18589 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18590 })
18591 }
18592
18593 pub(crate) fn apply_all_diff_hunks(
18594 &mut self,
18595 _: &ApplyAllDiffHunks,
18596 window: &mut Window,
18597 cx: &mut Context<Self>,
18598 ) {
18599 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18600
18601 let buffers = self.buffer.read(cx).all_buffers();
18602 for branch_buffer in buffers {
18603 branch_buffer.update(cx, |branch_buffer, cx| {
18604 branch_buffer.merge_into_base(Vec::new(), cx);
18605 });
18606 }
18607
18608 if let Some(project) = self.project.clone() {
18609 self.save(
18610 SaveOptions {
18611 format: true,
18612 autosave: false,
18613 },
18614 project,
18615 window,
18616 cx,
18617 )
18618 .detach_and_log_err(cx);
18619 }
18620 }
18621
18622 pub(crate) fn apply_selected_diff_hunks(
18623 &mut self,
18624 _: &ApplyDiffHunk,
18625 window: &mut Window,
18626 cx: &mut Context<Self>,
18627 ) {
18628 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18629 let snapshot = self.snapshot(window, cx);
18630 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18631 let mut ranges_by_buffer = HashMap::default();
18632 self.transact(window, cx, |editor, _window, cx| {
18633 for hunk in hunks {
18634 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18635 ranges_by_buffer
18636 .entry(buffer.clone())
18637 .or_insert_with(Vec::new)
18638 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18639 }
18640 }
18641
18642 for (buffer, ranges) in ranges_by_buffer {
18643 buffer.update(cx, |buffer, cx| {
18644 buffer.merge_into_base(ranges, cx);
18645 });
18646 }
18647 });
18648
18649 if let Some(project) = self.project.clone() {
18650 self.save(
18651 SaveOptions {
18652 format: true,
18653 autosave: false,
18654 },
18655 project,
18656 window,
18657 cx,
18658 )
18659 .detach_and_log_err(cx);
18660 }
18661 }
18662
18663 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18664 if hovered != self.gutter_hovered {
18665 self.gutter_hovered = hovered;
18666 cx.notify();
18667 }
18668 }
18669
18670 pub fn insert_blocks(
18671 &mut self,
18672 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18673 autoscroll: Option<Autoscroll>,
18674 cx: &mut Context<Self>,
18675 ) -> Vec<CustomBlockId> {
18676 let blocks = self
18677 .display_map
18678 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18679 if let Some(autoscroll) = autoscroll {
18680 self.request_autoscroll(autoscroll, cx);
18681 }
18682 cx.notify();
18683 blocks
18684 }
18685
18686 pub fn resize_blocks(
18687 &mut self,
18688 heights: HashMap<CustomBlockId, u32>,
18689 autoscroll: Option<Autoscroll>,
18690 cx: &mut Context<Self>,
18691 ) {
18692 self.display_map
18693 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18694 if let Some(autoscroll) = autoscroll {
18695 self.request_autoscroll(autoscroll, cx);
18696 }
18697 cx.notify();
18698 }
18699
18700 pub fn replace_blocks(
18701 &mut self,
18702 renderers: HashMap<CustomBlockId, RenderBlock>,
18703 autoscroll: Option<Autoscroll>,
18704 cx: &mut Context<Self>,
18705 ) {
18706 self.display_map
18707 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18708 if let Some(autoscroll) = autoscroll {
18709 self.request_autoscroll(autoscroll, cx);
18710 }
18711 cx.notify();
18712 }
18713
18714 pub fn remove_blocks(
18715 &mut self,
18716 block_ids: HashSet<CustomBlockId>,
18717 autoscroll: Option<Autoscroll>,
18718 cx: &mut Context<Self>,
18719 ) {
18720 self.display_map.update(cx, |display_map, cx| {
18721 display_map.remove_blocks(block_ids, cx)
18722 });
18723 if let Some(autoscroll) = autoscroll {
18724 self.request_autoscroll(autoscroll, cx);
18725 }
18726 cx.notify();
18727 }
18728
18729 pub fn row_for_block(
18730 &self,
18731 block_id: CustomBlockId,
18732 cx: &mut Context<Self>,
18733 ) -> Option<DisplayRow> {
18734 self.display_map
18735 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18736 }
18737
18738 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18739 self.focused_block = Some(focused_block);
18740 }
18741
18742 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18743 self.focused_block.take()
18744 }
18745
18746 pub fn insert_creases(
18747 &mut self,
18748 creases: impl IntoIterator<Item = Crease<Anchor>>,
18749 cx: &mut Context<Self>,
18750 ) -> Vec<CreaseId> {
18751 self.display_map
18752 .update(cx, |map, cx| map.insert_creases(creases, cx))
18753 }
18754
18755 pub fn remove_creases(
18756 &mut self,
18757 ids: impl IntoIterator<Item = CreaseId>,
18758 cx: &mut Context<Self>,
18759 ) -> Vec<(CreaseId, Range<Anchor>)> {
18760 self.display_map
18761 .update(cx, |map, cx| map.remove_creases(ids, cx))
18762 }
18763
18764 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18765 self.display_map
18766 .update(cx, |map, cx| map.snapshot(cx))
18767 .longest_row()
18768 }
18769
18770 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18771 self.display_map
18772 .update(cx, |map, cx| map.snapshot(cx))
18773 .max_point()
18774 }
18775
18776 pub fn text(&self, cx: &App) -> String {
18777 self.buffer.read(cx).read(cx).text()
18778 }
18779
18780 pub fn is_empty(&self, cx: &App) -> bool {
18781 self.buffer.read(cx).read(cx).is_empty()
18782 }
18783
18784 pub fn text_option(&self, cx: &App) -> Option<String> {
18785 let text = self.text(cx);
18786 let text = text.trim();
18787
18788 if text.is_empty() {
18789 return None;
18790 }
18791
18792 Some(text.to_string())
18793 }
18794
18795 pub fn set_text(
18796 &mut self,
18797 text: impl Into<Arc<str>>,
18798 window: &mut Window,
18799 cx: &mut Context<Self>,
18800 ) {
18801 self.transact(window, cx, |this, _, cx| {
18802 this.buffer
18803 .read(cx)
18804 .as_singleton()
18805 .expect("you can only call set_text on editors for singleton buffers")
18806 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18807 });
18808 }
18809
18810 pub fn display_text(&self, cx: &mut App) -> String {
18811 self.display_map
18812 .update(cx, |map, cx| map.snapshot(cx))
18813 .text()
18814 }
18815
18816 fn create_minimap(
18817 &self,
18818 minimap_settings: MinimapSettings,
18819 window: &mut Window,
18820 cx: &mut Context<Self>,
18821 ) -> Option<Entity<Self>> {
18822 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18823 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18824 }
18825
18826 fn initialize_new_minimap(
18827 &self,
18828 minimap_settings: MinimapSettings,
18829 window: &mut Window,
18830 cx: &mut Context<Self>,
18831 ) -> Entity<Self> {
18832 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18833
18834 let mut minimap = Editor::new_internal(
18835 EditorMode::Minimap {
18836 parent: cx.weak_entity(),
18837 },
18838 self.buffer.clone(),
18839 None,
18840 Some(self.display_map.clone()),
18841 window,
18842 cx,
18843 );
18844 minimap.scroll_manager.clone_state(&self.scroll_manager);
18845 minimap.set_text_style_refinement(TextStyleRefinement {
18846 font_size: Some(MINIMAP_FONT_SIZE),
18847 font_weight: Some(MINIMAP_FONT_WEIGHT),
18848 ..Default::default()
18849 });
18850 minimap.update_minimap_configuration(minimap_settings, cx);
18851 cx.new(|_| minimap)
18852 }
18853
18854 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18855 let current_line_highlight = minimap_settings
18856 .current_line_highlight
18857 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18858 self.set_current_line_highlight(Some(current_line_highlight));
18859 }
18860
18861 pub fn minimap(&self) -> Option<&Entity<Self>> {
18862 self.minimap
18863 .as_ref()
18864 .filter(|_| self.minimap_visibility.visible())
18865 }
18866
18867 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18868 let mut wrap_guides = smallvec![];
18869
18870 if self.show_wrap_guides == Some(false) {
18871 return wrap_guides;
18872 }
18873
18874 let settings = self.buffer.read(cx).language_settings(cx);
18875 if settings.show_wrap_guides {
18876 match self.soft_wrap_mode(cx) {
18877 SoftWrap::Column(soft_wrap) => {
18878 wrap_guides.push((soft_wrap as usize, true));
18879 }
18880 SoftWrap::Bounded(soft_wrap) => {
18881 wrap_guides.push((soft_wrap as usize, true));
18882 }
18883 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18884 }
18885 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18886 }
18887
18888 wrap_guides
18889 }
18890
18891 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18892 let settings = self.buffer.read(cx).language_settings(cx);
18893 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18894 match mode {
18895 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18896 SoftWrap::None
18897 }
18898 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18899 language_settings::SoftWrap::PreferredLineLength => {
18900 SoftWrap::Column(settings.preferred_line_length)
18901 }
18902 language_settings::SoftWrap::Bounded => {
18903 SoftWrap::Bounded(settings.preferred_line_length)
18904 }
18905 }
18906 }
18907
18908 pub fn set_soft_wrap_mode(
18909 &mut self,
18910 mode: language_settings::SoftWrap,
18911
18912 cx: &mut Context<Self>,
18913 ) {
18914 self.soft_wrap_mode_override = Some(mode);
18915 cx.notify();
18916 }
18917
18918 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18919 self.hard_wrap = hard_wrap;
18920 cx.notify();
18921 }
18922
18923 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18924 self.text_style_refinement = Some(style);
18925 }
18926
18927 /// called by the Element so we know what style we were most recently rendered with.
18928 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
18929 // We intentionally do not inform the display map about the minimap style
18930 // so that wrapping is not recalculated and stays consistent for the editor
18931 // and its linked minimap.
18932 if !self.mode.is_minimap() {
18933 let font = style.text.font();
18934 let font_size = style.text.font_size.to_pixels(window.rem_size());
18935 let display_map = self
18936 .placeholder_display_map
18937 .as_ref()
18938 .filter(|_| self.is_empty(cx))
18939 .unwrap_or(&self.display_map);
18940
18941 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
18942 }
18943 self.style = Some(style);
18944 }
18945
18946 pub fn style(&self) -> Option<&EditorStyle> {
18947 self.style.as_ref()
18948 }
18949
18950 // Called by the element. This method is not designed to be called outside of the editor
18951 // element's layout code because it does not notify when rewrapping is computed synchronously.
18952 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18953 if self.is_empty(cx) {
18954 self.placeholder_display_map
18955 .as_ref()
18956 .map_or(false, |display_map| {
18957 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
18958 })
18959 } else {
18960 self.display_map
18961 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18962 }
18963 }
18964
18965 pub fn set_soft_wrap(&mut self) {
18966 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18967 }
18968
18969 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18970 if self.soft_wrap_mode_override.is_some() {
18971 self.soft_wrap_mode_override.take();
18972 } else {
18973 let soft_wrap = match self.soft_wrap_mode(cx) {
18974 SoftWrap::GitDiff => return,
18975 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18976 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18977 language_settings::SoftWrap::None
18978 }
18979 };
18980 self.soft_wrap_mode_override = Some(soft_wrap);
18981 }
18982 cx.notify();
18983 }
18984
18985 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18986 let Some(workspace) = self.workspace() else {
18987 return;
18988 };
18989 let fs = workspace.read(cx).app_state().fs.clone();
18990 let current_show = TabBarSettings::get_global(cx).show;
18991 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18992 setting.show = Some(!current_show);
18993 });
18994 }
18995
18996 pub fn toggle_indent_guides(
18997 &mut self,
18998 _: &ToggleIndentGuides,
18999 _: &mut Window,
19000 cx: &mut Context<Self>,
19001 ) {
19002 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19003 self.buffer
19004 .read(cx)
19005 .language_settings(cx)
19006 .indent_guides
19007 .enabled
19008 });
19009 self.show_indent_guides = Some(!currently_enabled);
19010 cx.notify();
19011 }
19012
19013 fn should_show_indent_guides(&self) -> Option<bool> {
19014 self.show_indent_guides
19015 }
19016
19017 pub fn toggle_line_numbers(
19018 &mut self,
19019 _: &ToggleLineNumbers,
19020 _: &mut Window,
19021 cx: &mut Context<Self>,
19022 ) {
19023 let mut editor_settings = EditorSettings::get_global(cx).clone();
19024 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19025 EditorSettings::override_global(editor_settings, cx);
19026 }
19027
19028 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19029 if let Some(show_line_numbers) = self.show_line_numbers {
19030 return show_line_numbers;
19031 }
19032 EditorSettings::get_global(cx).gutter.line_numbers
19033 }
19034
19035 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19036 self.use_relative_line_numbers
19037 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19038 }
19039
19040 pub fn toggle_relative_line_numbers(
19041 &mut self,
19042 _: &ToggleRelativeLineNumbers,
19043 _: &mut Window,
19044 cx: &mut Context<Self>,
19045 ) {
19046 let is_relative = self.should_use_relative_line_numbers(cx);
19047 self.set_relative_line_number(Some(!is_relative), cx)
19048 }
19049
19050 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19051 self.use_relative_line_numbers = is_relative;
19052 cx.notify();
19053 }
19054
19055 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19056 self.show_gutter = show_gutter;
19057 cx.notify();
19058 }
19059
19060 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19061 self.show_scrollbars = ScrollbarAxes {
19062 horizontal: show,
19063 vertical: show,
19064 };
19065 cx.notify();
19066 }
19067
19068 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19069 self.show_scrollbars.vertical = show;
19070 cx.notify();
19071 }
19072
19073 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19074 self.show_scrollbars.horizontal = show;
19075 cx.notify();
19076 }
19077
19078 pub fn set_minimap_visibility(
19079 &mut self,
19080 minimap_visibility: MinimapVisibility,
19081 window: &mut Window,
19082 cx: &mut Context<Self>,
19083 ) {
19084 if self.minimap_visibility != minimap_visibility {
19085 if minimap_visibility.visible() && self.minimap.is_none() {
19086 let minimap_settings = EditorSettings::get_global(cx).minimap;
19087 self.minimap =
19088 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19089 }
19090 self.minimap_visibility = minimap_visibility;
19091 cx.notify();
19092 }
19093 }
19094
19095 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19096 self.set_show_scrollbars(false, cx);
19097 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19098 }
19099
19100 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19101 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19102 }
19103
19104 /// Normally the text in full mode and auto height editors is padded on the
19105 /// left side by roughly half a character width for improved hit testing.
19106 ///
19107 /// Use this method to disable this for cases where this is not wanted (e.g.
19108 /// if you want to align the editor text with some other text above or below)
19109 /// or if you want to add this padding to single-line editors.
19110 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19111 self.offset_content = offset_content;
19112 cx.notify();
19113 }
19114
19115 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19116 self.show_line_numbers = Some(show_line_numbers);
19117 cx.notify();
19118 }
19119
19120 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19121 self.disable_expand_excerpt_buttons = true;
19122 cx.notify();
19123 }
19124
19125 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19126 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19127 cx.notify();
19128 }
19129
19130 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19131 self.show_code_actions = Some(show_code_actions);
19132 cx.notify();
19133 }
19134
19135 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19136 self.show_runnables = Some(show_runnables);
19137 cx.notify();
19138 }
19139
19140 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19141 self.show_breakpoints = Some(show_breakpoints);
19142 cx.notify();
19143 }
19144
19145 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19146 if self.display_map.read(cx).masked != masked {
19147 self.display_map.update(cx, |map, _| map.masked = masked);
19148 }
19149 cx.notify()
19150 }
19151
19152 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19153 self.show_wrap_guides = Some(show_wrap_guides);
19154 cx.notify();
19155 }
19156
19157 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19158 self.show_indent_guides = Some(show_indent_guides);
19159 cx.notify();
19160 }
19161
19162 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19163 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19164 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19165 && let Some(dir) = file.abs_path(cx).parent()
19166 {
19167 return Some(dir.to_owned());
19168 }
19169
19170 if let Some(project_path) = buffer.read(cx).project_path(cx) {
19171 return Some(project_path.path.to_path_buf());
19172 }
19173 }
19174
19175 None
19176 }
19177
19178 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19179 self.active_excerpt(cx)?
19180 .1
19181 .read(cx)
19182 .file()
19183 .and_then(|f| f.as_local())
19184 }
19185
19186 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19187 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19188 let buffer = buffer.read(cx);
19189 if let Some(project_path) = buffer.project_path(cx) {
19190 let project = self.project()?.read(cx);
19191 project.absolute_path(&project_path, cx)
19192 } else {
19193 buffer
19194 .file()
19195 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19196 }
19197 })
19198 }
19199
19200 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19201 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19202 let project_path = buffer.read(cx).project_path(cx)?;
19203 let project = self.project()?.read(cx);
19204 let entry = project.entry_for_path(&project_path, cx)?;
19205 let path = entry.path.to_path_buf();
19206 Some(path)
19207 })
19208 }
19209
19210 pub fn reveal_in_finder(
19211 &mut self,
19212 _: &RevealInFileManager,
19213 _window: &mut Window,
19214 cx: &mut Context<Self>,
19215 ) {
19216 if let Some(target) = self.target_file(cx) {
19217 cx.reveal_path(&target.abs_path(cx));
19218 }
19219 }
19220
19221 pub fn copy_path(
19222 &mut self,
19223 _: &zed_actions::workspace::CopyPath,
19224 _window: &mut Window,
19225 cx: &mut Context<Self>,
19226 ) {
19227 if let Some(path) = self.target_file_abs_path(cx)
19228 && let Some(path) = path.to_str()
19229 {
19230 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19231 }
19232 }
19233
19234 pub fn copy_relative_path(
19235 &mut self,
19236 _: &zed_actions::workspace::CopyRelativePath,
19237 _window: &mut Window,
19238 cx: &mut Context<Self>,
19239 ) {
19240 if let Some(path) = self.target_file_path(cx)
19241 && let Some(path) = path.to_str()
19242 {
19243 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19244 }
19245 }
19246
19247 /// Returns the project path for the editor's buffer, if any buffer is
19248 /// opened in the editor.
19249 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19250 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19251 buffer.read(cx).project_path(cx)
19252 } else {
19253 None
19254 }
19255 }
19256
19257 // Returns true if the editor handled a go-to-line request
19258 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19259 maybe!({
19260 let breakpoint_store = self.breakpoint_store.as_ref()?;
19261
19262 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19263 else {
19264 self.clear_row_highlights::<ActiveDebugLine>();
19265 return None;
19266 };
19267
19268 let position = active_stack_frame.position;
19269 let buffer_id = position.buffer_id?;
19270 let snapshot = self
19271 .project
19272 .as_ref()?
19273 .read(cx)
19274 .buffer_for_id(buffer_id, cx)?
19275 .read(cx)
19276 .snapshot();
19277
19278 let mut handled = false;
19279 for (id, ExcerptRange { context, .. }) in
19280 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19281 {
19282 if context.start.cmp(&position, &snapshot).is_ge()
19283 || context.end.cmp(&position, &snapshot).is_lt()
19284 {
19285 continue;
19286 }
19287 let snapshot = self.buffer.read(cx).snapshot(cx);
19288 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19289
19290 handled = true;
19291 self.clear_row_highlights::<ActiveDebugLine>();
19292
19293 self.go_to_line::<ActiveDebugLine>(
19294 multibuffer_anchor,
19295 Some(cx.theme().colors().editor_debugger_active_line_background),
19296 window,
19297 cx,
19298 );
19299
19300 cx.notify();
19301 }
19302
19303 handled.then_some(())
19304 })
19305 .is_some()
19306 }
19307
19308 pub fn copy_file_name_without_extension(
19309 &mut self,
19310 _: &CopyFileNameWithoutExtension,
19311 _: &mut Window,
19312 cx: &mut Context<Self>,
19313 ) {
19314 if let Some(file) = self.target_file(cx)
19315 && let Some(file_stem) = file.path().file_stem()
19316 && let Some(name) = file_stem.to_str()
19317 {
19318 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19319 }
19320 }
19321
19322 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19323 if let Some(file) = self.target_file(cx)
19324 && let Some(file_name) = file.path().file_name()
19325 && let Some(name) = file_name.to_str()
19326 {
19327 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19328 }
19329 }
19330
19331 pub fn toggle_git_blame(
19332 &mut self,
19333 _: &::git::Blame,
19334 window: &mut Window,
19335 cx: &mut Context<Self>,
19336 ) {
19337 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19338
19339 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19340 self.start_git_blame(true, window, cx);
19341 }
19342
19343 cx.notify();
19344 }
19345
19346 pub fn toggle_git_blame_inline(
19347 &mut self,
19348 _: &ToggleGitBlameInline,
19349 window: &mut Window,
19350 cx: &mut Context<Self>,
19351 ) {
19352 self.toggle_git_blame_inline_internal(true, window, cx);
19353 cx.notify();
19354 }
19355
19356 pub fn open_git_blame_commit(
19357 &mut self,
19358 _: &OpenGitBlameCommit,
19359 window: &mut Window,
19360 cx: &mut Context<Self>,
19361 ) {
19362 self.open_git_blame_commit_internal(window, cx);
19363 }
19364
19365 fn open_git_blame_commit_internal(
19366 &mut self,
19367 window: &mut Window,
19368 cx: &mut Context<Self>,
19369 ) -> Option<()> {
19370 let blame = self.blame.as_ref()?;
19371 let snapshot = self.snapshot(window, cx);
19372 let cursor = self.selections.newest::<Point>(cx).head();
19373 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19374 let (_, blame_entry) = blame
19375 .update(cx, |blame, cx| {
19376 blame
19377 .blame_for_rows(
19378 &[RowInfo {
19379 buffer_id: Some(buffer.remote_id()),
19380 buffer_row: Some(point.row),
19381 ..Default::default()
19382 }],
19383 cx,
19384 )
19385 .next()
19386 })
19387 .flatten()?;
19388 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19389 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19390 let workspace = self.workspace()?.downgrade();
19391 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19392 None
19393 }
19394
19395 pub fn git_blame_inline_enabled(&self) -> bool {
19396 self.git_blame_inline_enabled
19397 }
19398
19399 pub fn toggle_selection_menu(
19400 &mut self,
19401 _: &ToggleSelectionMenu,
19402 _: &mut Window,
19403 cx: &mut Context<Self>,
19404 ) {
19405 self.show_selection_menu = self
19406 .show_selection_menu
19407 .map(|show_selections_menu| !show_selections_menu)
19408 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19409
19410 cx.notify();
19411 }
19412
19413 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19414 self.show_selection_menu
19415 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19416 }
19417
19418 fn start_git_blame(
19419 &mut self,
19420 user_triggered: bool,
19421 window: &mut Window,
19422 cx: &mut Context<Self>,
19423 ) {
19424 if let Some(project) = self.project() {
19425 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19426 && buffer.read(cx).file().is_none()
19427 {
19428 return;
19429 }
19430
19431 let focused = self.focus_handle(cx).contains_focused(window, cx);
19432
19433 let project = project.clone();
19434 let blame = cx
19435 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19436 self.blame_subscription =
19437 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19438 self.blame = Some(blame);
19439 }
19440 }
19441
19442 fn toggle_git_blame_inline_internal(
19443 &mut self,
19444 user_triggered: bool,
19445 window: &mut Window,
19446 cx: &mut Context<Self>,
19447 ) {
19448 if self.git_blame_inline_enabled {
19449 self.git_blame_inline_enabled = false;
19450 self.show_git_blame_inline = false;
19451 self.show_git_blame_inline_delay_task.take();
19452 } else {
19453 self.git_blame_inline_enabled = true;
19454 self.start_git_blame_inline(user_triggered, window, cx);
19455 }
19456
19457 cx.notify();
19458 }
19459
19460 fn start_git_blame_inline(
19461 &mut self,
19462 user_triggered: bool,
19463 window: &mut Window,
19464 cx: &mut Context<Self>,
19465 ) {
19466 self.start_git_blame(user_triggered, window, cx);
19467
19468 if ProjectSettings::get_global(cx)
19469 .git
19470 .inline_blame_delay()
19471 .is_some()
19472 {
19473 self.start_inline_blame_timer(window, cx);
19474 } else {
19475 self.show_git_blame_inline = true
19476 }
19477 }
19478
19479 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19480 self.blame.as_ref()
19481 }
19482
19483 pub fn show_git_blame_gutter(&self) -> bool {
19484 self.show_git_blame_gutter
19485 }
19486
19487 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19488 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19489 }
19490
19491 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19492 self.show_git_blame_inline
19493 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19494 && !self.newest_selection_head_on_empty_line(cx)
19495 && self.has_blame_entries(cx)
19496 }
19497
19498 fn has_blame_entries(&self, cx: &App) -> bool {
19499 self.blame()
19500 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19501 }
19502
19503 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19504 let cursor_anchor = self.selections.newest_anchor().head();
19505
19506 let snapshot = self.buffer.read(cx).snapshot(cx);
19507 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19508
19509 snapshot.line_len(buffer_row) == 0
19510 }
19511
19512 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19513 let buffer_and_selection = maybe!({
19514 let selection = self.selections.newest::<Point>(cx);
19515 let selection_range = selection.range();
19516
19517 let multi_buffer = self.buffer().read(cx);
19518 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19519 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19520
19521 let (buffer, range, _) = if selection.reversed {
19522 buffer_ranges.first()
19523 } else {
19524 buffer_ranges.last()
19525 }?;
19526
19527 let selection = text::ToPoint::to_point(&range.start, buffer).row
19528 ..text::ToPoint::to_point(&range.end, buffer).row;
19529 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19530 });
19531
19532 let Some((buffer, selection)) = buffer_and_selection else {
19533 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19534 };
19535
19536 let Some(project) = self.project() else {
19537 return Task::ready(Err(anyhow!("editor does not have project")));
19538 };
19539
19540 project.update(cx, |project, cx| {
19541 project.get_permalink_to_line(&buffer, selection, cx)
19542 })
19543 }
19544
19545 pub fn copy_permalink_to_line(
19546 &mut self,
19547 _: &CopyPermalinkToLine,
19548 window: &mut Window,
19549 cx: &mut Context<Self>,
19550 ) {
19551 let permalink_task = self.get_permalink_to_line(cx);
19552 let workspace = self.workspace();
19553
19554 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19555 Ok(permalink) => {
19556 cx.update(|_, cx| {
19557 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19558 })
19559 .ok();
19560 }
19561 Err(err) => {
19562 let message = format!("Failed to copy permalink: {err}");
19563
19564 anyhow::Result::<()>::Err(err).log_err();
19565
19566 if let Some(workspace) = workspace {
19567 workspace
19568 .update_in(cx, |workspace, _, cx| {
19569 struct CopyPermalinkToLine;
19570
19571 workspace.show_toast(
19572 Toast::new(
19573 NotificationId::unique::<CopyPermalinkToLine>(),
19574 message,
19575 ),
19576 cx,
19577 )
19578 })
19579 .ok();
19580 }
19581 }
19582 })
19583 .detach();
19584 }
19585
19586 pub fn copy_file_location(
19587 &mut self,
19588 _: &CopyFileLocation,
19589 _: &mut Window,
19590 cx: &mut Context<Self>,
19591 ) {
19592 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19593 if let Some(file) = self.target_file(cx)
19594 && let Some(path) = file.path().to_str()
19595 {
19596 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19597 }
19598 }
19599
19600 pub fn open_permalink_to_line(
19601 &mut self,
19602 _: &OpenPermalinkToLine,
19603 window: &mut Window,
19604 cx: &mut Context<Self>,
19605 ) {
19606 let permalink_task = self.get_permalink_to_line(cx);
19607 let workspace = self.workspace();
19608
19609 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19610 Ok(permalink) => {
19611 cx.update(|_, cx| {
19612 cx.open_url(permalink.as_ref());
19613 })
19614 .ok();
19615 }
19616 Err(err) => {
19617 let message = format!("Failed to open permalink: {err}");
19618
19619 anyhow::Result::<()>::Err(err).log_err();
19620
19621 if let Some(workspace) = workspace {
19622 workspace
19623 .update(cx, |workspace, cx| {
19624 struct OpenPermalinkToLine;
19625
19626 workspace.show_toast(
19627 Toast::new(
19628 NotificationId::unique::<OpenPermalinkToLine>(),
19629 message,
19630 ),
19631 cx,
19632 )
19633 })
19634 .ok();
19635 }
19636 }
19637 })
19638 .detach();
19639 }
19640
19641 pub fn insert_uuid_v4(
19642 &mut self,
19643 _: &InsertUuidV4,
19644 window: &mut Window,
19645 cx: &mut Context<Self>,
19646 ) {
19647 self.insert_uuid(UuidVersion::V4, window, cx);
19648 }
19649
19650 pub fn insert_uuid_v7(
19651 &mut self,
19652 _: &InsertUuidV7,
19653 window: &mut Window,
19654 cx: &mut Context<Self>,
19655 ) {
19656 self.insert_uuid(UuidVersion::V7, window, cx);
19657 }
19658
19659 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19660 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19661 self.transact(window, cx, |this, window, cx| {
19662 let edits = this
19663 .selections
19664 .all::<Point>(cx)
19665 .into_iter()
19666 .map(|selection| {
19667 let uuid = match version {
19668 UuidVersion::V4 => uuid::Uuid::new_v4(),
19669 UuidVersion::V7 => uuid::Uuid::now_v7(),
19670 };
19671
19672 (selection.range(), uuid.to_string())
19673 });
19674 this.edit(edits, cx);
19675 this.refresh_edit_prediction(true, false, window, cx);
19676 });
19677 }
19678
19679 pub fn open_selections_in_multibuffer(
19680 &mut self,
19681 _: &OpenSelectionsInMultibuffer,
19682 window: &mut Window,
19683 cx: &mut Context<Self>,
19684 ) {
19685 let multibuffer = self.buffer.read(cx);
19686
19687 let Some(buffer) = multibuffer.as_singleton() else {
19688 return;
19689 };
19690
19691 let Some(workspace) = self.workspace() else {
19692 return;
19693 };
19694
19695 let title = multibuffer.title(cx).to_string();
19696
19697 let locations = self
19698 .selections
19699 .all_anchors(cx)
19700 .iter()
19701 .map(|selection| Location {
19702 buffer: buffer.clone(),
19703 range: selection.start.text_anchor..selection.end.text_anchor,
19704 })
19705 .collect::<Vec<_>>();
19706
19707 cx.spawn_in(window, async move |_, cx| {
19708 workspace.update_in(cx, |workspace, window, cx| {
19709 Self::open_locations_in_multibuffer(
19710 workspace,
19711 locations,
19712 format!("Selections for '{title}'"),
19713 false,
19714 MultibufferSelectionMode::All,
19715 window,
19716 cx,
19717 );
19718 })
19719 })
19720 .detach();
19721 }
19722
19723 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19724 /// last highlight added will be used.
19725 ///
19726 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19727 pub fn highlight_rows<T: 'static>(
19728 &mut self,
19729 range: Range<Anchor>,
19730 color: Hsla,
19731 options: RowHighlightOptions,
19732 cx: &mut Context<Self>,
19733 ) {
19734 let snapshot = self.buffer().read(cx).snapshot(cx);
19735 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19736 let ix = row_highlights.binary_search_by(|highlight| {
19737 Ordering::Equal
19738 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19739 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19740 });
19741
19742 if let Err(mut ix) = ix {
19743 let index = post_inc(&mut self.highlight_order);
19744
19745 // If this range intersects with the preceding highlight, then merge it with
19746 // the preceding highlight. Otherwise insert a new highlight.
19747 let mut merged = false;
19748 if ix > 0 {
19749 let prev_highlight = &mut row_highlights[ix - 1];
19750 if prev_highlight
19751 .range
19752 .end
19753 .cmp(&range.start, &snapshot)
19754 .is_ge()
19755 {
19756 ix -= 1;
19757 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19758 prev_highlight.range.end = range.end;
19759 }
19760 merged = true;
19761 prev_highlight.index = index;
19762 prev_highlight.color = color;
19763 prev_highlight.options = options;
19764 }
19765 }
19766
19767 if !merged {
19768 row_highlights.insert(
19769 ix,
19770 RowHighlight {
19771 range,
19772 index,
19773 color,
19774 options,
19775 type_id: TypeId::of::<T>(),
19776 },
19777 );
19778 }
19779
19780 // If any of the following highlights intersect with this one, merge them.
19781 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19782 let highlight = &row_highlights[ix];
19783 if next_highlight
19784 .range
19785 .start
19786 .cmp(&highlight.range.end, &snapshot)
19787 .is_le()
19788 {
19789 if next_highlight
19790 .range
19791 .end
19792 .cmp(&highlight.range.end, &snapshot)
19793 .is_gt()
19794 {
19795 row_highlights[ix].range.end = next_highlight.range.end;
19796 }
19797 row_highlights.remove(ix + 1);
19798 } else {
19799 break;
19800 }
19801 }
19802 }
19803 }
19804
19805 /// Remove any highlighted row ranges of the given type that intersect the
19806 /// given ranges.
19807 pub fn remove_highlighted_rows<T: 'static>(
19808 &mut self,
19809 ranges_to_remove: Vec<Range<Anchor>>,
19810 cx: &mut Context<Self>,
19811 ) {
19812 let snapshot = self.buffer().read(cx).snapshot(cx);
19813 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19814 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19815 row_highlights.retain(|highlight| {
19816 while let Some(range_to_remove) = ranges_to_remove.peek() {
19817 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19818 Ordering::Less | Ordering::Equal => {
19819 ranges_to_remove.next();
19820 }
19821 Ordering::Greater => {
19822 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19823 Ordering::Less | Ordering::Equal => {
19824 return false;
19825 }
19826 Ordering::Greater => break,
19827 }
19828 }
19829 }
19830 }
19831
19832 true
19833 })
19834 }
19835
19836 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19837 pub fn clear_row_highlights<T: 'static>(&mut self) {
19838 self.highlighted_rows.remove(&TypeId::of::<T>());
19839 }
19840
19841 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19842 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19843 self.highlighted_rows
19844 .get(&TypeId::of::<T>())
19845 .map_or(&[] as &[_], |vec| vec.as_slice())
19846 .iter()
19847 .map(|highlight| (highlight.range.clone(), highlight.color))
19848 }
19849
19850 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19851 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19852 /// Allows to ignore certain kinds of highlights.
19853 pub fn highlighted_display_rows(
19854 &self,
19855 window: &mut Window,
19856 cx: &mut App,
19857 ) -> BTreeMap<DisplayRow, LineHighlight> {
19858 let snapshot = self.snapshot(window, cx);
19859 let mut used_highlight_orders = HashMap::default();
19860 self.highlighted_rows
19861 .iter()
19862 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19863 .fold(
19864 BTreeMap::<DisplayRow, LineHighlight>::new(),
19865 |mut unique_rows, highlight| {
19866 let start = highlight.range.start.to_display_point(&snapshot);
19867 let end = highlight.range.end.to_display_point(&snapshot);
19868 let start_row = start.row().0;
19869 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19870 && end.column() == 0
19871 {
19872 end.row().0.saturating_sub(1)
19873 } else {
19874 end.row().0
19875 };
19876 for row in start_row..=end_row {
19877 let used_index =
19878 used_highlight_orders.entry(row).or_insert(highlight.index);
19879 if highlight.index >= *used_index {
19880 *used_index = highlight.index;
19881 unique_rows.insert(
19882 DisplayRow(row),
19883 LineHighlight {
19884 include_gutter: highlight.options.include_gutter,
19885 border: None,
19886 background: highlight.color.into(),
19887 type_id: Some(highlight.type_id),
19888 },
19889 );
19890 }
19891 }
19892 unique_rows
19893 },
19894 )
19895 }
19896
19897 pub fn highlighted_display_row_for_autoscroll(
19898 &self,
19899 snapshot: &DisplaySnapshot,
19900 ) -> Option<DisplayRow> {
19901 self.highlighted_rows
19902 .values()
19903 .flat_map(|highlighted_rows| highlighted_rows.iter())
19904 .filter_map(|highlight| {
19905 if highlight.options.autoscroll {
19906 Some(highlight.range.start.to_display_point(snapshot).row())
19907 } else {
19908 None
19909 }
19910 })
19911 .min()
19912 }
19913
19914 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19915 self.highlight_background::<SearchWithinRange>(
19916 ranges,
19917 |colors| colors.colors().editor_document_highlight_read_background,
19918 cx,
19919 )
19920 }
19921
19922 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19923 self.breadcrumb_header = Some(new_header);
19924 }
19925
19926 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19927 self.clear_background_highlights::<SearchWithinRange>(cx);
19928 }
19929
19930 pub fn highlight_background<T: 'static>(
19931 &mut self,
19932 ranges: &[Range<Anchor>],
19933 color_fetcher: fn(&Theme) -> Hsla,
19934 cx: &mut Context<Self>,
19935 ) {
19936 self.background_highlights.insert(
19937 HighlightKey::Type(TypeId::of::<T>()),
19938 (color_fetcher, Arc::from(ranges)),
19939 );
19940 self.scrollbar_marker_state.dirty = true;
19941 cx.notify();
19942 }
19943
19944 pub fn highlight_background_key<T: 'static>(
19945 &mut self,
19946 key: usize,
19947 ranges: &[Range<Anchor>],
19948 color_fetcher: fn(&Theme) -> Hsla,
19949 cx: &mut Context<Self>,
19950 ) {
19951 self.background_highlights.insert(
19952 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19953 (color_fetcher, Arc::from(ranges)),
19954 );
19955 self.scrollbar_marker_state.dirty = true;
19956 cx.notify();
19957 }
19958
19959 pub fn clear_background_highlights<T: 'static>(
19960 &mut self,
19961 cx: &mut Context<Self>,
19962 ) -> Option<BackgroundHighlight> {
19963 let text_highlights = self
19964 .background_highlights
19965 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19966 if !text_highlights.1.is_empty() {
19967 self.scrollbar_marker_state.dirty = true;
19968 cx.notify();
19969 }
19970 Some(text_highlights)
19971 }
19972
19973 pub fn highlight_gutter<T: 'static>(
19974 &mut self,
19975 ranges: impl Into<Vec<Range<Anchor>>>,
19976 color_fetcher: fn(&App) -> Hsla,
19977 cx: &mut Context<Self>,
19978 ) {
19979 self.gutter_highlights
19980 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19981 cx.notify();
19982 }
19983
19984 pub fn clear_gutter_highlights<T: 'static>(
19985 &mut self,
19986 cx: &mut Context<Self>,
19987 ) -> Option<GutterHighlight> {
19988 cx.notify();
19989 self.gutter_highlights.remove(&TypeId::of::<T>())
19990 }
19991
19992 pub fn insert_gutter_highlight<T: 'static>(
19993 &mut self,
19994 range: Range<Anchor>,
19995 color_fetcher: fn(&App) -> Hsla,
19996 cx: &mut Context<Self>,
19997 ) {
19998 let snapshot = self.buffer().read(cx).snapshot(cx);
19999 let mut highlights = self
20000 .gutter_highlights
20001 .remove(&TypeId::of::<T>())
20002 .map(|(_, highlights)| highlights)
20003 .unwrap_or_default();
20004 let ix = highlights.binary_search_by(|highlight| {
20005 Ordering::Equal
20006 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20007 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20008 });
20009 if let Err(ix) = ix {
20010 highlights.insert(ix, range);
20011 }
20012 self.gutter_highlights
20013 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20014 }
20015
20016 pub fn remove_gutter_highlights<T: 'static>(
20017 &mut self,
20018 ranges_to_remove: Vec<Range<Anchor>>,
20019 cx: &mut Context<Self>,
20020 ) {
20021 let snapshot = self.buffer().read(cx).snapshot(cx);
20022 let Some((color_fetcher, mut gutter_highlights)) =
20023 self.gutter_highlights.remove(&TypeId::of::<T>())
20024 else {
20025 return;
20026 };
20027 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20028 gutter_highlights.retain(|highlight| {
20029 while let Some(range_to_remove) = ranges_to_remove.peek() {
20030 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20031 Ordering::Less | Ordering::Equal => {
20032 ranges_to_remove.next();
20033 }
20034 Ordering::Greater => {
20035 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20036 Ordering::Less | Ordering::Equal => {
20037 return false;
20038 }
20039 Ordering::Greater => break,
20040 }
20041 }
20042 }
20043 }
20044
20045 true
20046 });
20047 self.gutter_highlights
20048 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20049 }
20050
20051 #[cfg(feature = "test-support")]
20052 pub fn all_text_highlights(
20053 &self,
20054 window: &mut Window,
20055 cx: &mut Context<Self>,
20056 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20057 let snapshot = self.snapshot(window, cx);
20058 self.display_map.update(cx, |display_map, _| {
20059 display_map
20060 .all_text_highlights()
20061 .map(|highlight| {
20062 let (style, ranges) = highlight.as_ref();
20063 (
20064 *style,
20065 ranges
20066 .iter()
20067 .map(|range| range.clone().to_display_points(&snapshot))
20068 .collect(),
20069 )
20070 })
20071 .collect()
20072 })
20073 }
20074
20075 #[cfg(feature = "test-support")]
20076 pub fn all_text_background_highlights(
20077 &self,
20078 window: &mut Window,
20079 cx: &mut Context<Self>,
20080 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20081 let snapshot = self.snapshot(window, cx);
20082 let buffer = &snapshot.buffer_snapshot;
20083 let start = buffer.anchor_before(0);
20084 let end = buffer.anchor_after(buffer.len());
20085 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20086 }
20087
20088 #[cfg(any(test, feature = "test-support"))]
20089 pub fn sorted_background_highlights_in_range(
20090 &self,
20091 search_range: Range<Anchor>,
20092 display_snapshot: &DisplaySnapshot,
20093 theme: &Theme,
20094 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20095 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20096 res.sort_by(|a, b| {
20097 a.0.start
20098 .cmp(&b.0.start)
20099 .then_with(|| a.0.end.cmp(&b.0.end))
20100 .then_with(|| a.1.cmp(&b.1))
20101 });
20102 res
20103 }
20104
20105 #[cfg(feature = "test-support")]
20106 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20107 let snapshot = self.buffer().read(cx).snapshot(cx);
20108
20109 let highlights = self
20110 .background_highlights
20111 .get(&HighlightKey::Type(TypeId::of::<
20112 items::BufferSearchHighlights,
20113 >()));
20114
20115 if let Some((_color, ranges)) = highlights {
20116 ranges
20117 .iter()
20118 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20119 .collect_vec()
20120 } else {
20121 vec![]
20122 }
20123 }
20124
20125 fn document_highlights_for_position<'a>(
20126 &'a self,
20127 position: Anchor,
20128 buffer: &'a MultiBufferSnapshot,
20129 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20130 let read_highlights = self
20131 .background_highlights
20132 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20133 .map(|h| &h.1);
20134 let write_highlights = self
20135 .background_highlights
20136 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20137 .map(|h| &h.1);
20138 let left_position = position.bias_left(buffer);
20139 let right_position = position.bias_right(buffer);
20140 read_highlights
20141 .into_iter()
20142 .chain(write_highlights)
20143 .flat_map(move |ranges| {
20144 let start_ix = match ranges.binary_search_by(|probe| {
20145 let cmp = probe.end.cmp(&left_position, buffer);
20146 if cmp.is_ge() {
20147 Ordering::Greater
20148 } else {
20149 Ordering::Less
20150 }
20151 }) {
20152 Ok(i) | Err(i) => i,
20153 };
20154
20155 ranges[start_ix..]
20156 .iter()
20157 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20158 })
20159 }
20160
20161 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20162 self.background_highlights
20163 .get(&HighlightKey::Type(TypeId::of::<T>()))
20164 .is_some_and(|(_, highlights)| !highlights.is_empty())
20165 }
20166
20167 /// Returns all background highlights for a given range.
20168 ///
20169 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20170 pub fn background_highlights_in_range(
20171 &self,
20172 search_range: Range<Anchor>,
20173 display_snapshot: &DisplaySnapshot,
20174 theme: &Theme,
20175 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20176 let mut results = Vec::new();
20177 for (color_fetcher, ranges) in self.background_highlights.values() {
20178 let color = color_fetcher(theme);
20179 let start_ix = match ranges.binary_search_by(|probe| {
20180 let cmp = probe
20181 .end
20182 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20183 if cmp.is_gt() {
20184 Ordering::Greater
20185 } else {
20186 Ordering::Less
20187 }
20188 }) {
20189 Ok(i) | Err(i) => i,
20190 };
20191 for range in &ranges[start_ix..] {
20192 if range
20193 .start
20194 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20195 .is_ge()
20196 {
20197 break;
20198 }
20199
20200 let start = range.start.to_display_point(display_snapshot);
20201 let end = range.end.to_display_point(display_snapshot);
20202 results.push((start..end, color))
20203 }
20204 }
20205 results
20206 }
20207
20208 pub fn gutter_highlights_in_range(
20209 &self,
20210 search_range: Range<Anchor>,
20211 display_snapshot: &DisplaySnapshot,
20212 cx: &App,
20213 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20214 let mut results = Vec::new();
20215 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20216 let color = color_fetcher(cx);
20217 let start_ix = match ranges.binary_search_by(|probe| {
20218 let cmp = probe
20219 .end
20220 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20221 if cmp.is_gt() {
20222 Ordering::Greater
20223 } else {
20224 Ordering::Less
20225 }
20226 }) {
20227 Ok(i) | Err(i) => i,
20228 };
20229 for range in &ranges[start_ix..] {
20230 if range
20231 .start
20232 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20233 .is_ge()
20234 {
20235 break;
20236 }
20237
20238 let start = range.start.to_display_point(display_snapshot);
20239 let end = range.end.to_display_point(display_snapshot);
20240 results.push((start..end, color))
20241 }
20242 }
20243 results
20244 }
20245
20246 /// Get the text ranges corresponding to the redaction query
20247 pub fn redacted_ranges(
20248 &self,
20249 search_range: Range<Anchor>,
20250 display_snapshot: &DisplaySnapshot,
20251 cx: &App,
20252 ) -> Vec<Range<DisplayPoint>> {
20253 display_snapshot
20254 .buffer_snapshot
20255 .redacted_ranges(search_range, |file| {
20256 if let Some(file) = file {
20257 file.is_private()
20258 && EditorSettings::get(
20259 Some(SettingsLocation {
20260 worktree_id: file.worktree_id(cx),
20261 path: file.path().as_ref(),
20262 }),
20263 cx,
20264 )
20265 .redact_private_values
20266 } else {
20267 false
20268 }
20269 })
20270 .map(|range| {
20271 range.start.to_display_point(display_snapshot)
20272 ..range.end.to_display_point(display_snapshot)
20273 })
20274 .collect()
20275 }
20276
20277 pub fn highlight_text_key<T: 'static>(
20278 &mut self,
20279 key: usize,
20280 ranges: Vec<Range<Anchor>>,
20281 style: HighlightStyle,
20282 cx: &mut Context<Self>,
20283 ) {
20284 self.display_map.update(cx, |map, _| {
20285 map.highlight_text(
20286 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20287 ranges,
20288 style,
20289 );
20290 });
20291 cx.notify();
20292 }
20293
20294 pub fn highlight_text<T: 'static>(
20295 &mut self,
20296 ranges: Vec<Range<Anchor>>,
20297 style: HighlightStyle,
20298 cx: &mut Context<Self>,
20299 ) {
20300 self.display_map.update(cx, |map, _| {
20301 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20302 });
20303 cx.notify();
20304 }
20305
20306 pub(crate) fn highlight_inlays<T: 'static>(
20307 &mut self,
20308 highlights: Vec<InlayHighlight>,
20309 style: HighlightStyle,
20310 cx: &mut Context<Self>,
20311 ) {
20312 self.display_map.update(cx, |map, _| {
20313 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20314 });
20315 cx.notify();
20316 }
20317
20318 pub fn text_highlights<'a, T: 'static>(
20319 &'a self,
20320 cx: &'a App,
20321 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20322 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20323 }
20324
20325 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20326 let cleared = self
20327 .display_map
20328 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20329 if cleared {
20330 cx.notify();
20331 }
20332 }
20333
20334 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20335 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20336 && self.focus_handle.is_focused(window)
20337 }
20338
20339 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20340 self.show_cursor_when_unfocused = is_enabled;
20341 cx.notify();
20342 }
20343
20344 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20345 cx.notify();
20346 }
20347
20348 fn on_debug_session_event(
20349 &mut self,
20350 _session: Entity<Session>,
20351 event: &SessionEvent,
20352 cx: &mut Context<Self>,
20353 ) {
20354 if let SessionEvent::InvalidateInlineValue = event {
20355 self.refresh_inline_values(cx);
20356 }
20357 }
20358
20359 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20360 let Some(project) = self.project.clone() else {
20361 return;
20362 };
20363
20364 if !self.inline_value_cache.enabled {
20365 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20366 self.splice_inlays(&inlays, Vec::new(), cx);
20367 return;
20368 }
20369
20370 let current_execution_position = self
20371 .highlighted_rows
20372 .get(&TypeId::of::<ActiveDebugLine>())
20373 .and_then(|lines| lines.last().map(|line| line.range.end));
20374
20375 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20376 let inline_values = editor
20377 .update(cx, |editor, cx| {
20378 let Some(current_execution_position) = current_execution_position else {
20379 return Some(Task::ready(Ok(Vec::new())));
20380 };
20381
20382 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20383 let snapshot = buffer.snapshot(cx);
20384
20385 let excerpt = snapshot.excerpt_containing(
20386 current_execution_position..current_execution_position,
20387 )?;
20388
20389 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20390 })?;
20391
20392 let range =
20393 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20394
20395 project.inline_values(buffer, range, cx)
20396 })
20397 .ok()
20398 .flatten()?
20399 .await
20400 .context("refreshing debugger inlays")
20401 .log_err()?;
20402
20403 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20404
20405 for (buffer_id, inline_value) in inline_values
20406 .into_iter()
20407 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20408 {
20409 buffer_inline_values
20410 .entry(buffer_id)
20411 .or_default()
20412 .push(inline_value);
20413 }
20414
20415 editor
20416 .update(cx, |editor, cx| {
20417 let snapshot = editor.buffer.read(cx).snapshot(cx);
20418 let mut new_inlays = Vec::default();
20419
20420 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20421 let buffer_id = buffer_snapshot.remote_id();
20422 buffer_inline_values
20423 .get(&buffer_id)
20424 .into_iter()
20425 .flatten()
20426 .for_each(|hint| {
20427 let inlay = Inlay::debugger(
20428 post_inc(&mut editor.next_inlay_id),
20429 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20430 hint.text(),
20431 );
20432 if !inlay.text.chars().contains(&'\n') {
20433 new_inlays.push(inlay);
20434 }
20435 });
20436 }
20437
20438 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20439 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20440
20441 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20442 })
20443 .ok()?;
20444 Some(())
20445 });
20446 }
20447
20448 fn on_buffer_event(
20449 &mut self,
20450 multibuffer: &Entity<MultiBuffer>,
20451 event: &multi_buffer::Event,
20452 window: &mut Window,
20453 cx: &mut Context<Self>,
20454 ) {
20455 match event {
20456 multi_buffer::Event::Edited {
20457 singleton_buffer_edited,
20458 edited_buffer,
20459 } => {
20460 self.scrollbar_marker_state.dirty = true;
20461 self.active_indent_guides_state.dirty = true;
20462 self.refresh_active_diagnostics(cx);
20463 self.refresh_code_actions(window, cx);
20464 self.refresh_selected_text_highlights(true, window, cx);
20465 self.refresh_single_line_folds(window, cx);
20466 refresh_matching_bracket_highlights(self, window, cx);
20467 if self.has_active_edit_prediction() {
20468 self.update_visible_edit_prediction(window, cx);
20469 }
20470 if let Some(project) = self.project.as_ref()
20471 && let Some(edited_buffer) = edited_buffer
20472 {
20473 project.update(cx, |project, cx| {
20474 self.registered_buffers
20475 .entry(edited_buffer.read(cx).remote_id())
20476 .or_insert_with(|| {
20477 project.register_buffer_with_language_servers(edited_buffer, cx)
20478 });
20479 });
20480 }
20481 cx.emit(EditorEvent::BufferEdited);
20482 cx.emit(SearchEvent::MatchesInvalidated);
20483
20484 if let Some(buffer) = edited_buffer {
20485 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20486 }
20487
20488 if *singleton_buffer_edited {
20489 if let Some(buffer) = edited_buffer
20490 && buffer.read(cx).file().is_none()
20491 {
20492 cx.emit(EditorEvent::TitleChanged);
20493 }
20494 if let Some(project) = &self.project {
20495 #[allow(clippy::mutable_key_type)]
20496 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20497 multibuffer
20498 .all_buffers()
20499 .into_iter()
20500 .filter_map(|buffer| {
20501 buffer.update(cx, |buffer, cx| {
20502 let language = buffer.language()?;
20503 let should_discard = project.update(cx, |project, cx| {
20504 project.is_local()
20505 && !project.has_language_servers_for(buffer, cx)
20506 });
20507 should_discard.not().then_some(language.clone())
20508 })
20509 })
20510 .collect::<HashSet<_>>()
20511 });
20512 if !languages_affected.is_empty() {
20513 self.refresh_inlay_hints(
20514 InlayHintRefreshReason::BufferEdited(languages_affected),
20515 cx,
20516 );
20517 }
20518 }
20519 }
20520
20521 let Some(project) = &self.project else { return };
20522 let (telemetry, is_via_ssh) = {
20523 let project = project.read(cx);
20524 let telemetry = project.client().telemetry().clone();
20525 let is_via_ssh = project.is_via_remote_server();
20526 (telemetry, is_via_ssh)
20527 };
20528 refresh_linked_ranges(self, window, cx);
20529 telemetry.log_edit_event("editor", is_via_ssh);
20530 }
20531 multi_buffer::Event::ExcerptsAdded {
20532 buffer,
20533 predecessor,
20534 excerpts,
20535 } => {
20536 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20537 let buffer_id = buffer.read(cx).remote_id();
20538 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20539 && let Some(project) = &self.project
20540 {
20541 update_uncommitted_diff_for_buffer(
20542 cx.entity(),
20543 project,
20544 [buffer.clone()],
20545 self.buffer.clone(),
20546 cx,
20547 )
20548 .detach();
20549 }
20550 if self.active_diagnostics != ActiveDiagnostic::All {
20551 self.update_lsp_data(false, Some(buffer_id), window, cx);
20552 }
20553 cx.emit(EditorEvent::ExcerptsAdded {
20554 buffer: buffer.clone(),
20555 predecessor: *predecessor,
20556 excerpts: excerpts.clone(),
20557 });
20558 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20559 }
20560 multi_buffer::Event::ExcerptsRemoved {
20561 ids,
20562 removed_buffer_ids,
20563 } => {
20564 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20565 let buffer = self.buffer.read(cx);
20566 self.registered_buffers
20567 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20568 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20569 cx.emit(EditorEvent::ExcerptsRemoved {
20570 ids: ids.clone(),
20571 removed_buffer_ids: removed_buffer_ids.clone(),
20572 });
20573 }
20574 multi_buffer::Event::ExcerptsEdited {
20575 excerpt_ids,
20576 buffer_ids,
20577 } => {
20578 self.display_map.update(cx, |map, cx| {
20579 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20580 });
20581 cx.emit(EditorEvent::ExcerptsEdited {
20582 ids: excerpt_ids.clone(),
20583 });
20584 }
20585 multi_buffer::Event::ExcerptsExpanded { ids } => {
20586 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20587 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20588 }
20589 multi_buffer::Event::Reparsed(buffer_id) => {
20590 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20591 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20592
20593 cx.emit(EditorEvent::Reparsed(*buffer_id));
20594 }
20595 multi_buffer::Event::DiffHunksToggled => {
20596 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20597 }
20598 multi_buffer::Event::LanguageChanged(buffer_id) => {
20599 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20600 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20601 cx.emit(EditorEvent::Reparsed(*buffer_id));
20602 cx.notify();
20603 }
20604 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20605 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20606 multi_buffer::Event::FileHandleChanged
20607 | multi_buffer::Event::Reloaded
20608 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20609 multi_buffer::Event::DiagnosticsUpdated => {
20610 self.update_diagnostics_state(window, cx);
20611 }
20612 _ => {}
20613 };
20614 }
20615
20616 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20617 if !self.diagnostics_enabled() {
20618 return;
20619 }
20620 self.refresh_active_diagnostics(cx);
20621 self.refresh_inline_diagnostics(true, window, cx);
20622 self.scrollbar_marker_state.dirty = true;
20623 cx.notify();
20624 }
20625
20626 pub fn start_temporary_diff_override(&mut self) {
20627 self.load_diff_task.take();
20628 self.temporary_diff_override = true;
20629 }
20630
20631 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20632 self.temporary_diff_override = false;
20633 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20634 self.buffer.update(cx, |buffer, cx| {
20635 buffer.set_all_diff_hunks_collapsed(cx);
20636 });
20637
20638 if let Some(project) = self.project.clone() {
20639 self.load_diff_task = Some(
20640 update_uncommitted_diff_for_buffer(
20641 cx.entity(),
20642 &project,
20643 self.buffer.read(cx).all_buffers(),
20644 self.buffer.clone(),
20645 cx,
20646 )
20647 .shared(),
20648 );
20649 }
20650 }
20651
20652 fn on_display_map_changed(
20653 &mut self,
20654 _: Entity<DisplayMap>,
20655 _: &mut Window,
20656 cx: &mut Context<Self>,
20657 ) {
20658 cx.notify();
20659 }
20660
20661 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20662 if self.diagnostics_enabled() {
20663 let new_severity = EditorSettings::get_global(cx)
20664 .diagnostics_max_severity
20665 .unwrap_or(DiagnosticSeverity::Hint);
20666 self.set_max_diagnostics_severity(new_severity, cx);
20667 }
20668 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20669 self.update_edit_prediction_settings(cx);
20670 self.refresh_edit_prediction(true, false, window, cx);
20671 self.refresh_inline_values(cx);
20672 self.refresh_inlay_hints(
20673 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20674 self.selections.newest_anchor().head(),
20675 &self.buffer.read(cx).snapshot(cx),
20676 cx,
20677 )),
20678 cx,
20679 );
20680
20681 let old_cursor_shape = self.cursor_shape;
20682 let old_show_breadcrumbs = self.show_breadcrumbs;
20683
20684 {
20685 let editor_settings = EditorSettings::get_global(cx);
20686 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20687 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20688 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20689 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20690 }
20691
20692 if old_cursor_shape != self.cursor_shape {
20693 cx.emit(EditorEvent::CursorShapeChanged);
20694 }
20695
20696 if old_show_breadcrumbs != self.show_breadcrumbs {
20697 cx.emit(EditorEvent::BreadcrumbsChanged);
20698 }
20699
20700 let project_settings = ProjectSettings::get_global(cx);
20701 self.serialize_dirty_buffers =
20702 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20703
20704 if self.mode.is_full() {
20705 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20706 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20707 if self.show_inline_diagnostics != show_inline_diagnostics {
20708 self.show_inline_diagnostics = show_inline_diagnostics;
20709 self.refresh_inline_diagnostics(false, window, cx);
20710 }
20711
20712 if self.git_blame_inline_enabled != inline_blame_enabled {
20713 self.toggle_git_blame_inline_internal(false, window, cx);
20714 }
20715
20716 let minimap_settings = EditorSettings::get_global(cx).minimap;
20717 if self.minimap_visibility != MinimapVisibility::Disabled {
20718 if self.minimap_visibility.settings_visibility()
20719 != minimap_settings.minimap_enabled()
20720 {
20721 self.set_minimap_visibility(
20722 MinimapVisibility::for_mode(self.mode(), cx),
20723 window,
20724 cx,
20725 );
20726 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20727 minimap_entity.update(cx, |minimap_editor, cx| {
20728 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20729 })
20730 }
20731 }
20732 }
20733
20734 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20735 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20736 }) {
20737 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20738 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20739 }
20740 self.refresh_colors(false, None, window, cx);
20741 }
20742
20743 cx.notify();
20744 }
20745
20746 pub fn set_searchable(&mut self, searchable: bool) {
20747 self.searchable = searchable;
20748 }
20749
20750 pub fn searchable(&self) -> bool {
20751 self.searchable
20752 }
20753
20754 fn open_proposed_changes_editor(
20755 &mut self,
20756 _: &OpenProposedChangesEditor,
20757 window: &mut Window,
20758 cx: &mut Context<Self>,
20759 ) {
20760 let Some(workspace) = self.workspace() else {
20761 cx.propagate();
20762 return;
20763 };
20764
20765 let selections = self.selections.all::<usize>(cx);
20766 let multi_buffer = self.buffer.read(cx);
20767 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20768 let mut new_selections_by_buffer = HashMap::default();
20769 for selection in selections {
20770 for (buffer, range, _) in
20771 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20772 {
20773 let mut range = range.to_point(buffer);
20774 range.start.column = 0;
20775 range.end.column = buffer.line_len(range.end.row);
20776 new_selections_by_buffer
20777 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20778 .or_insert(Vec::new())
20779 .push(range)
20780 }
20781 }
20782
20783 let proposed_changes_buffers = new_selections_by_buffer
20784 .into_iter()
20785 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20786 .collect::<Vec<_>>();
20787 let proposed_changes_editor = cx.new(|cx| {
20788 ProposedChangesEditor::new(
20789 "Proposed changes",
20790 proposed_changes_buffers,
20791 self.project.clone(),
20792 window,
20793 cx,
20794 )
20795 });
20796
20797 window.defer(cx, move |window, cx| {
20798 workspace.update(cx, |workspace, cx| {
20799 workspace.active_pane().update(cx, |pane, cx| {
20800 pane.add_item(
20801 Box::new(proposed_changes_editor),
20802 true,
20803 true,
20804 None,
20805 window,
20806 cx,
20807 );
20808 });
20809 });
20810 });
20811 }
20812
20813 pub fn open_excerpts_in_split(
20814 &mut self,
20815 _: &OpenExcerptsSplit,
20816 window: &mut Window,
20817 cx: &mut Context<Self>,
20818 ) {
20819 self.open_excerpts_common(None, true, window, cx)
20820 }
20821
20822 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20823 self.open_excerpts_common(None, false, window, cx)
20824 }
20825
20826 fn open_excerpts_common(
20827 &mut self,
20828 jump_data: Option<JumpData>,
20829 split: bool,
20830 window: &mut Window,
20831 cx: &mut Context<Self>,
20832 ) {
20833 let Some(workspace) = self.workspace() else {
20834 cx.propagate();
20835 return;
20836 };
20837
20838 if self.buffer.read(cx).is_singleton() {
20839 cx.propagate();
20840 return;
20841 }
20842
20843 let mut new_selections_by_buffer = HashMap::default();
20844 match &jump_data {
20845 Some(JumpData::MultiBufferPoint {
20846 excerpt_id,
20847 position,
20848 anchor,
20849 line_offset_from_top,
20850 }) => {
20851 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20852 if let Some(buffer) = multi_buffer_snapshot
20853 .buffer_id_for_excerpt(*excerpt_id)
20854 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20855 {
20856 let buffer_snapshot = buffer.read(cx).snapshot();
20857 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20858 language::ToPoint::to_point(anchor, &buffer_snapshot)
20859 } else {
20860 buffer_snapshot.clip_point(*position, Bias::Left)
20861 };
20862 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20863 new_selections_by_buffer.insert(
20864 buffer,
20865 (
20866 vec![jump_to_offset..jump_to_offset],
20867 Some(*line_offset_from_top),
20868 ),
20869 );
20870 }
20871 }
20872 Some(JumpData::MultiBufferRow {
20873 row,
20874 line_offset_from_top,
20875 }) => {
20876 let point = MultiBufferPoint::new(row.0, 0);
20877 if let Some((buffer, buffer_point, _)) =
20878 self.buffer.read(cx).point_to_buffer_point(point, cx)
20879 {
20880 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20881 new_selections_by_buffer
20882 .entry(buffer)
20883 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20884 .0
20885 .push(buffer_offset..buffer_offset)
20886 }
20887 }
20888 None => {
20889 let selections = self.selections.all::<usize>(cx);
20890 let multi_buffer = self.buffer.read(cx);
20891 for selection in selections {
20892 for (snapshot, range, _, anchor) in multi_buffer
20893 .snapshot(cx)
20894 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20895 {
20896 if let Some(anchor) = anchor {
20897 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20898 else {
20899 continue;
20900 };
20901 let offset = text::ToOffset::to_offset(
20902 &anchor.text_anchor,
20903 &buffer_handle.read(cx).snapshot(),
20904 );
20905 let range = offset..offset;
20906 new_selections_by_buffer
20907 .entry(buffer_handle)
20908 .or_insert((Vec::new(), None))
20909 .0
20910 .push(range)
20911 } else {
20912 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20913 else {
20914 continue;
20915 };
20916 new_selections_by_buffer
20917 .entry(buffer_handle)
20918 .or_insert((Vec::new(), None))
20919 .0
20920 .push(range)
20921 }
20922 }
20923 }
20924 }
20925 }
20926
20927 new_selections_by_buffer
20928 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20929
20930 if new_selections_by_buffer.is_empty() {
20931 return;
20932 }
20933
20934 // We defer the pane interaction because we ourselves are a workspace item
20935 // and activating a new item causes the pane to call a method on us reentrantly,
20936 // which panics if we're on the stack.
20937 window.defer(cx, move |window, cx| {
20938 workspace.update(cx, |workspace, cx| {
20939 let pane = if split {
20940 workspace.adjacent_pane(window, cx)
20941 } else {
20942 workspace.active_pane().clone()
20943 };
20944
20945 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20946 let editor = buffer
20947 .read(cx)
20948 .file()
20949 .is_none()
20950 .then(|| {
20951 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20952 // so `workspace.open_project_item` will never find them, always opening a new editor.
20953 // Instead, we try to activate the existing editor in the pane first.
20954 let (editor, pane_item_index) =
20955 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20956 let editor = item.downcast::<Editor>()?;
20957 let singleton_buffer =
20958 editor.read(cx).buffer().read(cx).as_singleton()?;
20959 if singleton_buffer == buffer {
20960 Some((editor, i))
20961 } else {
20962 None
20963 }
20964 })?;
20965 pane.update(cx, |pane, cx| {
20966 pane.activate_item(pane_item_index, true, true, window, cx)
20967 });
20968 Some(editor)
20969 })
20970 .flatten()
20971 .unwrap_or_else(|| {
20972 workspace.open_project_item::<Self>(
20973 pane.clone(),
20974 buffer,
20975 true,
20976 true,
20977 window,
20978 cx,
20979 )
20980 });
20981
20982 editor.update(cx, |editor, cx| {
20983 let autoscroll = match scroll_offset {
20984 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20985 None => Autoscroll::newest(),
20986 };
20987 let nav_history = editor.nav_history.take();
20988 editor.change_selections(
20989 SelectionEffects::scroll(autoscroll),
20990 window,
20991 cx,
20992 |s| {
20993 s.select_ranges(ranges);
20994 },
20995 );
20996 editor.nav_history = nav_history;
20997 });
20998 }
20999 })
21000 });
21001 }
21002
21003 // For now, don't allow opening excerpts in buffers that aren't backed by
21004 // regular project files.
21005 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21006 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21007 }
21008
21009 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21010 let snapshot = self.buffer.read(cx).read(cx);
21011 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21012 Some(
21013 ranges
21014 .iter()
21015 .map(move |range| {
21016 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21017 })
21018 .collect(),
21019 )
21020 }
21021
21022 fn selection_replacement_ranges(
21023 &self,
21024 range: Range<OffsetUtf16>,
21025 cx: &mut App,
21026 ) -> Vec<Range<OffsetUtf16>> {
21027 let selections = self.selections.all::<OffsetUtf16>(cx);
21028 let newest_selection = selections
21029 .iter()
21030 .max_by_key(|selection| selection.id)
21031 .unwrap();
21032 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21033 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21034 let snapshot = self.buffer.read(cx).read(cx);
21035 selections
21036 .into_iter()
21037 .map(|mut selection| {
21038 selection.start.0 =
21039 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21040 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21041 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21042 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21043 })
21044 .collect()
21045 }
21046
21047 fn report_editor_event(
21048 &self,
21049 reported_event: ReportEditorEvent,
21050 file_extension: Option<String>,
21051 cx: &App,
21052 ) {
21053 if cfg!(any(test, feature = "test-support")) {
21054 return;
21055 }
21056
21057 let Some(project) = &self.project else { return };
21058
21059 // If None, we are in a file without an extension
21060 let file = self
21061 .buffer
21062 .read(cx)
21063 .as_singleton()
21064 .and_then(|b| b.read(cx).file());
21065 let file_extension = file_extension.or(file
21066 .as_ref()
21067 .and_then(|file| Path::new(file.file_name(cx)).extension())
21068 .and_then(|e| e.to_str())
21069 .map(|a| a.to_string()));
21070
21071 let vim_mode = vim_enabled(cx);
21072
21073 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21074 let copilot_enabled = edit_predictions_provider
21075 == language::language_settings::EditPredictionProvider::Copilot;
21076 let copilot_enabled_for_language = self
21077 .buffer
21078 .read(cx)
21079 .language_settings(cx)
21080 .show_edit_predictions;
21081
21082 let project = project.read(cx);
21083 let event_type = reported_event.event_type();
21084
21085 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21086 telemetry::event!(
21087 event_type,
21088 type = if auto_saved {"autosave"} else {"manual"},
21089 file_extension,
21090 vim_mode,
21091 copilot_enabled,
21092 copilot_enabled_for_language,
21093 edit_predictions_provider,
21094 is_via_ssh = project.is_via_remote_server(),
21095 );
21096 } else {
21097 telemetry::event!(
21098 event_type,
21099 file_extension,
21100 vim_mode,
21101 copilot_enabled,
21102 copilot_enabled_for_language,
21103 edit_predictions_provider,
21104 is_via_ssh = project.is_via_remote_server(),
21105 );
21106 };
21107 }
21108
21109 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21110 /// with each line being an array of {text, highlight} objects.
21111 fn copy_highlight_json(
21112 &mut self,
21113 _: &CopyHighlightJson,
21114 window: &mut Window,
21115 cx: &mut Context<Self>,
21116 ) {
21117 #[derive(Serialize)]
21118 struct Chunk<'a> {
21119 text: String,
21120 highlight: Option<&'a str>,
21121 }
21122
21123 let snapshot = self.buffer.read(cx).snapshot(cx);
21124 let range = self
21125 .selected_text_range(false, window, cx)
21126 .and_then(|selection| {
21127 if selection.range.is_empty() {
21128 None
21129 } else {
21130 Some(selection.range)
21131 }
21132 })
21133 .unwrap_or_else(|| 0..snapshot.len());
21134
21135 let chunks = snapshot.chunks(range, true);
21136 let mut lines = Vec::new();
21137 let mut line: VecDeque<Chunk> = VecDeque::new();
21138
21139 let Some(style) = self.style.as_ref() else {
21140 return;
21141 };
21142
21143 for chunk in chunks {
21144 let highlight = chunk
21145 .syntax_highlight_id
21146 .and_then(|id| id.name(&style.syntax));
21147 let mut chunk_lines = chunk.text.split('\n').peekable();
21148 while let Some(text) = chunk_lines.next() {
21149 let mut merged_with_last_token = false;
21150 if let Some(last_token) = line.back_mut()
21151 && last_token.highlight == highlight
21152 {
21153 last_token.text.push_str(text);
21154 merged_with_last_token = true;
21155 }
21156
21157 if !merged_with_last_token {
21158 line.push_back(Chunk {
21159 text: text.into(),
21160 highlight,
21161 });
21162 }
21163
21164 if chunk_lines.peek().is_some() {
21165 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21166 line.pop_front();
21167 }
21168 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21169 line.pop_back();
21170 }
21171
21172 lines.push(mem::take(&mut line));
21173 }
21174 }
21175 }
21176
21177 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21178 return;
21179 };
21180 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21181 }
21182
21183 pub fn open_context_menu(
21184 &mut self,
21185 _: &OpenContextMenu,
21186 window: &mut Window,
21187 cx: &mut Context<Self>,
21188 ) {
21189 self.request_autoscroll(Autoscroll::newest(), cx);
21190 let position = self.selections.newest_display(cx).start;
21191 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21192 }
21193
21194 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21195 &self.inlay_hint_cache
21196 }
21197
21198 pub fn replay_insert_event(
21199 &mut self,
21200 text: &str,
21201 relative_utf16_range: Option<Range<isize>>,
21202 window: &mut Window,
21203 cx: &mut Context<Self>,
21204 ) {
21205 if !self.input_enabled {
21206 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21207 return;
21208 }
21209 if let Some(relative_utf16_range) = relative_utf16_range {
21210 let selections = self.selections.all::<OffsetUtf16>(cx);
21211 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21212 let new_ranges = selections.into_iter().map(|range| {
21213 let start = OffsetUtf16(
21214 range
21215 .head()
21216 .0
21217 .saturating_add_signed(relative_utf16_range.start),
21218 );
21219 let end = OffsetUtf16(
21220 range
21221 .head()
21222 .0
21223 .saturating_add_signed(relative_utf16_range.end),
21224 );
21225 start..end
21226 });
21227 s.select_ranges(new_ranges);
21228 });
21229 }
21230
21231 self.handle_input(text, window, cx);
21232 }
21233
21234 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21235 let Some(provider) = self.semantics_provider.as_ref() else {
21236 return false;
21237 };
21238
21239 let mut supports = false;
21240 self.buffer().update(cx, |this, cx| {
21241 this.for_each_buffer(|buffer| {
21242 supports |= provider.supports_inlay_hints(buffer, cx);
21243 });
21244 });
21245
21246 supports
21247 }
21248
21249 pub fn is_focused(&self, window: &Window) -> bool {
21250 self.focus_handle.is_focused(window)
21251 }
21252
21253 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21254 cx.emit(EditorEvent::Focused);
21255
21256 if let Some(descendant) = self
21257 .last_focused_descendant
21258 .take()
21259 .and_then(|descendant| descendant.upgrade())
21260 {
21261 window.focus(&descendant);
21262 } else {
21263 if let Some(blame) = self.blame.as_ref() {
21264 blame.update(cx, GitBlame::focus)
21265 }
21266
21267 self.blink_manager.update(cx, BlinkManager::enable);
21268 self.show_cursor_names(window, cx);
21269 self.buffer.update(cx, |buffer, cx| {
21270 buffer.finalize_last_transaction(cx);
21271 if self.leader_id.is_none() {
21272 buffer.set_active_selections(
21273 &self.selections.disjoint_anchors(),
21274 self.selections.line_mode,
21275 self.cursor_shape,
21276 cx,
21277 );
21278 }
21279 });
21280 }
21281 }
21282
21283 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21284 cx.emit(EditorEvent::FocusedIn)
21285 }
21286
21287 fn handle_focus_out(
21288 &mut self,
21289 event: FocusOutEvent,
21290 _window: &mut Window,
21291 cx: &mut Context<Self>,
21292 ) {
21293 if event.blurred != self.focus_handle {
21294 self.last_focused_descendant = Some(event.blurred);
21295 }
21296 self.selection_drag_state = SelectionDragState::None;
21297 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21298 }
21299
21300 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21301 self.blink_manager.update(cx, BlinkManager::disable);
21302 self.buffer
21303 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21304
21305 if let Some(blame) = self.blame.as_ref() {
21306 blame.update(cx, GitBlame::blur)
21307 }
21308 if !self.hover_state.focused(window, cx) {
21309 hide_hover(self, cx);
21310 }
21311 if !self
21312 .context_menu
21313 .borrow()
21314 .as_ref()
21315 .is_some_and(|context_menu| context_menu.focused(window, cx))
21316 {
21317 self.hide_context_menu(window, cx);
21318 }
21319 self.discard_edit_prediction(false, cx);
21320 cx.emit(EditorEvent::Blurred);
21321 cx.notify();
21322 }
21323
21324 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21325 let mut pending: String = window
21326 .pending_input_keystrokes()
21327 .into_iter()
21328 .flatten()
21329 .filter_map(|keystroke| {
21330 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21331 keystroke.key_char.clone()
21332 } else {
21333 None
21334 }
21335 })
21336 .collect();
21337
21338 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21339 pending = "".to_string();
21340 }
21341
21342 let existing_pending = self
21343 .text_highlights::<PendingInput>(cx)
21344 .map(|(_, ranges)| ranges.to_vec());
21345 if existing_pending.is_none() && pending.is_empty() {
21346 return;
21347 }
21348 let transaction =
21349 self.transact(window, cx, |this, window, cx| {
21350 let selections = this.selections.all::<usize>(cx);
21351 let edits = selections
21352 .iter()
21353 .map(|selection| (selection.end..selection.end, pending.clone()));
21354 this.edit(edits, cx);
21355 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21356 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21357 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21358 }));
21359 });
21360 if let Some(existing_ranges) = existing_pending {
21361 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21362 this.edit(edits, cx);
21363 }
21364 });
21365
21366 let snapshot = self.snapshot(window, cx);
21367 let ranges = self
21368 .selections
21369 .all::<usize>(cx)
21370 .into_iter()
21371 .map(|selection| {
21372 snapshot.buffer_snapshot.anchor_after(selection.end)
21373 ..snapshot
21374 .buffer_snapshot
21375 .anchor_before(selection.end + pending.len())
21376 })
21377 .collect();
21378
21379 if pending.is_empty() {
21380 self.clear_highlights::<PendingInput>(cx);
21381 } else {
21382 self.highlight_text::<PendingInput>(
21383 ranges,
21384 HighlightStyle {
21385 underline: Some(UnderlineStyle {
21386 thickness: px(1.),
21387 color: None,
21388 wavy: false,
21389 }),
21390 ..Default::default()
21391 },
21392 cx,
21393 );
21394 }
21395
21396 self.ime_transaction = self.ime_transaction.or(transaction);
21397 if let Some(transaction) = self.ime_transaction {
21398 self.buffer.update(cx, |buffer, cx| {
21399 buffer.group_until_transaction(transaction, cx);
21400 });
21401 }
21402
21403 if self.text_highlights::<PendingInput>(cx).is_none() {
21404 self.ime_transaction.take();
21405 }
21406 }
21407
21408 pub fn register_action_renderer(
21409 &mut self,
21410 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21411 ) -> Subscription {
21412 let id = self.next_editor_action_id.post_inc();
21413 self.editor_actions
21414 .borrow_mut()
21415 .insert(id, Box::new(listener));
21416
21417 let editor_actions = self.editor_actions.clone();
21418 Subscription::new(move || {
21419 editor_actions.borrow_mut().remove(&id);
21420 })
21421 }
21422
21423 pub fn register_action<A: Action>(
21424 &mut self,
21425 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21426 ) -> Subscription {
21427 let id = self.next_editor_action_id.post_inc();
21428 let listener = Arc::new(listener);
21429 self.editor_actions.borrow_mut().insert(
21430 id,
21431 Box::new(move |_, window, _| {
21432 let listener = listener.clone();
21433 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21434 let action = action.downcast_ref().unwrap();
21435 if phase == DispatchPhase::Bubble {
21436 listener(action, window, cx)
21437 }
21438 })
21439 }),
21440 );
21441
21442 let editor_actions = self.editor_actions.clone();
21443 Subscription::new(move || {
21444 editor_actions.borrow_mut().remove(&id);
21445 })
21446 }
21447
21448 pub fn file_header_size(&self) -> u32 {
21449 FILE_HEADER_HEIGHT
21450 }
21451
21452 pub fn restore(
21453 &mut self,
21454 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21455 window: &mut Window,
21456 cx: &mut Context<Self>,
21457 ) {
21458 let workspace = self.workspace();
21459 let project = self.project();
21460 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21461 let mut tasks = Vec::new();
21462 for (buffer_id, changes) in revert_changes {
21463 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21464 buffer.update(cx, |buffer, cx| {
21465 buffer.edit(
21466 changes
21467 .into_iter()
21468 .map(|(range, text)| (range, text.to_string())),
21469 None,
21470 cx,
21471 );
21472 });
21473
21474 if let Some(project) =
21475 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21476 {
21477 project.update(cx, |project, cx| {
21478 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21479 })
21480 }
21481 }
21482 }
21483 tasks
21484 });
21485 cx.spawn_in(window, async move |_, cx| {
21486 for (buffer, task) in save_tasks {
21487 let result = task.await;
21488 if result.is_err() {
21489 let Some(path) = buffer
21490 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21491 .ok()
21492 else {
21493 continue;
21494 };
21495 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21496 let Some(task) = cx
21497 .update_window_entity(workspace, |workspace, window, cx| {
21498 workspace
21499 .open_path_preview(path, None, false, false, false, window, cx)
21500 })
21501 .ok()
21502 else {
21503 continue;
21504 };
21505 task.await.log_err();
21506 }
21507 }
21508 }
21509 })
21510 .detach();
21511 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21512 selections.refresh()
21513 });
21514 }
21515
21516 pub fn to_pixel_point(
21517 &self,
21518 source: multi_buffer::Anchor,
21519 editor_snapshot: &EditorSnapshot,
21520 window: &mut Window,
21521 ) -> Option<gpui::Point<Pixels>> {
21522 let source_point = source.to_display_point(editor_snapshot);
21523 self.display_to_pixel_point(source_point, editor_snapshot, window)
21524 }
21525
21526 pub fn display_to_pixel_point(
21527 &self,
21528 source: DisplayPoint,
21529 editor_snapshot: &EditorSnapshot,
21530 window: &mut Window,
21531 ) -> Option<gpui::Point<Pixels>> {
21532 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21533 let text_layout_details = self.text_layout_details(window);
21534 let scroll_top = text_layout_details
21535 .scroll_anchor
21536 .scroll_position(editor_snapshot)
21537 .y;
21538
21539 if source.row().as_f32() < scroll_top.floor() {
21540 return None;
21541 }
21542 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21543 let source_y = line_height * (source.row().as_f32() - scroll_top);
21544 Some(gpui::Point::new(source_x, source_y))
21545 }
21546
21547 pub fn has_visible_completions_menu(&self) -> bool {
21548 !self.edit_prediction_preview_is_active()
21549 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21550 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21551 })
21552 }
21553
21554 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21555 if self.mode.is_minimap() {
21556 return;
21557 }
21558 self.addons
21559 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21560 }
21561
21562 pub fn unregister_addon<T: Addon>(&mut self) {
21563 self.addons.remove(&std::any::TypeId::of::<T>());
21564 }
21565
21566 pub fn addon<T: Addon>(&self) -> Option<&T> {
21567 let type_id = std::any::TypeId::of::<T>();
21568 self.addons
21569 .get(&type_id)
21570 .and_then(|item| item.to_any().downcast_ref::<T>())
21571 }
21572
21573 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21574 let type_id = std::any::TypeId::of::<T>();
21575 self.addons
21576 .get_mut(&type_id)
21577 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21578 }
21579
21580 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21581 let text_layout_details = self.text_layout_details(window);
21582 let style = &text_layout_details.editor_style;
21583 let font_id = window.text_system().resolve_font(&style.text.font());
21584 let font_size = style.text.font_size.to_pixels(window.rem_size());
21585 let line_height = style.text.line_height_in_pixels(window.rem_size());
21586 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21587 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21588
21589 CharacterDimensions {
21590 em_width,
21591 em_advance,
21592 line_height,
21593 }
21594 }
21595
21596 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21597 self.load_diff_task.clone()
21598 }
21599
21600 fn read_metadata_from_db(
21601 &mut self,
21602 item_id: u64,
21603 workspace_id: WorkspaceId,
21604 window: &mut Window,
21605 cx: &mut Context<Editor>,
21606 ) {
21607 if self.is_singleton(cx)
21608 && !self.mode.is_minimap()
21609 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21610 {
21611 let buffer_snapshot = OnceCell::new();
21612
21613 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21614 && !folds.is_empty()
21615 {
21616 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21617 self.fold_ranges(
21618 folds
21619 .into_iter()
21620 .map(|(start, end)| {
21621 snapshot.clip_offset(start, Bias::Left)
21622 ..snapshot.clip_offset(end, Bias::Right)
21623 })
21624 .collect(),
21625 false,
21626 window,
21627 cx,
21628 );
21629 }
21630
21631 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21632 && !selections.is_empty()
21633 {
21634 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21635 // skip adding the initial selection to selection history
21636 self.selection_history.mode = SelectionHistoryMode::Skipping;
21637 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21638 s.select_ranges(selections.into_iter().map(|(start, end)| {
21639 snapshot.clip_offset(start, Bias::Left)
21640 ..snapshot.clip_offset(end, Bias::Right)
21641 }));
21642 });
21643 self.selection_history.mode = SelectionHistoryMode::Normal;
21644 };
21645 }
21646
21647 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21648 }
21649
21650 fn update_lsp_data(
21651 &mut self,
21652 ignore_cache: bool,
21653 for_buffer: Option<BufferId>,
21654 window: &mut Window,
21655 cx: &mut Context<'_, Self>,
21656 ) {
21657 self.pull_diagnostics(for_buffer, window, cx);
21658 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21659 }
21660}
21661
21662fn vim_enabled(cx: &App) -> bool {
21663 cx.global::<SettingsStore>()
21664 .raw_user_settings()
21665 .get("vim_mode")
21666 == Some(&serde_json::Value::Bool(true))
21667}
21668
21669fn process_completion_for_edit(
21670 completion: &Completion,
21671 intent: CompletionIntent,
21672 buffer: &Entity<Buffer>,
21673 cursor_position: &text::Anchor,
21674 cx: &mut Context<Editor>,
21675) -> CompletionEdit {
21676 let buffer = buffer.read(cx);
21677 let buffer_snapshot = buffer.snapshot();
21678 let (snippet, new_text) = if completion.is_snippet() {
21679 // Workaround for typescript language server issues so that methods don't expand within
21680 // strings and functions with type expressions. The previous point is used because the query
21681 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21682 let mut snippet_source = completion.new_text.clone();
21683 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21684 previous_point.column = previous_point.column.saturating_sub(1);
21685 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21686 && scope.prefers_label_for_snippet_in_completion()
21687 && let Some(label) = completion.label()
21688 && matches!(
21689 completion.kind(),
21690 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21691 )
21692 {
21693 snippet_source = label;
21694 }
21695 match Snippet::parse(&snippet_source).log_err() {
21696 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21697 None => (None, completion.new_text.clone()),
21698 }
21699 } else {
21700 (None, completion.new_text.clone())
21701 };
21702
21703 let mut range_to_replace = {
21704 let replace_range = &completion.replace_range;
21705 if let CompletionSource::Lsp {
21706 insert_range: Some(insert_range),
21707 ..
21708 } = &completion.source
21709 {
21710 debug_assert_eq!(
21711 insert_range.start, replace_range.start,
21712 "insert_range and replace_range should start at the same position"
21713 );
21714 debug_assert!(
21715 insert_range
21716 .start
21717 .cmp(cursor_position, &buffer_snapshot)
21718 .is_le(),
21719 "insert_range should start before or at cursor position"
21720 );
21721 debug_assert!(
21722 replace_range
21723 .start
21724 .cmp(cursor_position, &buffer_snapshot)
21725 .is_le(),
21726 "replace_range should start before or at cursor position"
21727 );
21728
21729 let should_replace = match intent {
21730 CompletionIntent::CompleteWithInsert => false,
21731 CompletionIntent::CompleteWithReplace => true,
21732 CompletionIntent::Complete | CompletionIntent::Compose => {
21733 let insert_mode =
21734 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21735 .completions
21736 .lsp_insert_mode;
21737 match insert_mode {
21738 LspInsertMode::Insert => false,
21739 LspInsertMode::Replace => true,
21740 LspInsertMode::ReplaceSubsequence => {
21741 let mut text_to_replace = buffer.chars_for_range(
21742 buffer.anchor_before(replace_range.start)
21743 ..buffer.anchor_after(replace_range.end),
21744 );
21745 let mut current_needle = text_to_replace.next();
21746 for haystack_ch in completion.label.text.chars() {
21747 if let Some(needle_ch) = current_needle
21748 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21749 {
21750 current_needle = text_to_replace.next();
21751 }
21752 }
21753 current_needle.is_none()
21754 }
21755 LspInsertMode::ReplaceSuffix => {
21756 if replace_range
21757 .end
21758 .cmp(cursor_position, &buffer_snapshot)
21759 .is_gt()
21760 {
21761 let range_after_cursor = *cursor_position..replace_range.end;
21762 let text_after_cursor = buffer
21763 .text_for_range(
21764 buffer.anchor_before(range_after_cursor.start)
21765 ..buffer.anchor_after(range_after_cursor.end),
21766 )
21767 .collect::<String>()
21768 .to_ascii_lowercase();
21769 completion
21770 .label
21771 .text
21772 .to_ascii_lowercase()
21773 .ends_with(&text_after_cursor)
21774 } else {
21775 true
21776 }
21777 }
21778 }
21779 }
21780 };
21781
21782 if should_replace {
21783 replace_range.clone()
21784 } else {
21785 insert_range.clone()
21786 }
21787 } else {
21788 replace_range.clone()
21789 }
21790 };
21791
21792 if range_to_replace
21793 .end
21794 .cmp(cursor_position, &buffer_snapshot)
21795 .is_lt()
21796 {
21797 range_to_replace.end = *cursor_position;
21798 }
21799
21800 CompletionEdit {
21801 new_text,
21802 replace_range: range_to_replace.to_offset(buffer),
21803 snippet,
21804 }
21805}
21806
21807struct CompletionEdit {
21808 new_text: String,
21809 replace_range: Range<usize>,
21810 snippet: Option<Snippet>,
21811}
21812
21813fn insert_extra_newline_brackets(
21814 buffer: &MultiBufferSnapshot,
21815 range: Range<usize>,
21816 language: &language::LanguageScope,
21817) -> bool {
21818 let leading_whitespace_len = buffer
21819 .reversed_chars_at(range.start)
21820 .take_while(|c| c.is_whitespace() && *c != '\n')
21821 .map(|c| c.len_utf8())
21822 .sum::<usize>();
21823 let trailing_whitespace_len = buffer
21824 .chars_at(range.end)
21825 .take_while(|c| c.is_whitespace() && *c != '\n')
21826 .map(|c| c.len_utf8())
21827 .sum::<usize>();
21828 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21829
21830 language.brackets().any(|(pair, enabled)| {
21831 let pair_start = pair.start.trim_end();
21832 let pair_end = pair.end.trim_start();
21833
21834 enabled
21835 && pair.newline
21836 && buffer.contains_str_at(range.end, pair_end)
21837 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21838 })
21839}
21840
21841fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21842 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21843 [(buffer, range, _)] => (*buffer, range.clone()),
21844 _ => return false,
21845 };
21846 let pair = {
21847 let mut result: Option<BracketMatch> = None;
21848
21849 for pair in buffer
21850 .all_bracket_ranges(range.clone())
21851 .filter(move |pair| {
21852 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21853 })
21854 {
21855 let len = pair.close_range.end - pair.open_range.start;
21856
21857 if let Some(existing) = &result {
21858 let existing_len = existing.close_range.end - existing.open_range.start;
21859 if len > existing_len {
21860 continue;
21861 }
21862 }
21863
21864 result = Some(pair);
21865 }
21866
21867 result
21868 };
21869 let Some(pair) = pair else {
21870 return false;
21871 };
21872 pair.newline_only
21873 && buffer
21874 .chars_for_range(pair.open_range.end..range.start)
21875 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21876 .all(|c| c.is_whitespace() && c != '\n')
21877}
21878
21879fn update_uncommitted_diff_for_buffer(
21880 editor: Entity<Editor>,
21881 project: &Entity<Project>,
21882 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21883 buffer: Entity<MultiBuffer>,
21884 cx: &mut App,
21885) -> Task<()> {
21886 let mut tasks = Vec::new();
21887 project.update(cx, |project, cx| {
21888 for buffer in buffers {
21889 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21890 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21891 }
21892 }
21893 });
21894 cx.spawn(async move |cx| {
21895 let diffs = future::join_all(tasks).await;
21896 if editor
21897 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21898 .unwrap_or(false)
21899 {
21900 return;
21901 }
21902
21903 buffer
21904 .update(cx, |buffer, cx| {
21905 for diff in diffs.into_iter().flatten() {
21906 buffer.add_diff(diff, cx);
21907 }
21908 })
21909 .ok();
21910 })
21911}
21912
21913fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21914 let tab_size = tab_size.get() as usize;
21915 let mut width = offset;
21916
21917 for ch in text.chars() {
21918 width += if ch == '\t' {
21919 tab_size - (width % tab_size)
21920 } else {
21921 1
21922 };
21923 }
21924
21925 width - offset
21926}
21927
21928#[cfg(test)]
21929mod tests {
21930 use super::*;
21931
21932 #[test]
21933 fn test_string_size_with_expanded_tabs() {
21934 let nz = |val| NonZeroU32::new(val).unwrap();
21935 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21936 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21937 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21938 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21939 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21940 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21941 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21942 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21943 }
21944}
21945
21946/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21947struct WordBreakingTokenizer<'a> {
21948 input: &'a str,
21949}
21950
21951impl<'a> WordBreakingTokenizer<'a> {
21952 fn new(input: &'a str) -> Self {
21953 Self { input }
21954 }
21955}
21956
21957fn is_char_ideographic(ch: char) -> bool {
21958 use unicode_script::Script::*;
21959 use unicode_script::UnicodeScript;
21960 matches!(ch.script(), Han | Tangut | Yi)
21961}
21962
21963fn is_grapheme_ideographic(text: &str) -> bool {
21964 text.chars().any(is_char_ideographic)
21965}
21966
21967fn is_grapheme_whitespace(text: &str) -> bool {
21968 text.chars().any(|x| x.is_whitespace())
21969}
21970
21971fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21972 text.chars()
21973 .next()
21974 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21975}
21976
21977#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21978enum WordBreakToken<'a> {
21979 Word { token: &'a str, grapheme_len: usize },
21980 InlineWhitespace { token: &'a str, grapheme_len: usize },
21981 Newline,
21982}
21983
21984impl<'a> Iterator for WordBreakingTokenizer<'a> {
21985 /// Yields a span, the count of graphemes in the token, and whether it was
21986 /// whitespace. Note that it also breaks at word boundaries.
21987 type Item = WordBreakToken<'a>;
21988
21989 fn next(&mut self) -> Option<Self::Item> {
21990 use unicode_segmentation::UnicodeSegmentation;
21991 if self.input.is_empty() {
21992 return None;
21993 }
21994
21995 let mut iter = self.input.graphemes(true).peekable();
21996 let mut offset = 0;
21997 let mut grapheme_len = 0;
21998 if let Some(first_grapheme) = iter.next() {
21999 let is_newline = first_grapheme == "\n";
22000 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22001 offset += first_grapheme.len();
22002 grapheme_len += 1;
22003 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22004 if let Some(grapheme) = iter.peek().copied()
22005 && should_stay_with_preceding_ideograph(grapheme)
22006 {
22007 offset += grapheme.len();
22008 grapheme_len += 1;
22009 }
22010 } else {
22011 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22012 let mut next_word_bound = words.peek().copied();
22013 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22014 next_word_bound = words.next();
22015 }
22016 while let Some(grapheme) = iter.peek().copied() {
22017 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22018 break;
22019 };
22020 if is_grapheme_whitespace(grapheme) != is_whitespace
22021 || (grapheme == "\n") != is_newline
22022 {
22023 break;
22024 };
22025 offset += grapheme.len();
22026 grapheme_len += 1;
22027 iter.next();
22028 }
22029 }
22030 let token = &self.input[..offset];
22031 self.input = &self.input[offset..];
22032 if token == "\n" {
22033 Some(WordBreakToken::Newline)
22034 } else if is_whitespace {
22035 Some(WordBreakToken::InlineWhitespace {
22036 token,
22037 grapheme_len,
22038 })
22039 } else {
22040 Some(WordBreakToken::Word {
22041 token,
22042 grapheme_len,
22043 })
22044 }
22045 } else {
22046 None
22047 }
22048 }
22049}
22050
22051#[test]
22052fn test_word_breaking_tokenizer() {
22053 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22054 ("", &[]),
22055 (" ", &[whitespace(" ", 2)]),
22056 ("Ʒ", &[word("Ʒ", 1)]),
22057 ("Ǽ", &[word("Ǽ", 1)]),
22058 ("⋑", &[word("⋑", 1)]),
22059 ("⋑⋑", &[word("⋑⋑", 2)]),
22060 (
22061 "原理,进而",
22062 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22063 ),
22064 (
22065 "hello world",
22066 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22067 ),
22068 (
22069 "hello, world",
22070 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22071 ),
22072 (
22073 " hello world",
22074 &[
22075 whitespace(" ", 2),
22076 word("hello", 5),
22077 whitespace(" ", 1),
22078 word("world", 5),
22079 ],
22080 ),
22081 (
22082 "这是什么 \n 钢笔",
22083 &[
22084 word("这", 1),
22085 word("是", 1),
22086 word("什", 1),
22087 word("么", 1),
22088 whitespace(" ", 1),
22089 newline(),
22090 whitespace(" ", 1),
22091 word("钢", 1),
22092 word("笔", 1),
22093 ],
22094 ),
22095 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22096 ];
22097
22098 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22099 WordBreakToken::Word {
22100 token,
22101 grapheme_len,
22102 }
22103 }
22104
22105 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22106 WordBreakToken::InlineWhitespace {
22107 token,
22108 grapheme_len,
22109 }
22110 }
22111
22112 fn newline() -> WordBreakToken<'static> {
22113 WordBreakToken::Newline
22114 }
22115
22116 for (input, result) in tests {
22117 assert_eq!(
22118 WordBreakingTokenizer::new(input)
22119 .collect::<Vec<_>>()
22120 .as_slice(),
22121 *result,
22122 );
22123 }
22124}
22125
22126fn wrap_with_prefix(
22127 first_line_prefix: String,
22128 subsequent_lines_prefix: String,
22129 unwrapped_text: String,
22130 wrap_column: usize,
22131 tab_size: NonZeroU32,
22132 preserve_existing_whitespace: bool,
22133) -> String {
22134 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22135 let subsequent_lines_prefix_len =
22136 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22137 let mut wrapped_text = String::new();
22138 let mut current_line = first_line_prefix;
22139 let mut is_first_line = true;
22140
22141 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22142 let mut current_line_len = first_line_prefix_len;
22143 let mut in_whitespace = false;
22144 for token in tokenizer {
22145 let have_preceding_whitespace = in_whitespace;
22146 match token {
22147 WordBreakToken::Word {
22148 token,
22149 grapheme_len,
22150 } => {
22151 in_whitespace = false;
22152 let current_prefix_len = if is_first_line {
22153 first_line_prefix_len
22154 } else {
22155 subsequent_lines_prefix_len
22156 };
22157 if current_line_len + grapheme_len > wrap_column
22158 && current_line_len != current_prefix_len
22159 {
22160 wrapped_text.push_str(current_line.trim_end());
22161 wrapped_text.push('\n');
22162 is_first_line = false;
22163 current_line = subsequent_lines_prefix.clone();
22164 current_line_len = subsequent_lines_prefix_len;
22165 }
22166 current_line.push_str(token);
22167 current_line_len += grapheme_len;
22168 }
22169 WordBreakToken::InlineWhitespace {
22170 mut token,
22171 mut grapheme_len,
22172 } => {
22173 in_whitespace = true;
22174 if have_preceding_whitespace && !preserve_existing_whitespace {
22175 continue;
22176 }
22177 if !preserve_existing_whitespace {
22178 token = " ";
22179 grapheme_len = 1;
22180 }
22181 let current_prefix_len = if is_first_line {
22182 first_line_prefix_len
22183 } else {
22184 subsequent_lines_prefix_len
22185 };
22186 if current_line_len + grapheme_len > wrap_column {
22187 wrapped_text.push_str(current_line.trim_end());
22188 wrapped_text.push('\n');
22189 is_first_line = false;
22190 current_line = subsequent_lines_prefix.clone();
22191 current_line_len = subsequent_lines_prefix_len;
22192 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22193 current_line.push_str(token);
22194 current_line_len += grapheme_len;
22195 }
22196 }
22197 WordBreakToken::Newline => {
22198 in_whitespace = true;
22199 let current_prefix_len = if is_first_line {
22200 first_line_prefix_len
22201 } else {
22202 subsequent_lines_prefix_len
22203 };
22204 if preserve_existing_whitespace {
22205 wrapped_text.push_str(current_line.trim_end());
22206 wrapped_text.push('\n');
22207 is_first_line = false;
22208 current_line = subsequent_lines_prefix.clone();
22209 current_line_len = subsequent_lines_prefix_len;
22210 } else if have_preceding_whitespace {
22211 continue;
22212 } else if current_line_len + 1 > wrap_column
22213 && current_line_len != current_prefix_len
22214 {
22215 wrapped_text.push_str(current_line.trim_end());
22216 wrapped_text.push('\n');
22217 is_first_line = false;
22218 current_line = subsequent_lines_prefix.clone();
22219 current_line_len = subsequent_lines_prefix_len;
22220 } else if current_line_len != current_prefix_len {
22221 current_line.push(' ');
22222 current_line_len += 1;
22223 }
22224 }
22225 }
22226 }
22227
22228 if !current_line.is_empty() {
22229 wrapped_text.push_str(¤t_line);
22230 }
22231 wrapped_text
22232}
22233
22234#[test]
22235fn test_wrap_with_prefix() {
22236 assert_eq!(
22237 wrap_with_prefix(
22238 "# ".to_string(),
22239 "# ".to_string(),
22240 "abcdefg".to_string(),
22241 4,
22242 NonZeroU32::new(4).unwrap(),
22243 false,
22244 ),
22245 "# abcdefg"
22246 );
22247 assert_eq!(
22248 wrap_with_prefix(
22249 "".to_string(),
22250 "".to_string(),
22251 "\thello world".to_string(),
22252 8,
22253 NonZeroU32::new(4).unwrap(),
22254 false,
22255 ),
22256 "hello\nworld"
22257 );
22258 assert_eq!(
22259 wrap_with_prefix(
22260 "// ".to_string(),
22261 "// ".to_string(),
22262 "xx \nyy zz aa bb cc".to_string(),
22263 12,
22264 NonZeroU32::new(4).unwrap(),
22265 false,
22266 ),
22267 "// xx yy zz\n// aa bb cc"
22268 );
22269 assert_eq!(
22270 wrap_with_prefix(
22271 String::new(),
22272 String::new(),
22273 "这是什么 \n 钢笔".to_string(),
22274 3,
22275 NonZeroU32::new(4).unwrap(),
22276 false,
22277 ),
22278 "这是什\n么 钢\n笔"
22279 );
22280}
22281
22282pub trait CollaborationHub {
22283 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22284 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22285 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22286}
22287
22288impl CollaborationHub for Entity<Project> {
22289 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22290 self.read(cx).collaborators()
22291 }
22292
22293 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22294 self.read(cx).user_store().read(cx).participant_indices()
22295 }
22296
22297 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22298 let this = self.read(cx);
22299 let user_ids = this.collaborators().values().map(|c| c.user_id);
22300 this.user_store().read(cx).participant_names(user_ids, cx)
22301 }
22302}
22303
22304pub trait SemanticsProvider {
22305 fn hover(
22306 &self,
22307 buffer: &Entity<Buffer>,
22308 position: text::Anchor,
22309 cx: &mut App,
22310 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22311
22312 fn inline_values(
22313 &self,
22314 buffer_handle: Entity<Buffer>,
22315 range: Range<text::Anchor>,
22316 cx: &mut App,
22317 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22318
22319 fn inlay_hints(
22320 &self,
22321 buffer_handle: Entity<Buffer>,
22322 range: Range<text::Anchor>,
22323 cx: &mut App,
22324 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22325
22326 fn resolve_inlay_hint(
22327 &self,
22328 hint: InlayHint,
22329 buffer_handle: Entity<Buffer>,
22330 server_id: LanguageServerId,
22331 cx: &mut App,
22332 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22333
22334 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22335
22336 fn document_highlights(
22337 &self,
22338 buffer: &Entity<Buffer>,
22339 position: text::Anchor,
22340 cx: &mut App,
22341 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22342
22343 fn definitions(
22344 &self,
22345 buffer: &Entity<Buffer>,
22346 position: text::Anchor,
22347 kind: GotoDefinitionKind,
22348 cx: &mut App,
22349 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22350
22351 fn range_for_rename(
22352 &self,
22353 buffer: &Entity<Buffer>,
22354 position: text::Anchor,
22355 cx: &mut App,
22356 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22357
22358 fn perform_rename(
22359 &self,
22360 buffer: &Entity<Buffer>,
22361 position: text::Anchor,
22362 new_name: String,
22363 cx: &mut App,
22364 ) -> Option<Task<Result<ProjectTransaction>>>;
22365}
22366
22367pub trait CompletionProvider {
22368 fn completions(
22369 &self,
22370 excerpt_id: ExcerptId,
22371 buffer: &Entity<Buffer>,
22372 buffer_position: text::Anchor,
22373 trigger: CompletionContext,
22374 window: &mut Window,
22375 cx: &mut Context<Editor>,
22376 ) -> Task<Result<Vec<CompletionResponse>>>;
22377
22378 fn resolve_completions(
22379 &self,
22380 _buffer: Entity<Buffer>,
22381 _completion_indices: Vec<usize>,
22382 _completions: Rc<RefCell<Box<[Completion]>>>,
22383 _cx: &mut Context<Editor>,
22384 ) -> Task<Result<bool>> {
22385 Task::ready(Ok(false))
22386 }
22387
22388 fn apply_additional_edits_for_completion(
22389 &self,
22390 _buffer: Entity<Buffer>,
22391 _completions: Rc<RefCell<Box<[Completion]>>>,
22392 _completion_index: usize,
22393 _push_to_history: bool,
22394 _cx: &mut Context<Editor>,
22395 ) -> Task<Result<Option<language::Transaction>>> {
22396 Task::ready(Ok(None))
22397 }
22398
22399 fn is_completion_trigger(
22400 &self,
22401 buffer: &Entity<Buffer>,
22402 position: language::Anchor,
22403 text: &str,
22404 trigger_in_words: bool,
22405 menu_is_open: bool,
22406 cx: &mut Context<Editor>,
22407 ) -> bool;
22408
22409 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22410
22411 fn sort_completions(&self) -> bool {
22412 true
22413 }
22414
22415 fn filter_completions(&self) -> bool {
22416 true
22417 }
22418}
22419
22420pub trait CodeActionProvider {
22421 fn id(&self) -> Arc<str>;
22422
22423 fn code_actions(
22424 &self,
22425 buffer: &Entity<Buffer>,
22426 range: Range<text::Anchor>,
22427 window: &mut Window,
22428 cx: &mut App,
22429 ) -> Task<Result<Vec<CodeAction>>>;
22430
22431 fn apply_code_action(
22432 &self,
22433 buffer_handle: Entity<Buffer>,
22434 action: CodeAction,
22435 excerpt_id: ExcerptId,
22436 push_to_history: bool,
22437 window: &mut Window,
22438 cx: &mut App,
22439 ) -> Task<Result<ProjectTransaction>>;
22440}
22441
22442impl CodeActionProvider for Entity<Project> {
22443 fn id(&self) -> Arc<str> {
22444 "project".into()
22445 }
22446
22447 fn code_actions(
22448 &self,
22449 buffer: &Entity<Buffer>,
22450 range: Range<text::Anchor>,
22451 _window: &mut Window,
22452 cx: &mut App,
22453 ) -> Task<Result<Vec<CodeAction>>> {
22454 self.update(cx, |project, cx| {
22455 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22456 let code_actions = project.code_actions(buffer, range, None, cx);
22457 cx.background_spawn(async move {
22458 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22459 Ok(code_lens_actions
22460 .context("code lens fetch")?
22461 .into_iter()
22462 .flatten()
22463 .chain(
22464 code_actions
22465 .context("code action fetch")?
22466 .into_iter()
22467 .flatten(),
22468 )
22469 .collect())
22470 })
22471 })
22472 }
22473
22474 fn apply_code_action(
22475 &self,
22476 buffer_handle: Entity<Buffer>,
22477 action: CodeAction,
22478 _excerpt_id: ExcerptId,
22479 push_to_history: bool,
22480 _window: &mut Window,
22481 cx: &mut App,
22482 ) -> Task<Result<ProjectTransaction>> {
22483 self.update(cx, |project, cx| {
22484 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22485 })
22486 }
22487}
22488
22489fn snippet_completions(
22490 project: &Project,
22491 buffer: &Entity<Buffer>,
22492 buffer_position: text::Anchor,
22493 cx: &mut App,
22494) -> Task<Result<CompletionResponse>> {
22495 let languages = buffer.read(cx).languages_at(buffer_position);
22496 let snippet_store = project.snippets().read(cx);
22497
22498 let scopes: Vec<_> = languages
22499 .iter()
22500 .filter_map(|language| {
22501 let language_name = language.lsp_id();
22502 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22503
22504 if snippets.is_empty() {
22505 None
22506 } else {
22507 Some((language.default_scope(), snippets))
22508 }
22509 })
22510 .collect();
22511
22512 if scopes.is_empty() {
22513 return Task::ready(Ok(CompletionResponse {
22514 completions: vec![],
22515 display_options: CompletionDisplayOptions::default(),
22516 is_incomplete: false,
22517 }));
22518 }
22519
22520 let snapshot = buffer.read(cx).text_snapshot();
22521 let chars: String = snapshot
22522 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22523 .collect();
22524 let executor = cx.background_executor().clone();
22525
22526 cx.background_spawn(async move {
22527 let mut is_incomplete = false;
22528 let mut completions: Vec<Completion> = Vec::new();
22529 for (scope, snippets) in scopes.into_iter() {
22530 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22531 let mut last_word = chars
22532 .chars()
22533 .take_while(|c| classifier.is_word(*c))
22534 .collect::<String>();
22535 last_word = last_word.chars().rev().collect();
22536
22537 if last_word.is_empty() {
22538 return Ok(CompletionResponse {
22539 completions: vec![],
22540 display_options: CompletionDisplayOptions::default(),
22541 is_incomplete: true,
22542 });
22543 }
22544
22545 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22546 let to_lsp = |point: &text::Anchor| {
22547 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22548 point_to_lsp(end)
22549 };
22550 let lsp_end = to_lsp(&buffer_position);
22551
22552 let candidates = snippets
22553 .iter()
22554 .enumerate()
22555 .flat_map(|(ix, snippet)| {
22556 snippet
22557 .prefix
22558 .iter()
22559 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22560 })
22561 .collect::<Vec<StringMatchCandidate>>();
22562
22563 const MAX_RESULTS: usize = 100;
22564 let mut matches = fuzzy::match_strings(
22565 &candidates,
22566 &last_word,
22567 last_word.chars().any(|c| c.is_uppercase()),
22568 true,
22569 MAX_RESULTS,
22570 &Default::default(),
22571 executor.clone(),
22572 )
22573 .await;
22574
22575 if matches.len() >= MAX_RESULTS {
22576 is_incomplete = true;
22577 }
22578
22579 // Remove all candidates where the query's start does not match the start of any word in the candidate
22580 if let Some(query_start) = last_word.chars().next() {
22581 matches.retain(|string_match| {
22582 split_words(&string_match.string).any(|word| {
22583 // Check that the first codepoint of the word as lowercase matches the first
22584 // codepoint of the query as lowercase
22585 word.chars()
22586 .flat_map(|codepoint| codepoint.to_lowercase())
22587 .zip(query_start.to_lowercase())
22588 .all(|(word_cp, query_cp)| word_cp == query_cp)
22589 })
22590 });
22591 }
22592
22593 let matched_strings = matches
22594 .into_iter()
22595 .map(|m| m.string)
22596 .collect::<HashSet<_>>();
22597
22598 completions.extend(snippets.iter().filter_map(|snippet| {
22599 let matching_prefix = snippet
22600 .prefix
22601 .iter()
22602 .find(|prefix| matched_strings.contains(*prefix))?;
22603 let start = as_offset - last_word.len();
22604 let start = snapshot.anchor_before(start);
22605 let range = start..buffer_position;
22606 let lsp_start = to_lsp(&start);
22607 let lsp_range = lsp::Range {
22608 start: lsp_start,
22609 end: lsp_end,
22610 };
22611 Some(Completion {
22612 replace_range: range,
22613 new_text: snippet.body.clone(),
22614 source: CompletionSource::Lsp {
22615 insert_range: None,
22616 server_id: LanguageServerId(usize::MAX),
22617 resolved: true,
22618 lsp_completion: Box::new(lsp::CompletionItem {
22619 label: snippet.prefix.first().unwrap().clone(),
22620 kind: Some(CompletionItemKind::SNIPPET),
22621 label_details: snippet.description.as_ref().map(|description| {
22622 lsp::CompletionItemLabelDetails {
22623 detail: Some(description.clone()),
22624 description: None,
22625 }
22626 }),
22627 insert_text_format: Some(InsertTextFormat::SNIPPET),
22628 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22629 lsp::InsertReplaceEdit {
22630 new_text: snippet.body.clone(),
22631 insert: lsp_range,
22632 replace: lsp_range,
22633 },
22634 )),
22635 filter_text: Some(snippet.body.clone()),
22636 sort_text: Some(char::MAX.to_string()),
22637 ..lsp::CompletionItem::default()
22638 }),
22639 lsp_defaults: None,
22640 },
22641 label: CodeLabel {
22642 text: matching_prefix.clone(),
22643 runs: Vec::new(),
22644 filter_range: 0..matching_prefix.len(),
22645 },
22646 icon_path: None,
22647 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22648 single_line: snippet.name.clone().into(),
22649 plain_text: snippet
22650 .description
22651 .clone()
22652 .map(|description| description.into()),
22653 }),
22654 insert_text_mode: None,
22655 confirm: None,
22656 })
22657 }))
22658 }
22659
22660 Ok(CompletionResponse {
22661 completions,
22662 display_options: CompletionDisplayOptions::default(),
22663 is_incomplete,
22664 })
22665 })
22666}
22667
22668impl CompletionProvider for Entity<Project> {
22669 fn completions(
22670 &self,
22671 _excerpt_id: ExcerptId,
22672 buffer: &Entity<Buffer>,
22673 buffer_position: text::Anchor,
22674 options: CompletionContext,
22675 _window: &mut Window,
22676 cx: &mut Context<Editor>,
22677 ) -> Task<Result<Vec<CompletionResponse>>> {
22678 self.update(cx, |project, cx| {
22679 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22680 let project_completions = project.completions(buffer, buffer_position, options, cx);
22681 cx.background_spawn(async move {
22682 let mut responses = project_completions.await?;
22683 let snippets = snippets.await?;
22684 if !snippets.completions.is_empty() {
22685 responses.push(snippets);
22686 }
22687 Ok(responses)
22688 })
22689 })
22690 }
22691
22692 fn resolve_completions(
22693 &self,
22694 buffer: Entity<Buffer>,
22695 completion_indices: Vec<usize>,
22696 completions: Rc<RefCell<Box<[Completion]>>>,
22697 cx: &mut Context<Editor>,
22698 ) -> Task<Result<bool>> {
22699 self.update(cx, |project, cx| {
22700 project.lsp_store().update(cx, |lsp_store, cx| {
22701 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22702 })
22703 })
22704 }
22705
22706 fn apply_additional_edits_for_completion(
22707 &self,
22708 buffer: Entity<Buffer>,
22709 completions: Rc<RefCell<Box<[Completion]>>>,
22710 completion_index: usize,
22711 push_to_history: bool,
22712 cx: &mut Context<Editor>,
22713 ) -> Task<Result<Option<language::Transaction>>> {
22714 self.update(cx, |project, cx| {
22715 project.lsp_store().update(cx, |lsp_store, cx| {
22716 lsp_store.apply_additional_edits_for_completion(
22717 buffer,
22718 completions,
22719 completion_index,
22720 push_to_history,
22721 cx,
22722 )
22723 })
22724 })
22725 }
22726
22727 fn is_completion_trigger(
22728 &self,
22729 buffer: &Entity<Buffer>,
22730 position: language::Anchor,
22731 text: &str,
22732 trigger_in_words: bool,
22733 menu_is_open: bool,
22734 cx: &mut Context<Editor>,
22735 ) -> bool {
22736 let mut chars = text.chars();
22737 let char = if let Some(char) = chars.next() {
22738 char
22739 } else {
22740 return false;
22741 };
22742 if chars.next().is_some() {
22743 return false;
22744 }
22745
22746 let buffer = buffer.read(cx);
22747 let snapshot = buffer.snapshot();
22748 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22749 return false;
22750 }
22751 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22752 if trigger_in_words && classifier.is_word(char) {
22753 return true;
22754 }
22755
22756 buffer.completion_triggers().contains(text)
22757 }
22758}
22759
22760impl SemanticsProvider for Entity<Project> {
22761 fn hover(
22762 &self,
22763 buffer: &Entity<Buffer>,
22764 position: text::Anchor,
22765 cx: &mut App,
22766 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22767 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22768 }
22769
22770 fn document_highlights(
22771 &self,
22772 buffer: &Entity<Buffer>,
22773 position: text::Anchor,
22774 cx: &mut App,
22775 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22776 Some(self.update(cx, |project, cx| {
22777 project.document_highlights(buffer, position, cx)
22778 }))
22779 }
22780
22781 fn definitions(
22782 &self,
22783 buffer: &Entity<Buffer>,
22784 position: text::Anchor,
22785 kind: GotoDefinitionKind,
22786 cx: &mut App,
22787 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22788 Some(self.update(cx, |project, cx| match kind {
22789 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22790 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22791 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22792 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22793 }))
22794 }
22795
22796 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22797 self.update(cx, |project, cx| {
22798 if project
22799 .active_debug_session(cx)
22800 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22801 {
22802 return true;
22803 }
22804
22805 buffer.update(cx, |buffer, cx| {
22806 project.any_language_server_supports_inlay_hints(buffer, cx)
22807 })
22808 })
22809 }
22810
22811 fn inline_values(
22812 &self,
22813 buffer_handle: Entity<Buffer>,
22814 range: Range<text::Anchor>,
22815 cx: &mut App,
22816 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22817 self.update(cx, |project, cx| {
22818 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22819
22820 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22821 })
22822 }
22823
22824 fn inlay_hints(
22825 &self,
22826 buffer_handle: Entity<Buffer>,
22827 range: Range<text::Anchor>,
22828 cx: &mut App,
22829 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22830 Some(self.update(cx, |project, cx| {
22831 project.inlay_hints(buffer_handle, range, cx)
22832 }))
22833 }
22834
22835 fn resolve_inlay_hint(
22836 &self,
22837 hint: InlayHint,
22838 buffer_handle: Entity<Buffer>,
22839 server_id: LanguageServerId,
22840 cx: &mut App,
22841 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22842 Some(self.update(cx, |project, cx| {
22843 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22844 }))
22845 }
22846
22847 fn range_for_rename(
22848 &self,
22849 buffer: &Entity<Buffer>,
22850 position: text::Anchor,
22851 cx: &mut App,
22852 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22853 Some(self.update(cx, |project, cx| {
22854 let buffer = buffer.clone();
22855 let task = project.prepare_rename(buffer.clone(), position, cx);
22856 cx.spawn(async move |_, cx| {
22857 Ok(match task.await? {
22858 PrepareRenameResponse::Success(range) => Some(range),
22859 PrepareRenameResponse::InvalidPosition => None,
22860 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22861 // Fallback on using TreeSitter info to determine identifier range
22862 buffer.read_with(cx, |buffer, _| {
22863 let snapshot = buffer.snapshot();
22864 let (range, kind) = snapshot.surrounding_word(position, false);
22865 if kind != Some(CharKind::Word) {
22866 return None;
22867 }
22868 Some(
22869 snapshot.anchor_before(range.start)
22870 ..snapshot.anchor_after(range.end),
22871 )
22872 })?
22873 }
22874 })
22875 })
22876 }))
22877 }
22878
22879 fn perform_rename(
22880 &self,
22881 buffer: &Entity<Buffer>,
22882 position: text::Anchor,
22883 new_name: String,
22884 cx: &mut App,
22885 ) -> Option<Task<Result<ProjectTransaction>>> {
22886 Some(self.update(cx, |project, cx| {
22887 project.perform_rename(buffer.clone(), position, new_name, cx)
22888 }))
22889 }
22890}
22891
22892fn inlay_hint_settings(
22893 location: Anchor,
22894 snapshot: &MultiBufferSnapshot,
22895 cx: &mut Context<Editor>,
22896) -> InlayHintSettings {
22897 let file = snapshot.file_at(location);
22898 let language = snapshot.language_at(location).map(|l| l.name());
22899 language_settings(language, file, cx).inlay_hints
22900}
22901
22902fn consume_contiguous_rows(
22903 contiguous_row_selections: &mut Vec<Selection<Point>>,
22904 selection: &Selection<Point>,
22905 display_map: &DisplaySnapshot,
22906 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22907) -> (MultiBufferRow, MultiBufferRow) {
22908 contiguous_row_selections.push(selection.clone());
22909 let start_row = starting_row(selection, display_map);
22910 let mut end_row = ending_row(selection, display_map);
22911
22912 while let Some(next_selection) = selections.peek() {
22913 if next_selection.start.row <= end_row.0 {
22914 end_row = ending_row(next_selection, display_map);
22915 contiguous_row_selections.push(selections.next().unwrap().clone());
22916 } else {
22917 break;
22918 }
22919 }
22920 (start_row, end_row)
22921}
22922
22923fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22924 if selection.start.column > 0 {
22925 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22926 } else {
22927 MultiBufferRow(selection.start.row)
22928 }
22929}
22930
22931fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22932 if next_selection.end.column > 0 || next_selection.is_empty() {
22933 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22934 } else {
22935 MultiBufferRow(next_selection.end.row)
22936 }
22937}
22938
22939impl EditorSnapshot {
22940 pub fn remote_selections_in_range<'a>(
22941 &'a self,
22942 range: &'a Range<Anchor>,
22943 collaboration_hub: &dyn CollaborationHub,
22944 cx: &'a App,
22945 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22946 let participant_names = collaboration_hub.user_names(cx);
22947 let participant_indices = collaboration_hub.user_participant_indices(cx);
22948 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22949 let collaborators_by_replica_id = collaborators_by_peer_id
22950 .values()
22951 .map(|collaborator| (collaborator.replica_id, collaborator))
22952 .collect::<HashMap<_, _>>();
22953 self.buffer_snapshot
22954 .selections_in_range(range, false)
22955 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22956 if replica_id == AGENT_REPLICA_ID {
22957 Some(RemoteSelection {
22958 replica_id,
22959 selection,
22960 cursor_shape,
22961 line_mode,
22962 collaborator_id: CollaboratorId::Agent,
22963 user_name: Some("Agent".into()),
22964 color: cx.theme().players().agent(),
22965 })
22966 } else {
22967 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22968 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22969 let user_name = participant_names.get(&collaborator.user_id).cloned();
22970 Some(RemoteSelection {
22971 replica_id,
22972 selection,
22973 cursor_shape,
22974 line_mode,
22975 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22976 user_name,
22977 color: if let Some(index) = participant_index {
22978 cx.theme().players().color_for_participant(index.0)
22979 } else {
22980 cx.theme().players().absent()
22981 },
22982 })
22983 }
22984 })
22985 }
22986
22987 pub fn hunks_for_ranges(
22988 &self,
22989 ranges: impl IntoIterator<Item = Range<Point>>,
22990 ) -> Vec<MultiBufferDiffHunk> {
22991 let mut hunks = Vec::new();
22992 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22993 HashMap::default();
22994 for query_range in ranges {
22995 let query_rows =
22996 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22997 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22998 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22999 ) {
23000 // Include deleted hunks that are adjacent to the query range, because
23001 // otherwise they would be missed.
23002 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23003 if hunk.status().is_deleted() {
23004 intersects_range |= hunk.row_range.start == query_rows.end;
23005 intersects_range |= hunk.row_range.end == query_rows.start;
23006 }
23007 if intersects_range {
23008 if !processed_buffer_rows
23009 .entry(hunk.buffer_id)
23010 .or_default()
23011 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23012 {
23013 continue;
23014 }
23015 hunks.push(hunk);
23016 }
23017 }
23018 }
23019
23020 hunks
23021 }
23022
23023 fn display_diff_hunks_for_rows<'a>(
23024 &'a self,
23025 display_rows: Range<DisplayRow>,
23026 folded_buffers: &'a HashSet<BufferId>,
23027 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23028 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23029 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23030
23031 self.buffer_snapshot
23032 .diff_hunks_in_range(buffer_start..buffer_end)
23033 .filter_map(|hunk| {
23034 if folded_buffers.contains(&hunk.buffer_id) {
23035 return None;
23036 }
23037
23038 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23039 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23040
23041 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23042 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23043
23044 let display_hunk = if hunk_display_start.column() != 0 {
23045 DisplayDiffHunk::Folded {
23046 display_row: hunk_display_start.row(),
23047 }
23048 } else {
23049 let mut end_row = hunk_display_end.row();
23050 if hunk_display_end.column() > 0 {
23051 end_row.0 += 1;
23052 }
23053 let is_created_file = hunk.is_created_file();
23054 DisplayDiffHunk::Unfolded {
23055 status: hunk.status(),
23056 diff_base_byte_range: hunk.diff_base_byte_range,
23057 display_row_range: hunk_display_start.row()..end_row,
23058 multi_buffer_range: Anchor::range_in_buffer(
23059 hunk.excerpt_id,
23060 hunk.buffer_id,
23061 hunk.buffer_range,
23062 ),
23063 is_created_file,
23064 }
23065 };
23066
23067 Some(display_hunk)
23068 })
23069 }
23070
23071 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23072 self.display_snapshot.buffer_snapshot.language_at(position)
23073 }
23074
23075 pub fn is_focused(&self) -> bool {
23076 self.is_focused
23077 }
23078
23079 pub fn placeholder_text(&self) -> Option<String> {
23080 self.placeholder_display_snapshot
23081 .as_ref()
23082 .map(|display_map| display_map.text())
23083 }
23084
23085 pub fn scroll_position(&self) -> gpui::Point<f32> {
23086 self.scroll_anchor.scroll_position(&self.display_snapshot)
23087 }
23088
23089 fn gutter_dimensions(
23090 &self,
23091 font_id: FontId,
23092 font_size: Pixels,
23093 max_line_number_width: Pixels,
23094 cx: &App,
23095 ) -> Option<GutterDimensions> {
23096 if !self.show_gutter {
23097 return None;
23098 }
23099
23100 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23101 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23102
23103 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23104 matches!(
23105 ProjectSettings::get_global(cx).git.git_gutter,
23106 Some(GitGutterSetting::TrackedFiles)
23107 )
23108 });
23109 let gutter_settings = EditorSettings::get_global(cx).gutter;
23110 let show_line_numbers = self
23111 .show_line_numbers
23112 .unwrap_or(gutter_settings.line_numbers);
23113 let line_gutter_width = if show_line_numbers {
23114 // Avoid flicker-like gutter resizes when the line number gains another digit by
23115 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23116 let min_width_for_number_on_gutter =
23117 ch_advance * gutter_settings.min_line_number_digits as f32;
23118 max_line_number_width.max(min_width_for_number_on_gutter)
23119 } else {
23120 0.0.into()
23121 };
23122
23123 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23124 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23125
23126 let git_blame_entries_width =
23127 self.git_blame_gutter_max_author_length
23128 .map(|max_author_length| {
23129 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23130 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23131
23132 /// The number of characters to dedicate to gaps and margins.
23133 const SPACING_WIDTH: usize = 4;
23134
23135 let max_char_count = max_author_length.min(renderer.max_author_length())
23136 + ::git::SHORT_SHA_LENGTH
23137 + MAX_RELATIVE_TIMESTAMP.len()
23138 + SPACING_WIDTH;
23139
23140 ch_advance * max_char_count
23141 });
23142
23143 let is_singleton = self.buffer_snapshot.is_singleton();
23144
23145 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23146 left_padding += if !is_singleton {
23147 ch_width * 4.0
23148 } else if show_runnables || show_breakpoints {
23149 ch_width * 3.0
23150 } else if show_git_gutter && show_line_numbers {
23151 ch_width * 2.0
23152 } else if show_git_gutter || show_line_numbers {
23153 ch_width
23154 } else {
23155 px(0.)
23156 };
23157
23158 let shows_folds = is_singleton && gutter_settings.folds;
23159
23160 let right_padding = if shows_folds && show_line_numbers {
23161 ch_width * 4.0
23162 } else if shows_folds || (!is_singleton && show_line_numbers) {
23163 ch_width * 3.0
23164 } else if show_line_numbers {
23165 ch_width
23166 } else {
23167 px(0.)
23168 };
23169
23170 Some(GutterDimensions {
23171 left_padding,
23172 right_padding,
23173 width: line_gutter_width + left_padding + right_padding,
23174 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23175 git_blame_entries_width,
23176 })
23177 }
23178
23179 pub fn render_crease_toggle(
23180 &self,
23181 buffer_row: MultiBufferRow,
23182 row_contains_cursor: bool,
23183 editor: Entity<Editor>,
23184 window: &mut Window,
23185 cx: &mut App,
23186 ) -> Option<AnyElement> {
23187 let folded = self.is_line_folded(buffer_row);
23188 let mut is_foldable = false;
23189
23190 if let Some(crease) = self
23191 .crease_snapshot
23192 .query_row(buffer_row, &self.buffer_snapshot)
23193 {
23194 is_foldable = true;
23195 match crease {
23196 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23197 if let Some(render_toggle) = render_toggle {
23198 let toggle_callback =
23199 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23200 if folded {
23201 editor.update(cx, |editor, cx| {
23202 editor.fold_at(buffer_row, window, cx)
23203 });
23204 } else {
23205 editor.update(cx, |editor, cx| {
23206 editor.unfold_at(buffer_row, window, cx)
23207 });
23208 }
23209 });
23210 return Some((render_toggle)(
23211 buffer_row,
23212 folded,
23213 toggle_callback,
23214 window,
23215 cx,
23216 ));
23217 }
23218 }
23219 }
23220 }
23221
23222 is_foldable |= self.starts_indent(buffer_row);
23223
23224 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23225 Some(
23226 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23227 .toggle_state(folded)
23228 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23229 if folded {
23230 this.unfold_at(buffer_row, window, cx);
23231 } else {
23232 this.fold_at(buffer_row, window, cx);
23233 }
23234 }))
23235 .into_any_element(),
23236 )
23237 } else {
23238 None
23239 }
23240 }
23241
23242 pub fn render_crease_trailer(
23243 &self,
23244 buffer_row: MultiBufferRow,
23245 window: &mut Window,
23246 cx: &mut App,
23247 ) -> Option<AnyElement> {
23248 let folded = self.is_line_folded(buffer_row);
23249 if let Crease::Inline { render_trailer, .. } = self
23250 .crease_snapshot
23251 .query_row(buffer_row, &self.buffer_snapshot)?
23252 {
23253 let render_trailer = render_trailer.as_ref()?;
23254 Some(render_trailer(buffer_row, folded, window, cx))
23255 } else {
23256 None
23257 }
23258 }
23259}
23260
23261impl Deref for EditorSnapshot {
23262 type Target = DisplaySnapshot;
23263
23264 fn deref(&self) -> &Self::Target {
23265 &self.display_snapshot
23266 }
23267}
23268
23269#[derive(Clone, Debug, PartialEq, Eq)]
23270pub enum EditorEvent {
23271 InputIgnored {
23272 text: Arc<str>,
23273 },
23274 InputHandled {
23275 utf16_range_to_replace: Option<Range<isize>>,
23276 text: Arc<str>,
23277 },
23278 ExcerptsAdded {
23279 buffer: Entity<Buffer>,
23280 predecessor: ExcerptId,
23281 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23282 },
23283 ExcerptsRemoved {
23284 ids: Vec<ExcerptId>,
23285 removed_buffer_ids: Vec<BufferId>,
23286 },
23287 BufferFoldToggled {
23288 ids: Vec<ExcerptId>,
23289 folded: bool,
23290 },
23291 ExcerptsEdited {
23292 ids: Vec<ExcerptId>,
23293 },
23294 ExcerptsExpanded {
23295 ids: Vec<ExcerptId>,
23296 },
23297 BufferEdited,
23298 Edited {
23299 transaction_id: clock::Lamport,
23300 },
23301 Reparsed(BufferId),
23302 Focused,
23303 FocusedIn,
23304 Blurred,
23305 DirtyChanged,
23306 Saved,
23307 TitleChanged,
23308 SelectionsChanged {
23309 local: bool,
23310 },
23311 ScrollPositionChanged {
23312 local: bool,
23313 autoscroll: bool,
23314 },
23315 TransactionUndone {
23316 transaction_id: clock::Lamport,
23317 },
23318 TransactionBegun {
23319 transaction_id: clock::Lamport,
23320 },
23321 CursorShapeChanged,
23322 BreadcrumbsChanged,
23323 PushedToNavHistory {
23324 anchor: Anchor,
23325 is_deactivate: bool,
23326 },
23327}
23328
23329impl EventEmitter<EditorEvent> for Editor {}
23330
23331impl Focusable for Editor {
23332 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23333 self.focus_handle.clone()
23334 }
23335}
23336
23337impl Render for Editor {
23338 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23339 let settings = ThemeSettings::get_global(cx);
23340
23341 let mut text_style = match self.mode {
23342 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23343 color: cx.theme().colors().editor_foreground,
23344 font_family: settings.ui_font.family.clone(),
23345 font_features: settings.ui_font.features.clone(),
23346 font_fallbacks: settings.ui_font.fallbacks.clone(),
23347 font_size: rems(0.875).into(),
23348 font_weight: settings.ui_font.weight,
23349 line_height: relative(settings.buffer_line_height.value()),
23350 ..Default::default()
23351 },
23352 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23353 color: cx.theme().colors().editor_foreground,
23354 font_family: settings.buffer_font.family.clone(),
23355 font_features: settings.buffer_font.features.clone(),
23356 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23357 font_size: settings.buffer_font_size(cx).into(),
23358 font_weight: settings.buffer_font.weight,
23359 line_height: relative(settings.buffer_line_height.value()),
23360 ..Default::default()
23361 },
23362 };
23363 if let Some(text_style_refinement) = &self.text_style_refinement {
23364 text_style.refine(text_style_refinement)
23365 }
23366
23367 let background = match self.mode {
23368 EditorMode::SingleLine => cx.theme().system().transparent,
23369 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23370 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23371 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23372 };
23373
23374 EditorElement::new(
23375 &cx.entity(),
23376 EditorStyle {
23377 background,
23378 border: cx.theme().colors().border,
23379 local_player: cx.theme().players().local(),
23380 text: text_style,
23381 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23382 syntax: cx.theme().syntax().clone(),
23383 status: cx.theme().status().clone(),
23384 inlay_hints_style: make_inlay_hints_style(cx),
23385 edit_prediction_styles: make_suggestion_styles(cx),
23386 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23387 show_underlines: self.diagnostics_enabled(),
23388 },
23389 )
23390 }
23391}
23392
23393impl EntityInputHandler for Editor {
23394 fn text_for_range(
23395 &mut self,
23396 range_utf16: Range<usize>,
23397 adjusted_range: &mut Option<Range<usize>>,
23398 _: &mut Window,
23399 cx: &mut Context<Self>,
23400 ) -> Option<String> {
23401 let snapshot = self.buffer.read(cx).read(cx);
23402 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23403 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23404 if (start.0..end.0) != range_utf16 {
23405 adjusted_range.replace(start.0..end.0);
23406 }
23407 Some(snapshot.text_for_range(start..end).collect())
23408 }
23409
23410 fn selected_text_range(
23411 &mut self,
23412 ignore_disabled_input: bool,
23413 _: &mut Window,
23414 cx: &mut Context<Self>,
23415 ) -> Option<UTF16Selection> {
23416 // Prevent the IME menu from appearing when holding down an alphabetic key
23417 // while input is disabled.
23418 if !ignore_disabled_input && !self.input_enabled {
23419 return None;
23420 }
23421
23422 let selection = self.selections.newest::<OffsetUtf16>(cx);
23423 let range = selection.range();
23424
23425 Some(UTF16Selection {
23426 range: range.start.0..range.end.0,
23427 reversed: selection.reversed,
23428 })
23429 }
23430
23431 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23432 let snapshot = self.buffer.read(cx).read(cx);
23433 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23434 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23435 }
23436
23437 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23438 self.clear_highlights::<InputComposition>(cx);
23439 self.ime_transaction.take();
23440 }
23441
23442 fn replace_text_in_range(
23443 &mut self,
23444 range_utf16: Option<Range<usize>>,
23445 text: &str,
23446 window: &mut Window,
23447 cx: &mut Context<Self>,
23448 ) {
23449 if !self.input_enabled {
23450 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23451 return;
23452 }
23453
23454 self.transact(window, cx, |this, window, cx| {
23455 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23456 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23457 Some(this.selection_replacement_ranges(range_utf16, cx))
23458 } else {
23459 this.marked_text_ranges(cx)
23460 };
23461
23462 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23463 let newest_selection_id = this.selections.newest_anchor().id;
23464 this.selections
23465 .all::<OffsetUtf16>(cx)
23466 .iter()
23467 .zip(ranges_to_replace.iter())
23468 .find_map(|(selection, range)| {
23469 if selection.id == newest_selection_id {
23470 Some(
23471 (range.start.0 as isize - selection.head().0 as isize)
23472 ..(range.end.0 as isize - selection.head().0 as isize),
23473 )
23474 } else {
23475 None
23476 }
23477 })
23478 });
23479
23480 cx.emit(EditorEvent::InputHandled {
23481 utf16_range_to_replace: range_to_replace,
23482 text: text.into(),
23483 });
23484
23485 if let Some(new_selected_ranges) = new_selected_ranges {
23486 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23487 selections.select_ranges(new_selected_ranges)
23488 });
23489 this.backspace(&Default::default(), window, cx);
23490 }
23491
23492 this.handle_input(text, window, cx);
23493 });
23494
23495 if let Some(transaction) = self.ime_transaction {
23496 self.buffer.update(cx, |buffer, cx| {
23497 buffer.group_until_transaction(transaction, cx);
23498 });
23499 }
23500
23501 self.unmark_text(window, cx);
23502 }
23503
23504 fn replace_and_mark_text_in_range(
23505 &mut self,
23506 range_utf16: Option<Range<usize>>,
23507 text: &str,
23508 new_selected_range_utf16: Option<Range<usize>>,
23509 window: &mut Window,
23510 cx: &mut Context<Self>,
23511 ) {
23512 if !self.input_enabled {
23513 return;
23514 }
23515
23516 let transaction = self.transact(window, cx, |this, window, cx| {
23517 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23518 let snapshot = this.buffer.read(cx).read(cx);
23519 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23520 for marked_range in &mut marked_ranges {
23521 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23522 marked_range.start.0 += relative_range_utf16.start;
23523 marked_range.start =
23524 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23525 marked_range.end =
23526 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23527 }
23528 }
23529 Some(marked_ranges)
23530 } else if let Some(range_utf16) = range_utf16 {
23531 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23532 Some(this.selection_replacement_ranges(range_utf16, cx))
23533 } else {
23534 None
23535 };
23536
23537 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23538 let newest_selection_id = this.selections.newest_anchor().id;
23539 this.selections
23540 .all::<OffsetUtf16>(cx)
23541 .iter()
23542 .zip(ranges_to_replace.iter())
23543 .find_map(|(selection, range)| {
23544 if selection.id == newest_selection_id {
23545 Some(
23546 (range.start.0 as isize - selection.head().0 as isize)
23547 ..(range.end.0 as isize - selection.head().0 as isize),
23548 )
23549 } else {
23550 None
23551 }
23552 })
23553 });
23554
23555 cx.emit(EditorEvent::InputHandled {
23556 utf16_range_to_replace: range_to_replace,
23557 text: text.into(),
23558 });
23559
23560 if let Some(ranges) = ranges_to_replace {
23561 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23562 s.select_ranges(ranges)
23563 });
23564 }
23565
23566 let marked_ranges = {
23567 let snapshot = this.buffer.read(cx).read(cx);
23568 this.selections
23569 .disjoint_anchors()
23570 .iter()
23571 .map(|selection| {
23572 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23573 })
23574 .collect::<Vec<_>>()
23575 };
23576
23577 if text.is_empty() {
23578 this.unmark_text(window, cx);
23579 } else {
23580 this.highlight_text::<InputComposition>(
23581 marked_ranges.clone(),
23582 HighlightStyle {
23583 underline: Some(UnderlineStyle {
23584 thickness: px(1.),
23585 color: None,
23586 wavy: false,
23587 }),
23588 ..Default::default()
23589 },
23590 cx,
23591 );
23592 }
23593
23594 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23595 let use_autoclose = this.use_autoclose;
23596 let use_auto_surround = this.use_auto_surround;
23597 this.set_use_autoclose(false);
23598 this.set_use_auto_surround(false);
23599 this.handle_input(text, window, cx);
23600 this.set_use_autoclose(use_autoclose);
23601 this.set_use_auto_surround(use_auto_surround);
23602
23603 if let Some(new_selected_range) = new_selected_range_utf16 {
23604 let snapshot = this.buffer.read(cx).read(cx);
23605 let new_selected_ranges = marked_ranges
23606 .into_iter()
23607 .map(|marked_range| {
23608 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23609 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23610 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23611 snapshot.clip_offset_utf16(new_start, Bias::Left)
23612 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23613 })
23614 .collect::<Vec<_>>();
23615
23616 drop(snapshot);
23617 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23618 selections.select_ranges(new_selected_ranges)
23619 });
23620 }
23621 });
23622
23623 self.ime_transaction = self.ime_transaction.or(transaction);
23624 if let Some(transaction) = self.ime_transaction {
23625 self.buffer.update(cx, |buffer, cx| {
23626 buffer.group_until_transaction(transaction, cx);
23627 });
23628 }
23629
23630 if self.text_highlights::<InputComposition>(cx).is_none() {
23631 self.ime_transaction.take();
23632 }
23633 }
23634
23635 fn bounds_for_range(
23636 &mut self,
23637 range_utf16: Range<usize>,
23638 element_bounds: gpui::Bounds<Pixels>,
23639 window: &mut Window,
23640 cx: &mut Context<Self>,
23641 ) -> Option<gpui::Bounds<Pixels>> {
23642 let text_layout_details = self.text_layout_details(window);
23643 let CharacterDimensions {
23644 em_width,
23645 em_advance,
23646 line_height,
23647 } = self.character_dimensions(window);
23648
23649 let snapshot = self.snapshot(window, cx);
23650 let scroll_position = snapshot.scroll_position();
23651 let scroll_left = scroll_position.x * em_advance;
23652
23653 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23654 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23655 + self.gutter_dimensions.full_width();
23656 let y = line_height * (start.row().as_f32() - scroll_position.y);
23657
23658 Some(Bounds {
23659 origin: element_bounds.origin + point(x, y),
23660 size: size(em_width, line_height),
23661 })
23662 }
23663
23664 fn character_index_for_point(
23665 &mut self,
23666 point: gpui::Point<Pixels>,
23667 _window: &mut Window,
23668 _cx: &mut Context<Self>,
23669 ) -> Option<usize> {
23670 let position_map = self.last_position_map.as_ref()?;
23671 if !position_map.text_hitbox.contains(&point) {
23672 return None;
23673 }
23674 let display_point = position_map.point_for_position(point).previous_valid;
23675 let anchor = position_map
23676 .snapshot
23677 .display_point_to_anchor(display_point, Bias::Left);
23678 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23679 Some(utf16_offset.0)
23680 }
23681}
23682
23683trait SelectionExt {
23684 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23685 fn spanned_rows(
23686 &self,
23687 include_end_if_at_line_start: bool,
23688 map: &DisplaySnapshot,
23689 ) -> Range<MultiBufferRow>;
23690}
23691
23692impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23693 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23694 let start = self
23695 .start
23696 .to_point(&map.buffer_snapshot)
23697 .to_display_point(map);
23698 let end = self
23699 .end
23700 .to_point(&map.buffer_snapshot)
23701 .to_display_point(map);
23702 if self.reversed {
23703 end..start
23704 } else {
23705 start..end
23706 }
23707 }
23708
23709 fn spanned_rows(
23710 &self,
23711 include_end_if_at_line_start: bool,
23712 map: &DisplaySnapshot,
23713 ) -> Range<MultiBufferRow> {
23714 let start = self.start.to_point(&map.buffer_snapshot);
23715 let mut end = self.end.to_point(&map.buffer_snapshot);
23716 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23717 end.row -= 1;
23718 }
23719
23720 let buffer_start = map.prev_line_boundary(start).0;
23721 let buffer_end = map.next_line_boundary(end).0;
23722 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23723 }
23724}
23725
23726impl<T: InvalidationRegion> InvalidationStack<T> {
23727 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23728 where
23729 S: Clone + ToOffset,
23730 {
23731 while let Some(region) = self.last() {
23732 let all_selections_inside_invalidation_ranges =
23733 if selections.len() == region.ranges().len() {
23734 selections
23735 .iter()
23736 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23737 .all(|(selection, invalidation_range)| {
23738 let head = selection.head().to_offset(buffer);
23739 invalidation_range.start <= head && invalidation_range.end >= head
23740 })
23741 } else {
23742 false
23743 };
23744
23745 if all_selections_inside_invalidation_ranges {
23746 break;
23747 } else {
23748 self.pop();
23749 }
23750 }
23751 }
23752}
23753
23754impl<T> Default for InvalidationStack<T> {
23755 fn default() -> Self {
23756 Self(Default::default())
23757 }
23758}
23759
23760impl<T> Deref for InvalidationStack<T> {
23761 type Target = Vec<T>;
23762
23763 fn deref(&self) -> &Self::Target {
23764 &self.0
23765 }
23766}
23767
23768impl<T> DerefMut for InvalidationStack<T> {
23769 fn deref_mut(&mut self) -> &mut Self::Target {
23770 &mut self.0
23771 }
23772}
23773
23774impl InvalidationRegion for SnippetState {
23775 fn ranges(&self) -> &[Range<Anchor>] {
23776 &self.ranges[self.active_index]
23777 }
23778}
23779
23780fn edit_prediction_edit_text(
23781 current_snapshot: &BufferSnapshot,
23782 edits: &[(Range<Anchor>, String)],
23783 edit_preview: &EditPreview,
23784 include_deletions: bool,
23785 cx: &App,
23786) -> HighlightedText {
23787 let edits = edits
23788 .iter()
23789 .map(|(anchor, text)| {
23790 (
23791 anchor.start.text_anchor..anchor.end.text_anchor,
23792 text.clone(),
23793 )
23794 })
23795 .collect::<Vec<_>>();
23796
23797 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23798}
23799
23800fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23801 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23802 // Just show the raw edit text with basic styling
23803 let mut text = String::new();
23804 let mut highlights = Vec::new();
23805
23806 let insertion_highlight_style = HighlightStyle {
23807 color: Some(cx.theme().colors().text),
23808 ..Default::default()
23809 };
23810
23811 for (_, edit_text) in edits {
23812 let start_offset = text.len();
23813 text.push_str(edit_text);
23814 let end_offset = text.len();
23815
23816 if start_offset < end_offset {
23817 highlights.push((start_offset..end_offset, insertion_highlight_style));
23818 }
23819 }
23820
23821 HighlightedText {
23822 text: text.into(),
23823 highlights,
23824 }
23825}
23826
23827pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23828 match severity {
23829 lsp::DiagnosticSeverity::ERROR => colors.error,
23830 lsp::DiagnosticSeverity::WARNING => colors.warning,
23831 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23832 lsp::DiagnosticSeverity::HINT => colors.info,
23833 _ => colors.ignored,
23834 }
23835}
23836
23837pub fn styled_runs_for_code_label<'a>(
23838 label: &'a CodeLabel,
23839 syntax_theme: &'a theme::SyntaxTheme,
23840) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23841 let fade_out = HighlightStyle {
23842 fade_out: Some(0.35),
23843 ..Default::default()
23844 };
23845
23846 let mut prev_end = label.filter_range.end;
23847 label
23848 .runs
23849 .iter()
23850 .enumerate()
23851 .flat_map(move |(ix, (range, highlight_id))| {
23852 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23853 style
23854 } else {
23855 return Default::default();
23856 };
23857 let muted_style = style.highlight(fade_out);
23858
23859 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23860 if range.start >= label.filter_range.end {
23861 if range.start > prev_end {
23862 runs.push((prev_end..range.start, fade_out));
23863 }
23864 runs.push((range.clone(), muted_style));
23865 } else if range.end <= label.filter_range.end {
23866 runs.push((range.clone(), style));
23867 } else {
23868 runs.push((range.start..label.filter_range.end, style));
23869 runs.push((label.filter_range.end..range.end, muted_style));
23870 }
23871 prev_end = cmp::max(prev_end, range.end);
23872
23873 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23874 runs.push((prev_end..label.text.len(), fade_out));
23875 }
23876
23877 runs
23878 })
23879}
23880
23881pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23882 let mut prev_index = 0;
23883 let mut prev_codepoint: Option<char> = None;
23884 text.char_indices()
23885 .chain([(text.len(), '\0')])
23886 .filter_map(move |(index, codepoint)| {
23887 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23888 let is_boundary = index == text.len()
23889 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23890 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23891 if is_boundary {
23892 let chunk = &text[prev_index..index];
23893 prev_index = index;
23894 Some(chunk)
23895 } else {
23896 None
23897 }
23898 })
23899}
23900
23901pub trait RangeToAnchorExt: Sized {
23902 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23903
23904 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23905 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23906 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23907 }
23908}
23909
23910impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23911 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23912 let start_offset = self.start.to_offset(snapshot);
23913 let end_offset = self.end.to_offset(snapshot);
23914 if start_offset == end_offset {
23915 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23916 } else {
23917 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23918 }
23919 }
23920}
23921
23922pub trait RowExt {
23923 fn as_f32(&self) -> f32;
23924
23925 fn next_row(&self) -> Self;
23926
23927 fn previous_row(&self) -> Self;
23928
23929 fn minus(&self, other: Self) -> u32;
23930}
23931
23932impl RowExt for DisplayRow {
23933 fn as_f32(&self) -> f32 {
23934 self.0 as f32
23935 }
23936
23937 fn next_row(&self) -> Self {
23938 Self(self.0 + 1)
23939 }
23940
23941 fn previous_row(&self) -> Self {
23942 Self(self.0.saturating_sub(1))
23943 }
23944
23945 fn minus(&self, other: Self) -> u32 {
23946 self.0 - other.0
23947 }
23948}
23949
23950impl RowExt for MultiBufferRow {
23951 fn as_f32(&self) -> f32 {
23952 self.0 as f32
23953 }
23954
23955 fn next_row(&self) -> Self {
23956 Self(self.0 + 1)
23957 }
23958
23959 fn previous_row(&self) -> Self {
23960 Self(self.0.saturating_sub(1))
23961 }
23962
23963 fn minus(&self, other: Self) -> u32 {
23964 self.0 - other.0
23965 }
23966}
23967
23968trait RowRangeExt {
23969 type Row;
23970
23971 fn len(&self) -> usize;
23972
23973 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23974}
23975
23976impl RowRangeExt for Range<MultiBufferRow> {
23977 type Row = MultiBufferRow;
23978
23979 fn len(&self) -> usize {
23980 (self.end.0 - self.start.0) as usize
23981 }
23982
23983 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23984 (self.start.0..self.end.0).map(MultiBufferRow)
23985 }
23986}
23987
23988impl RowRangeExt for Range<DisplayRow> {
23989 type Row = DisplayRow;
23990
23991 fn len(&self) -> usize {
23992 (self.end.0 - self.start.0) as usize
23993 }
23994
23995 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23996 (self.start.0..self.end.0).map(DisplayRow)
23997 }
23998}
23999
24000/// If select range has more than one line, we
24001/// just point the cursor to range.start.
24002fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24003 if range.start.row == range.end.row {
24004 range
24005 } else {
24006 range.start..range.start
24007 }
24008}
24009pub struct KillRing(ClipboardItem);
24010impl Global for KillRing {}
24011
24012const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24013
24014enum BreakpointPromptEditAction {
24015 Log,
24016 Condition,
24017 HitCondition,
24018}
24019
24020struct BreakpointPromptEditor {
24021 pub(crate) prompt: Entity<Editor>,
24022 editor: WeakEntity<Editor>,
24023 breakpoint_anchor: Anchor,
24024 breakpoint: Breakpoint,
24025 edit_action: BreakpointPromptEditAction,
24026 block_ids: HashSet<CustomBlockId>,
24027 editor_margins: Arc<Mutex<EditorMargins>>,
24028 _subscriptions: Vec<Subscription>,
24029}
24030
24031impl BreakpointPromptEditor {
24032 const MAX_LINES: u8 = 4;
24033
24034 fn new(
24035 editor: WeakEntity<Editor>,
24036 breakpoint_anchor: Anchor,
24037 breakpoint: Breakpoint,
24038 edit_action: BreakpointPromptEditAction,
24039 window: &mut Window,
24040 cx: &mut Context<Self>,
24041 ) -> Self {
24042 let base_text = match edit_action {
24043 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24044 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24045 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24046 }
24047 .map(|msg| msg.to_string())
24048 .unwrap_or_default();
24049
24050 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24051 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24052
24053 let prompt = cx.new(|cx| {
24054 let mut prompt = Editor::new(
24055 EditorMode::AutoHeight {
24056 min_lines: 1,
24057 max_lines: Some(Self::MAX_LINES as usize),
24058 },
24059 buffer,
24060 None,
24061 window,
24062 cx,
24063 );
24064 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24065 prompt.set_show_cursor_when_unfocused(false, cx);
24066 prompt.set_placeholder_text(
24067 match edit_action {
24068 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24069 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24070 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24071 },
24072 window,
24073 cx,
24074 );
24075
24076 prompt
24077 });
24078
24079 Self {
24080 prompt,
24081 editor,
24082 breakpoint_anchor,
24083 breakpoint,
24084 edit_action,
24085 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24086 block_ids: Default::default(),
24087 _subscriptions: vec![],
24088 }
24089 }
24090
24091 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24092 self.block_ids.extend(block_ids)
24093 }
24094
24095 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24096 if let Some(editor) = self.editor.upgrade() {
24097 let message = self
24098 .prompt
24099 .read(cx)
24100 .buffer
24101 .read(cx)
24102 .as_singleton()
24103 .expect("A multi buffer in breakpoint prompt isn't possible")
24104 .read(cx)
24105 .as_rope()
24106 .to_string();
24107
24108 editor.update(cx, |editor, cx| {
24109 editor.edit_breakpoint_at_anchor(
24110 self.breakpoint_anchor,
24111 self.breakpoint.clone(),
24112 match self.edit_action {
24113 BreakpointPromptEditAction::Log => {
24114 BreakpointEditAction::EditLogMessage(message.into())
24115 }
24116 BreakpointPromptEditAction::Condition => {
24117 BreakpointEditAction::EditCondition(message.into())
24118 }
24119 BreakpointPromptEditAction::HitCondition => {
24120 BreakpointEditAction::EditHitCondition(message.into())
24121 }
24122 },
24123 cx,
24124 );
24125
24126 editor.remove_blocks(self.block_ids.clone(), None, cx);
24127 cx.focus_self(window);
24128 });
24129 }
24130 }
24131
24132 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24133 self.editor
24134 .update(cx, |editor, cx| {
24135 editor.remove_blocks(self.block_ids.clone(), None, cx);
24136 window.focus(&editor.focus_handle);
24137 })
24138 .log_err();
24139 }
24140
24141 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24142 let settings = ThemeSettings::get_global(cx);
24143 let text_style = TextStyle {
24144 color: if self.prompt.read(cx).read_only(cx) {
24145 cx.theme().colors().text_disabled
24146 } else {
24147 cx.theme().colors().text
24148 },
24149 font_family: settings.buffer_font.family.clone(),
24150 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24151 font_size: settings.buffer_font_size(cx).into(),
24152 font_weight: settings.buffer_font.weight,
24153 line_height: relative(settings.buffer_line_height.value()),
24154 ..Default::default()
24155 };
24156 EditorElement::new(
24157 &self.prompt,
24158 EditorStyle {
24159 background: cx.theme().colors().editor_background,
24160 local_player: cx.theme().players().local(),
24161 text: text_style,
24162 ..Default::default()
24163 },
24164 )
24165 }
24166}
24167
24168impl Render for BreakpointPromptEditor {
24169 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24170 let editor_margins = *self.editor_margins.lock();
24171 let gutter_dimensions = editor_margins.gutter;
24172 h_flex()
24173 .key_context("Editor")
24174 .bg(cx.theme().colors().editor_background)
24175 .border_y_1()
24176 .border_color(cx.theme().status().info_border)
24177 .size_full()
24178 .py(window.line_height() / 2.5)
24179 .on_action(cx.listener(Self::confirm))
24180 .on_action(cx.listener(Self::cancel))
24181 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24182 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24183 }
24184}
24185
24186impl Focusable for BreakpointPromptEditor {
24187 fn focus_handle(&self, cx: &App) -> FocusHandle {
24188 self.prompt.focus_handle(cx)
24189 }
24190}
24191
24192fn all_edits_insertions_or_deletions(
24193 edits: &Vec<(Range<Anchor>, String)>,
24194 snapshot: &MultiBufferSnapshot,
24195) -> bool {
24196 let mut all_insertions = true;
24197 let mut all_deletions = true;
24198
24199 for (range, new_text) in edits.iter() {
24200 let range_is_empty = range.to_offset(snapshot).is_empty();
24201 let text_is_empty = new_text.is_empty();
24202
24203 if range_is_empty != text_is_empty {
24204 if range_is_empty {
24205 all_deletions = false;
24206 } else {
24207 all_insertions = false;
24208 }
24209 } else {
24210 return false;
24211 }
24212
24213 if !all_insertions && !all_deletions {
24214 return false;
24215 }
24216 }
24217 all_insertions || all_deletions
24218}
24219
24220struct MissingEditPredictionKeybindingTooltip;
24221
24222impl Render for MissingEditPredictionKeybindingTooltip {
24223 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24224 ui::tooltip_container(window, cx, |container, _, cx| {
24225 container
24226 .flex_shrink_0()
24227 .max_w_80()
24228 .min_h(rems_from_px(124.))
24229 .justify_between()
24230 .child(
24231 v_flex()
24232 .flex_1()
24233 .text_ui_sm(cx)
24234 .child(Label::new("Conflict with Accept Keybinding"))
24235 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24236 )
24237 .child(
24238 h_flex()
24239 .pb_1()
24240 .gap_1()
24241 .items_end()
24242 .w_full()
24243 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24244 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24245 }))
24246 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24247 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24248 })),
24249 )
24250 })
24251 }
24252}
24253
24254#[derive(Debug, Clone, Copy, PartialEq)]
24255pub struct LineHighlight {
24256 pub background: Background,
24257 pub border: Option<gpui::Hsla>,
24258 pub include_gutter: bool,
24259 pub type_id: Option<TypeId>,
24260}
24261
24262struct LineManipulationResult {
24263 pub new_text: String,
24264 pub line_count_before: usize,
24265 pub line_count_after: usize,
24266}
24267
24268fn render_diff_hunk_controls(
24269 row: u32,
24270 status: &DiffHunkStatus,
24271 hunk_range: Range<Anchor>,
24272 is_created_file: bool,
24273 line_height: Pixels,
24274 editor: &Entity<Editor>,
24275 _window: &mut Window,
24276 cx: &mut App,
24277) -> AnyElement {
24278 h_flex()
24279 .h(line_height)
24280 .mr_1()
24281 .gap_1()
24282 .px_0p5()
24283 .pb_1()
24284 .border_x_1()
24285 .border_b_1()
24286 .border_color(cx.theme().colors().border_variant)
24287 .rounded_b_lg()
24288 .bg(cx.theme().colors().editor_background)
24289 .gap_1()
24290 .block_mouse_except_scroll()
24291 .shadow_md()
24292 .child(if status.has_secondary_hunk() {
24293 Button::new(("stage", row as u64), "Stage")
24294 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24295 .tooltip({
24296 let focus_handle = editor.focus_handle(cx);
24297 move |window, cx| {
24298 Tooltip::for_action_in(
24299 "Stage Hunk",
24300 &::git::ToggleStaged,
24301 &focus_handle,
24302 window,
24303 cx,
24304 )
24305 }
24306 })
24307 .on_click({
24308 let editor = editor.clone();
24309 move |_event, _window, cx| {
24310 editor.update(cx, |editor, cx| {
24311 editor.stage_or_unstage_diff_hunks(
24312 true,
24313 vec![hunk_range.start..hunk_range.start],
24314 cx,
24315 );
24316 });
24317 }
24318 })
24319 } else {
24320 Button::new(("unstage", row as u64), "Unstage")
24321 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24322 .tooltip({
24323 let focus_handle = editor.focus_handle(cx);
24324 move |window, cx| {
24325 Tooltip::for_action_in(
24326 "Unstage Hunk",
24327 &::git::ToggleStaged,
24328 &focus_handle,
24329 window,
24330 cx,
24331 )
24332 }
24333 })
24334 .on_click({
24335 let editor = editor.clone();
24336 move |_event, _window, cx| {
24337 editor.update(cx, |editor, cx| {
24338 editor.stage_or_unstage_diff_hunks(
24339 false,
24340 vec![hunk_range.start..hunk_range.start],
24341 cx,
24342 );
24343 });
24344 }
24345 })
24346 })
24347 .child(
24348 Button::new(("restore", row as u64), "Restore")
24349 .tooltip({
24350 let focus_handle = editor.focus_handle(cx);
24351 move |window, cx| {
24352 Tooltip::for_action_in(
24353 "Restore Hunk",
24354 &::git::Restore,
24355 &focus_handle,
24356 window,
24357 cx,
24358 )
24359 }
24360 })
24361 .on_click({
24362 let editor = editor.clone();
24363 move |_event, window, cx| {
24364 editor.update(cx, |editor, cx| {
24365 let snapshot = editor.snapshot(window, cx);
24366 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24367 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24368 });
24369 }
24370 })
24371 .disabled(is_created_file),
24372 )
24373 .when(
24374 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24375 |el| {
24376 el.child(
24377 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24378 .shape(IconButtonShape::Square)
24379 .icon_size(IconSize::Small)
24380 // .disabled(!has_multiple_hunks)
24381 .tooltip({
24382 let focus_handle = editor.focus_handle(cx);
24383 move |window, cx| {
24384 Tooltip::for_action_in(
24385 "Next Hunk",
24386 &GoToHunk,
24387 &focus_handle,
24388 window,
24389 cx,
24390 )
24391 }
24392 })
24393 .on_click({
24394 let editor = editor.clone();
24395 move |_event, window, cx| {
24396 editor.update(cx, |editor, cx| {
24397 let snapshot = editor.snapshot(window, cx);
24398 let position =
24399 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24400 editor.go_to_hunk_before_or_after_position(
24401 &snapshot,
24402 position,
24403 Direction::Next,
24404 window,
24405 cx,
24406 );
24407 editor.expand_selected_diff_hunks(cx);
24408 });
24409 }
24410 }),
24411 )
24412 .child(
24413 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24414 .shape(IconButtonShape::Square)
24415 .icon_size(IconSize::Small)
24416 // .disabled(!has_multiple_hunks)
24417 .tooltip({
24418 let focus_handle = editor.focus_handle(cx);
24419 move |window, cx| {
24420 Tooltip::for_action_in(
24421 "Previous Hunk",
24422 &GoToPreviousHunk,
24423 &focus_handle,
24424 window,
24425 cx,
24426 )
24427 }
24428 })
24429 .on_click({
24430 let editor = editor.clone();
24431 move |_event, window, cx| {
24432 editor.update(cx, |editor, cx| {
24433 let snapshot = editor.snapshot(window, cx);
24434 let point =
24435 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24436 editor.go_to_hunk_before_or_after_position(
24437 &snapshot,
24438 point,
24439 Direction::Prev,
24440 window,
24441 cx,
24442 );
24443 editor.expand_selected_diff_hunks(cx);
24444 });
24445 }
24446 }),
24447 )
24448 },
24449 )
24450 .into_any_element()
24451}
24452
24453pub fn multibuffer_context_lines(cx: &App) -> u32 {
24454 EditorSettings::try_get(cx)
24455 .map(|settings| settings.excerpt_context_lines)
24456 .unwrap_or(2)
24457 .min(32)
24458}