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_position = multibuffer_snapshot.anchor_before(position);
5477 let Some(buffer) = buffer_position
5478 .buffer_id
5479 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5480 else {
5481 return;
5482 };
5483 let buffer_snapshot = buffer.read(cx).snapshot();
5484
5485 let query: Option<Arc<String>> =
5486 Self::completion_query(&multibuffer_snapshot, buffer_position)
5487 .map(|query| query.into());
5488
5489 drop(multibuffer_snapshot);
5490
5491 // Hide the current completions menu when query is empty. Without this, cached
5492 // completions from before the trigger char may be reused (#32774).
5493 if query.is_none() {
5494 let menu_is_open = matches!(
5495 self.context_menu.borrow().as_ref(),
5496 Some(CodeContextMenu::Completions(_))
5497 );
5498 if menu_is_open {
5499 self.hide_context_menu(window, cx);
5500 }
5501 }
5502
5503 let mut ignore_word_threshold = false;
5504 let provider = match requested_source {
5505 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5506 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5507 ignore_word_threshold = ignore_threshold;
5508 None
5509 }
5510 Some(CompletionsMenuSource::SnippetChoices) => {
5511 log::error!("bug: SnippetChoices requested_source is not handled");
5512 None
5513 }
5514 };
5515
5516 let sort_completions = provider
5517 .as_ref()
5518 .is_some_and(|provider| provider.sort_completions());
5519
5520 let filter_completions = provider
5521 .as_ref()
5522 .is_none_or(|provider| provider.filter_completions());
5523
5524 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5525 if filter_completions {
5526 menu.filter(query.clone(), provider.clone(), window, cx);
5527 }
5528 // When `is_incomplete` is false, no need to re-query completions when the current query
5529 // is a suffix of the initial query.
5530 if !menu.is_incomplete {
5531 // If the new query is a suffix of the old query (typing more characters) and
5532 // the previous result was complete, the existing completions can be filtered.
5533 //
5534 // Note that this is always true for snippet completions.
5535 let query_matches = match (&menu.initial_query, &query) {
5536 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5537 (None, _) => true,
5538 _ => false,
5539 };
5540 if query_matches {
5541 let position_matches = if menu.initial_position == position {
5542 true
5543 } else {
5544 let snapshot = self.buffer.read(cx).read(cx);
5545 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5546 };
5547 if position_matches {
5548 return;
5549 }
5550 }
5551 }
5552 };
5553
5554 let trigger_kind = match trigger {
5555 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5556 CompletionTriggerKind::TRIGGER_CHARACTER
5557 }
5558 _ => CompletionTriggerKind::INVOKED,
5559 };
5560 let completion_context = CompletionContext {
5561 trigger_character: trigger.and_then(|trigger| {
5562 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5563 Some(String::from(trigger))
5564 } else {
5565 None
5566 }
5567 }),
5568 trigger_kind,
5569 };
5570
5571 let Anchor {
5572 excerpt_id: buffer_excerpt_id,
5573 text_anchor: buffer_position,
5574 ..
5575 } = buffer_position;
5576
5577 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5578 buffer_snapshot.surrounding_word(buffer_position, false)
5579 {
5580 let word_to_exclude = buffer_snapshot
5581 .text_for_range(word_range.clone())
5582 .collect::<String>();
5583 (
5584 buffer_snapshot.anchor_before(word_range.start)
5585 ..buffer_snapshot.anchor_after(buffer_position),
5586 Some(word_to_exclude),
5587 )
5588 } else {
5589 (buffer_position..buffer_position, None)
5590 };
5591
5592 let language = buffer_snapshot
5593 .language_at(buffer_position)
5594 .map(|language| language.name());
5595
5596 let completion_settings =
5597 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5598
5599 let show_completion_documentation = buffer_snapshot
5600 .settings_at(buffer_position, cx)
5601 .show_completion_documentation;
5602
5603 // The document can be large, so stay in reasonable bounds when searching for words,
5604 // otherwise completion pop-up might be slow to appear.
5605 const WORD_LOOKUP_ROWS: u32 = 5_000;
5606 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5607 let min_word_search = buffer_snapshot.clip_point(
5608 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5609 Bias::Left,
5610 );
5611 let max_word_search = buffer_snapshot.clip_point(
5612 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5613 Bias::Right,
5614 );
5615 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5616 ..buffer_snapshot.point_to_offset(max_word_search);
5617
5618 let skip_digits = query
5619 .as_ref()
5620 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5621
5622 let omit_word_completions = !self.word_completions_enabled
5623 || (!ignore_word_threshold
5624 && match &query {
5625 Some(query) => query.chars().count() < completion_settings.words_min_length,
5626 None => completion_settings.words_min_length != 0,
5627 });
5628
5629 let (mut words, provider_responses) = match &provider {
5630 Some(provider) => {
5631 let provider_responses = provider.completions(
5632 buffer_excerpt_id,
5633 &buffer,
5634 buffer_position,
5635 completion_context,
5636 window,
5637 cx,
5638 );
5639
5640 let words = match (omit_word_completions, completion_settings.words) {
5641 (true, _) | (_, WordsCompletionMode::Disabled) => {
5642 Task::ready(BTreeMap::default())
5643 }
5644 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5645 .background_spawn(async move {
5646 buffer_snapshot.words_in_range(WordsQuery {
5647 fuzzy_contents: None,
5648 range: word_search_range,
5649 skip_digits,
5650 })
5651 }),
5652 };
5653
5654 (words, provider_responses)
5655 }
5656 None => {
5657 let words = if omit_word_completions {
5658 Task::ready(BTreeMap::default())
5659 } else {
5660 cx.background_spawn(async move {
5661 buffer_snapshot.words_in_range(WordsQuery {
5662 fuzzy_contents: None,
5663 range: word_search_range,
5664 skip_digits,
5665 })
5666 })
5667 };
5668 (words, Task::ready(Ok(Vec::new())))
5669 }
5670 };
5671
5672 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5673
5674 let id = post_inc(&mut self.next_completion_id);
5675 let task = cx.spawn_in(window, async move |editor, cx| {
5676 let Ok(()) = editor.update(cx, |this, _| {
5677 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5678 }) else {
5679 return;
5680 };
5681
5682 // TODO: Ideally completions from different sources would be selectively re-queried, so
5683 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5684 let mut completions = Vec::new();
5685 let mut is_incomplete = false;
5686 let mut display_options: Option<CompletionDisplayOptions> = None;
5687 if let Some(provider_responses) = provider_responses.await.log_err()
5688 && !provider_responses.is_empty()
5689 {
5690 for response in provider_responses {
5691 completions.extend(response.completions);
5692 is_incomplete = is_incomplete || response.is_incomplete;
5693 match display_options.as_mut() {
5694 None => {
5695 display_options = Some(response.display_options);
5696 }
5697 Some(options) => options.merge(&response.display_options),
5698 }
5699 }
5700 if completion_settings.words == WordsCompletionMode::Fallback {
5701 words = Task::ready(BTreeMap::default());
5702 }
5703 }
5704 let display_options = display_options.unwrap_or_default();
5705
5706 let mut words = words.await;
5707 if let Some(word_to_exclude) = &word_to_exclude {
5708 words.remove(word_to_exclude);
5709 }
5710 for lsp_completion in &completions {
5711 words.remove(&lsp_completion.new_text);
5712 }
5713 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5714 replace_range: word_replace_range.clone(),
5715 new_text: word.clone(),
5716 label: CodeLabel::plain(word, None),
5717 icon_path: None,
5718 documentation: None,
5719 source: CompletionSource::BufferWord {
5720 word_range,
5721 resolved: false,
5722 },
5723 insert_text_mode: Some(InsertTextMode::AS_IS),
5724 confirm: None,
5725 }));
5726
5727 let menu = if completions.is_empty() {
5728 None
5729 } else {
5730 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5731 let languages = editor
5732 .workspace
5733 .as_ref()
5734 .and_then(|(workspace, _)| workspace.upgrade())
5735 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5736 let menu = CompletionsMenu::new(
5737 id,
5738 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5739 sort_completions,
5740 show_completion_documentation,
5741 position,
5742 query.clone(),
5743 is_incomplete,
5744 buffer.clone(),
5745 completions.into(),
5746 display_options,
5747 snippet_sort_order,
5748 languages,
5749 language,
5750 cx,
5751 );
5752
5753 let query = if filter_completions { query } else { None };
5754 let matches_task = if let Some(query) = query {
5755 menu.do_async_filtering(query, cx)
5756 } else {
5757 Task::ready(menu.unfiltered_matches())
5758 };
5759 (menu, matches_task)
5760 }) else {
5761 return;
5762 };
5763
5764 let matches = matches_task.await;
5765
5766 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5767 // Newer menu already set, so exit.
5768 if let Some(CodeContextMenu::Completions(prev_menu)) =
5769 editor.context_menu.borrow().as_ref()
5770 && prev_menu.id > id
5771 {
5772 return;
5773 };
5774
5775 // Only valid to take prev_menu because it the new menu is immediately set
5776 // below, or the menu is hidden.
5777 if let Some(CodeContextMenu::Completions(prev_menu)) =
5778 editor.context_menu.borrow_mut().take()
5779 {
5780 let position_matches =
5781 if prev_menu.initial_position == menu.initial_position {
5782 true
5783 } else {
5784 let snapshot = editor.buffer.read(cx).read(cx);
5785 prev_menu.initial_position.to_offset(&snapshot)
5786 == menu.initial_position.to_offset(&snapshot)
5787 };
5788 if position_matches {
5789 // Preserve markdown cache before `set_filter_results` because it will
5790 // try to populate the documentation cache.
5791 menu.preserve_markdown_cache(prev_menu);
5792 }
5793 };
5794
5795 menu.set_filter_results(matches, provider, window, cx);
5796 }) else {
5797 return;
5798 };
5799
5800 menu.visible().then_some(menu)
5801 };
5802
5803 editor
5804 .update_in(cx, |editor, window, cx| {
5805 if editor.focus_handle.is_focused(window)
5806 && let Some(menu) = menu
5807 {
5808 *editor.context_menu.borrow_mut() =
5809 Some(CodeContextMenu::Completions(menu));
5810
5811 crate::hover_popover::hide_hover(editor, cx);
5812 if editor.show_edit_predictions_in_menu() {
5813 editor.update_visible_edit_prediction(window, cx);
5814 } else {
5815 editor.discard_edit_prediction(false, cx);
5816 }
5817
5818 cx.notify();
5819 return;
5820 }
5821
5822 if editor.completion_tasks.len() <= 1 {
5823 // If there are no more completion tasks and the last menu was empty, we should hide it.
5824 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5825 // If it was already hidden and we don't show edit predictions in the menu,
5826 // we should also show the edit prediction when available.
5827 if was_hidden && editor.show_edit_predictions_in_menu() {
5828 editor.update_visible_edit_prediction(window, cx);
5829 }
5830 }
5831 })
5832 .ok();
5833 });
5834
5835 self.completion_tasks.push((id, task));
5836 }
5837
5838 #[cfg(feature = "test-support")]
5839 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5840 let menu = self.context_menu.borrow();
5841 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5842 let completions = menu.completions.borrow();
5843 Some(completions.to_vec())
5844 } else {
5845 None
5846 }
5847 }
5848
5849 pub fn with_completions_menu_matching_id<R>(
5850 &self,
5851 id: CompletionId,
5852 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5853 ) -> R {
5854 let mut context_menu = self.context_menu.borrow_mut();
5855 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5856 return f(None);
5857 };
5858 if completions_menu.id != id {
5859 return f(None);
5860 }
5861 f(Some(completions_menu))
5862 }
5863
5864 pub fn confirm_completion(
5865 &mut self,
5866 action: &ConfirmCompletion,
5867 window: &mut Window,
5868 cx: &mut Context<Self>,
5869 ) -> Option<Task<Result<()>>> {
5870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5871 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5872 }
5873
5874 pub fn confirm_completion_insert(
5875 &mut self,
5876 _: &ConfirmCompletionInsert,
5877 window: &mut Window,
5878 cx: &mut Context<Self>,
5879 ) -> Option<Task<Result<()>>> {
5880 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5881 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5882 }
5883
5884 pub fn confirm_completion_replace(
5885 &mut self,
5886 _: &ConfirmCompletionReplace,
5887 window: &mut Window,
5888 cx: &mut Context<Self>,
5889 ) -> Option<Task<Result<()>>> {
5890 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5891 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5892 }
5893
5894 pub fn compose_completion(
5895 &mut self,
5896 action: &ComposeCompletion,
5897 window: &mut Window,
5898 cx: &mut Context<Self>,
5899 ) -> Option<Task<Result<()>>> {
5900 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5901 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5902 }
5903
5904 fn do_completion(
5905 &mut self,
5906 item_ix: Option<usize>,
5907 intent: CompletionIntent,
5908 window: &mut Window,
5909 cx: &mut Context<Editor>,
5910 ) -> Option<Task<Result<()>>> {
5911 use language::ToOffset as _;
5912
5913 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5914 else {
5915 return None;
5916 };
5917
5918 let candidate_id = {
5919 let entries = completions_menu.entries.borrow();
5920 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5921 if self.show_edit_predictions_in_menu() {
5922 self.discard_edit_prediction(true, cx);
5923 }
5924 mat.candidate_id
5925 };
5926
5927 let completion = completions_menu
5928 .completions
5929 .borrow()
5930 .get(candidate_id)?
5931 .clone();
5932 cx.stop_propagation();
5933
5934 let buffer_handle = completions_menu.buffer.clone();
5935
5936 let CompletionEdit {
5937 new_text,
5938 snippet,
5939 replace_range,
5940 } = process_completion_for_edit(
5941 &completion,
5942 intent,
5943 &buffer_handle,
5944 &completions_menu.initial_position.text_anchor,
5945 cx,
5946 );
5947
5948 let buffer = buffer_handle.read(cx);
5949 let snapshot = self.buffer.read(cx).snapshot(cx);
5950 let newest_anchor = self.selections.newest_anchor();
5951 let replace_range_multibuffer = {
5952 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5953 let multibuffer_anchor = snapshot
5954 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5955 .unwrap()
5956 ..snapshot
5957 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5958 .unwrap();
5959 multibuffer_anchor.start.to_offset(&snapshot)
5960 ..multibuffer_anchor.end.to_offset(&snapshot)
5961 };
5962 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5963 return None;
5964 }
5965
5966 let old_text = buffer
5967 .text_for_range(replace_range.clone())
5968 .collect::<String>();
5969 let lookbehind = newest_anchor
5970 .start
5971 .text_anchor
5972 .to_offset(buffer)
5973 .saturating_sub(replace_range.start);
5974 let lookahead = replace_range
5975 .end
5976 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5977 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5978 let suffix = &old_text[lookbehind.min(old_text.len())..];
5979
5980 let selections = self.selections.all::<usize>(cx);
5981 let mut ranges = Vec::new();
5982 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5983
5984 for selection in &selections {
5985 let range = if selection.id == newest_anchor.id {
5986 replace_range_multibuffer.clone()
5987 } else {
5988 let mut range = selection.range();
5989
5990 // if prefix is present, don't duplicate it
5991 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5992 range.start = range.start.saturating_sub(lookbehind);
5993
5994 // if suffix is also present, mimic the newest cursor and replace it
5995 if selection.id != newest_anchor.id
5996 && snapshot.contains_str_at(range.end, suffix)
5997 {
5998 range.end += lookahead;
5999 }
6000 }
6001 range
6002 };
6003
6004 ranges.push(range.clone());
6005
6006 if !self.linked_edit_ranges.is_empty() {
6007 let start_anchor = snapshot.anchor_before(range.start);
6008 let end_anchor = snapshot.anchor_after(range.end);
6009 if let Some(ranges) = self
6010 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6011 {
6012 for (buffer, edits) in ranges {
6013 linked_edits
6014 .entry(buffer.clone())
6015 .or_default()
6016 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6017 }
6018 }
6019 }
6020 }
6021
6022 let common_prefix_len = old_text
6023 .chars()
6024 .zip(new_text.chars())
6025 .take_while(|(a, b)| a == b)
6026 .map(|(a, _)| a.len_utf8())
6027 .sum::<usize>();
6028
6029 cx.emit(EditorEvent::InputHandled {
6030 utf16_range_to_replace: None,
6031 text: new_text[common_prefix_len..].into(),
6032 });
6033
6034 self.transact(window, cx, |editor, window, cx| {
6035 if let Some(mut snippet) = snippet {
6036 snippet.text = new_text.to_string();
6037 editor
6038 .insert_snippet(&ranges, snippet, window, cx)
6039 .log_err();
6040 } else {
6041 editor.buffer.update(cx, |multi_buffer, cx| {
6042 let auto_indent = match completion.insert_text_mode {
6043 Some(InsertTextMode::AS_IS) => None,
6044 _ => editor.autoindent_mode.clone(),
6045 };
6046 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6047 multi_buffer.edit(edits, auto_indent, cx);
6048 });
6049 }
6050 for (buffer, edits) in linked_edits {
6051 buffer.update(cx, |buffer, cx| {
6052 let snapshot = buffer.snapshot();
6053 let edits = edits
6054 .into_iter()
6055 .map(|(range, text)| {
6056 use text::ToPoint as TP;
6057 let end_point = TP::to_point(&range.end, &snapshot);
6058 let start_point = TP::to_point(&range.start, &snapshot);
6059 (start_point..end_point, text)
6060 })
6061 .sorted_by_key(|(range, _)| range.start);
6062 buffer.edit(edits, None, cx);
6063 })
6064 }
6065
6066 editor.refresh_edit_prediction(true, false, window, cx);
6067 });
6068 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6069
6070 let show_new_completions_on_confirm = completion
6071 .confirm
6072 .as_ref()
6073 .is_some_and(|confirm| confirm(intent, window, cx));
6074 if show_new_completions_on_confirm {
6075 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6076 }
6077
6078 let provider = self.completion_provider.as_ref()?;
6079 drop(completion);
6080 let apply_edits = provider.apply_additional_edits_for_completion(
6081 buffer_handle,
6082 completions_menu.completions.clone(),
6083 candidate_id,
6084 true,
6085 cx,
6086 );
6087
6088 let editor_settings = EditorSettings::get_global(cx);
6089 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6090 // After the code completion is finished, users often want to know what signatures are needed.
6091 // so we should automatically call signature_help
6092 self.show_signature_help(&ShowSignatureHelp, window, cx);
6093 }
6094
6095 Some(cx.foreground_executor().spawn(async move {
6096 apply_edits.await?;
6097 Ok(())
6098 }))
6099 }
6100
6101 pub fn toggle_code_actions(
6102 &mut self,
6103 action: &ToggleCodeActions,
6104 window: &mut Window,
6105 cx: &mut Context<Self>,
6106 ) {
6107 let quick_launch = action.quick_launch;
6108 let mut context_menu = self.context_menu.borrow_mut();
6109 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6110 if code_actions.deployed_from == action.deployed_from {
6111 // Toggle if we're selecting the same one
6112 *context_menu = None;
6113 cx.notify();
6114 return;
6115 } else {
6116 // Otherwise, clear it and start a new one
6117 *context_menu = None;
6118 cx.notify();
6119 }
6120 }
6121 drop(context_menu);
6122 let snapshot = self.snapshot(window, cx);
6123 let deployed_from = action.deployed_from.clone();
6124 let action = action.clone();
6125 self.completion_tasks.clear();
6126 self.discard_edit_prediction(false, cx);
6127
6128 let multibuffer_point = match &action.deployed_from {
6129 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6130 DisplayPoint::new(*row, 0).to_point(&snapshot)
6131 }
6132 _ => self.selections.newest::<Point>(cx).head(),
6133 };
6134 let Some((buffer, buffer_row)) = snapshot
6135 .buffer_snapshot
6136 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6137 .and_then(|(buffer_snapshot, range)| {
6138 self.buffer()
6139 .read(cx)
6140 .buffer(buffer_snapshot.remote_id())
6141 .map(|buffer| (buffer, range.start.row))
6142 })
6143 else {
6144 return;
6145 };
6146 let buffer_id = buffer.read(cx).remote_id();
6147 let tasks = self
6148 .tasks
6149 .get(&(buffer_id, buffer_row))
6150 .map(|t| Arc::new(t.to_owned()));
6151
6152 if !self.focus_handle.is_focused(window) {
6153 return;
6154 }
6155 let project = self.project.clone();
6156
6157 let code_actions_task = match deployed_from {
6158 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6159 _ => self.code_actions(buffer_row, window, cx),
6160 };
6161
6162 let runnable_task = match deployed_from {
6163 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6164 _ => {
6165 let mut task_context_task = Task::ready(None);
6166 if let Some(tasks) = &tasks
6167 && let Some(project) = project
6168 {
6169 task_context_task =
6170 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6171 }
6172
6173 cx.spawn_in(window, {
6174 let buffer = buffer.clone();
6175 async move |editor, cx| {
6176 let task_context = task_context_task.await;
6177
6178 let resolved_tasks =
6179 tasks
6180 .zip(task_context.clone())
6181 .map(|(tasks, task_context)| ResolvedTasks {
6182 templates: tasks.resolve(&task_context).collect(),
6183 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6184 multibuffer_point.row,
6185 tasks.column,
6186 )),
6187 });
6188 let debug_scenarios = editor
6189 .update(cx, |editor, cx| {
6190 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6191 })?
6192 .await;
6193 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6194 }
6195 })
6196 }
6197 };
6198
6199 cx.spawn_in(window, async move |editor, cx| {
6200 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6201 let code_actions = code_actions_task.await;
6202 let spawn_straight_away = quick_launch
6203 && resolved_tasks
6204 .as_ref()
6205 .is_some_and(|tasks| tasks.templates.len() == 1)
6206 && code_actions
6207 .as_ref()
6208 .is_none_or(|actions| actions.is_empty())
6209 && debug_scenarios.is_empty();
6210
6211 editor.update_in(cx, |editor, window, cx| {
6212 crate::hover_popover::hide_hover(editor, cx);
6213 let actions = CodeActionContents::new(
6214 resolved_tasks,
6215 code_actions,
6216 debug_scenarios,
6217 task_context.unwrap_or_default(),
6218 );
6219
6220 // Don't show the menu if there are no actions available
6221 if actions.is_empty() {
6222 cx.notify();
6223 return Task::ready(Ok(()));
6224 }
6225
6226 *editor.context_menu.borrow_mut() =
6227 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6228 buffer,
6229 actions,
6230 selected_item: Default::default(),
6231 scroll_handle: UniformListScrollHandle::default(),
6232 deployed_from,
6233 }));
6234 cx.notify();
6235 if spawn_straight_away
6236 && let Some(task) = editor.confirm_code_action(
6237 &ConfirmCodeAction { item_ix: Some(0) },
6238 window,
6239 cx,
6240 )
6241 {
6242 return task;
6243 }
6244
6245 Task::ready(Ok(()))
6246 })
6247 })
6248 .detach_and_log_err(cx);
6249 }
6250
6251 fn debug_scenarios(
6252 &mut self,
6253 resolved_tasks: &Option<ResolvedTasks>,
6254 buffer: &Entity<Buffer>,
6255 cx: &mut App,
6256 ) -> Task<Vec<task::DebugScenario>> {
6257 maybe!({
6258 let project = self.project()?;
6259 let dap_store = project.read(cx).dap_store();
6260 let mut scenarios = vec![];
6261 let resolved_tasks = resolved_tasks.as_ref()?;
6262 let buffer = buffer.read(cx);
6263 let language = buffer.language()?;
6264 let file = buffer.file();
6265 let debug_adapter = language_settings(language.name().into(), file, cx)
6266 .debuggers
6267 .first()
6268 .map(SharedString::from)
6269 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6270
6271 dap_store.update(cx, |dap_store, cx| {
6272 for (_, task) in &resolved_tasks.templates {
6273 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6274 task.original_task().clone(),
6275 debug_adapter.clone().into(),
6276 task.display_label().to_owned().into(),
6277 cx,
6278 );
6279 scenarios.push(maybe_scenario);
6280 }
6281 });
6282 Some(cx.background_spawn(async move {
6283 futures::future::join_all(scenarios)
6284 .await
6285 .into_iter()
6286 .flatten()
6287 .collect::<Vec<_>>()
6288 }))
6289 })
6290 .unwrap_or_else(|| Task::ready(vec![]))
6291 }
6292
6293 fn code_actions(
6294 &mut self,
6295 buffer_row: u32,
6296 window: &mut Window,
6297 cx: &mut Context<Self>,
6298 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6299 let mut task = self.code_actions_task.take();
6300 cx.spawn_in(window, async move |editor, cx| {
6301 while let Some(prev_task) = task {
6302 prev_task.await.log_err();
6303 task = editor
6304 .update(cx, |this, _| this.code_actions_task.take())
6305 .ok()?;
6306 }
6307
6308 editor
6309 .update(cx, |editor, cx| {
6310 editor
6311 .available_code_actions
6312 .clone()
6313 .and_then(|(location, code_actions)| {
6314 let snapshot = location.buffer.read(cx).snapshot();
6315 let point_range = location.range.to_point(&snapshot);
6316 let point_range = point_range.start.row..=point_range.end.row;
6317 if point_range.contains(&buffer_row) {
6318 Some(code_actions)
6319 } else {
6320 None
6321 }
6322 })
6323 })
6324 .ok()
6325 .flatten()
6326 })
6327 }
6328
6329 pub fn confirm_code_action(
6330 &mut self,
6331 action: &ConfirmCodeAction,
6332 window: &mut Window,
6333 cx: &mut Context<Self>,
6334 ) -> Option<Task<Result<()>>> {
6335 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6336
6337 let actions_menu =
6338 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6339 menu
6340 } else {
6341 return None;
6342 };
6343
6344 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6345 let action = actions_menu.actions.get(action_ix)?;
6346 let title = action.label();
6347 let buffer = actions_menu.buffer;
6348 let workspace = self.workspace()?;
6349
6350 match action {
6351 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6352 workspace.update(cx, |workspace, cx| {
6353 workspace.schedule_resolved_task(
6354 task_source_kind,
6355 resolved_task,
6356 false,
6357 window,
6358 cx,
6359 );
6360
6361 Some(Task::ready(Ok(())))
6362 })
6363 }
6364 CodeActionsItem::CodeAction {
6365 excerpt_id,
6366 action,
6367 provider,
6368 } => {
6369 let apply_code_action =
6370 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6371 let workspace = workspace.downgrade();
6372 Some(cx.spawn_in(window, async move |editor, cx| {
6373 let project_transaction = apply_code_action.await?;
6374 Self::open_project_transaction(
6375 &editor,
6376 workspace,
6377 project_transaction,
6378 title,
6379 cx,
6380 )
6381 .await
6382 }))
6383 }
6384 CodeActionsItem::DebugScenario(scenario) => {
6385 let context = actions_menu.actions.context;
6386
6387 workspace.update(cx, |workspace, cx| {
6388 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6389 workspace.start_debug_session(
6390 scenario,
6391 context,
6392 Some(buffer),
6393 None,
6394 window,
6395 cx,
6396 );
6397 });
6398 Some(Task::ready(Ok(())))
6399 }
6400 }
6401 }
6402
6403 pub async fn open_project_transaction(
6404 editor: &WeakEntity<Editor>,
6405 workspace: WeakEntity<Workspace>,
6406 transaction: ProjectTransaction,
6407 title: String,
6408 cx: &mut AsyncWindowContext,
6409 ) -> Result<()> {
6410 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6411 cx.update(|_, cx| {
6412 entries.sort_unstable_by_key(|(buffer, _)| {
6413 buffer.read(cx).file().map(|f| f.path().clone())
6414 });
6415 })?;
6416
6417 // If the project transaction's edits are all contained within this editor, then
6418 // avoid opening a new editor to display them.
6419
6420 if let Some((buffer, transaction)) = entries.first() {
6421 if entries.len() == 1 {
6422 let excerpt = editor.update(cx, |editor, cx| {
6423 editor
6424 .buffer()
6425 .read(cx)
6426 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6427 })?;
6428 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6429 && excerpted_buffer == *buffer
6430 {
6431 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6432 let excerpt_range = excerpt_range.to_offset(buffer);
6433 buffer
6434 .edited_ranges_for_transaction::<usize>(transaction)
6435 .all(|range| {
6436 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6437 })
6438 })?;
6439
6440 if all_edits_within_excerpt {
6441 return Ok(());
6442 }
6443 }
6444 }
6445 } else {
6446 return Ok(());
6447 }
6448
6449 let mut ranges_to_highlight = Vec::new();
6450 let excerpt_buffer = cx.new(|cx| {
6451 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6452 for (buffer_handle, transaction) in &entries {
6453 let edited_ranges = buffer_handle
6454 .read(cx)
6455 .edited_ranges_for_transaction::<Point>(transaction)
6456 .collect::<Vec<_>>();
6457 let (ranges, _) = multibuffer.set_excerpts_for_path(
6458 PathKey::for_buffer(buffer_handle, cx),
6459 buffer_handle.clone(),
6460 edited_ranges,
6461 multibuffer_context_lines(cx),
6462 cx,
6463 );
6464
6465 ranges_to_highlight.extend(ranges);
6466 }
6467 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6468 multibuffer
6469 })?;
6470
6471 workspace.update_in(cx, |workspace, window, cx| {
6472 let project = workspace.project().clone();
6473 let editor =
6474 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6475 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6476 editor.update(cx, |editor, cx| {
6477 editor.highlight_background::<Self>(
6478 &ranges_to_highlight,
6479 |theme| theme.colors().editor_highlighted_line_background,
6480 cx,
6481 );
6482 });
6483 })?;
6484
6485 Ok(())
6486 }
6487
6488 pub fn clear_code_action_providers(&mut self) {
6489 self.code_action_providers.clear();
6490 self.available_code_actions.take();
6491 }
6492
6493 pub fn add_code_action_provider(
6494 &mut self,
6495 provider: Rc<dyn CodeActionProvider>,
6496 window: &mut Window,
6497 cx: &mut Context<Self>,
6498 ) {
6499 if self
6500 .code_action_providers
6501 .iter()
6502 .any(|existing_provider| existing_provider.id() == provider.id())
6503 {
6504 return;
6505 }
6506
6507 self.code_action_providers.push(provider);
6508 self.refresh_code_actions(window, cx);
6509 }
6510
6511 pub fn remove_code_action_provider(
6512 &mut self,
6513 id: Arc<str>,
6514 window: &mut Window,
6515 cx: &mut Context<Self>,
6516 ) {
6517 self.code_action_providers
6518 .retain(|provider| provider.id() != id);
6519 self.refresh_code_actions(window, cx);
6520 }
6521
6522 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6523 !self.code_action_providers.is_empty()
6524 && EditorSettings::get_global(cx).toolbar.code_actions
6525 }
6526
6527 pub fn has_available_code_actions(&self) -> bool {
6528 self.available_code_actions
6529 .as_ref()
6530 .is_some_and(|(_, actions)| !actions.is_empty())
6531 }
6532
6533 fn render_inline_code_actions(
6534 &self,
6535 icon_size: ui::IconSize,
6536 display_row: DisplayRow,
6537 is_active: bool,
6538 cx: &mut Context<Self>,
6539 ) -> AnyElement {
6540 let show_tooltip = !self.context_menu_visible();
6541 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6542 .icon_size(icon_size)
6543 .shape(ui::IconButtonShape::Square)
6544 .icon_color(ui::Color::Hidden)
6545 .toggle_state(is_active)
6546 .when(show_tooltip, |this| {
6547 this.tooltip({
6548 let focus_handle = self.focus_handle.clone();
6549 move |window, cx| {
6550 Tooltip::for_action_in(
6551 "Toggle Code Actions",
6552 &ToggleCodeActions {
6553 deployed_from: None,
6554 quick_launch: false,
6555 },
6556 &focus_handle,
6557 window,
6558 cx,
6559 )
6560 }
6561 })
6562 })
6563 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6564 window.focus(&editor.focus_handle(cx));
6565 editor.toggle_code_actions(
6566 &crate::actions::ToggleCodeActions {
6567 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6568 display_row,
6569 )),
6570 quick_launch: false,
6571 },
6572 window,
6573 cx,
6574 );
6575 }))
6576 .into_any_element()
6577 }
6578
6579 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6580 &self.context_menu
6581 }
6582
6583 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6584 let newest_selection = self.selections.newest_anchor().clone();
6585 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6586 let buffer = self.buffer.read(cx);
6587 if newest_selection.head().diff_base_anchor.is_some() {
6588 return None;
6589 }
6590 let (start_buffer, start) =
6591 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6592 let (end_buffer, end) =
6593 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6594 if start_buffer != end_buffer {
6595 return None;
6596 }
6597
6598 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6599 cx.background_executor()
6600 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6601 .await;
6602
6603 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6604 let providers = this.code_action_providers.clone();
6605 let tasks = this
6606 .code_action_providers
6607 .iter()
6608 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6609 .collect::<Vec<_>>();
6610 (providers, tasks)
6611 })?;
6612
6613 let mut actions = Vec::new();
6614 for (provider, provider_actions) in
6615 providers.into_iter().zip(future::join_all(tasks).await)
6616 {
6617 if let Some(provider_actions) = provider_actions.log_err() {
6618 actions.extend(provider_actions.into_iter().map(|action| {
6619 AvailableCodeAction {
6620 excerpt_id: newest_selection.start.excerpt_id,
6621 action,
6622 provider: provider.clone(),
6623 }
6624 }));
6625 }
6626 }
6627
6628 this.update(cx, |this, cx| {
6629 this.available_code_actions = if actions.is_empty() {
6630 None
6631 } else {
6632 Some((
6633 Location {
6634 buffer: start_buffer,
6635 range: start..end,
6636 },
6637 actions.into(),
6638 ))
6639 };
6640 cx.notify();
6641 })
6642 }));
6643 None
6644 }
6645
6646 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6647 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6648 self.show_git_blame_inline = false;
6649
6650 self.show_git_blame_inline_delay_task =
6651 Some(cx.spawn_in(window, async move |this, cx| {
6652 cx.background_executor().timer(delay).await;
6653
6654 this.update(cx, |this, cx| {
6655 this.show_git_blame_inline = true;
6656 cx.notify();
6657 })
6658 .log_err();
6659 }));
6660 }
6661 }
6662
6663 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6664 let snapshot = self.snapshot(window, cx);
6665 let cursor = self.selections.newest::<Point>(cx).head();
6666 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6667 else {
6668 return;
6669 };
6670
6671 let Some(blame) = self.blame.as_ref() else {
6672 return;
6673 };
6674
6675 let row_info = RowInfo {
6676 buffer_id: Some(buffer.remote_id()),
6677 buffer_row: Some(point.row),
6678 ..Default::default()
6679 };
6680 let Some((buffer, blame_entry)) = blame
6681 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6682 .flatten()
6683 else {
6684 return;
6685 };
6686
6687 let anchor = self.selections.newest_anchor().head();
6688 let position = self.to_pixel_point(anchor, &snapshot, window);
6689 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6690 self.show_blame_popover(
6691 buffer,
6692 &blame_entry,
6693 position + last_bounds.origin,
6694 true,
6695 cx,
6696 );
6697 };
6698 }
6699
6700 fn show_blame_popover(
6701 &mut self,
6702 buffer: BufferId,
6703 blame_entry: &BlameEntry,
6704 position: gpui::Point<Pixels>,
6705 ignore_timeout: bool,
6706 cx: &mut Context<Self>,
6707 ) {
6708 if let Some(state) = &mut self.inline_blame_popover {
6709 state.hide_task.take();
6710 } else {
6711 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6712 let blame_entry = blame_entry.clone();
6713 let show_task = cx.spawn(async move |editor, cx| {
6714 if !ignore_timeout {
6715 cx.background_executor()
6716 .timer(std::time::Duration::from_millis(blame_popover_delay))
6717 .await;
6718 }
6719 editor
6720 .update(cx, |editor, cx| {
6721 editor.inline_blame_popover_show_task.take();
6722 let Some(blame) = editor.blame.as_ref() else {
6723 return;
6724 };
6725 let blame = blame.read(cx);
6726 let details = blame.details_for_entry(buffer, &blame_entry);
6727 let markdown = cx.new(|cx| {
6728 Markdown::new(
6729 details
6730 .as_ref()
6731 .map(|message| message.message.clone())
6732 .unwrap_or_default(),
6733 None,
6734 None,
6735 cx,
6736 )
6737 });
6738 editor.inline_blame_popover = Some(InlineBlamePopover {
6739 position,
6740 hide_task: None,
6741 popover_bounds: None,
6742 popover_state: InlineBlamePopoverState {
6743 scroll_handle: ScrollHandle::new(),
6744 commit_message: details,
6745 markdown,
6746 },
6747 keyboard_grace: ignore_timeout,
6748 });
6749 cx.notify();
6750 })
6751 .ok();
6752 });
6753 self.inline_blame_popover_show_task = Some(show_task);
6754 }
6755 }
6756
6757 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6758 self.inline_blame_popover_show_task.take();
6759 if let Some(state) = &mut self.inline_blame_popover {
6760 let hide_task = cx.spawn(async move |editor, cx| {
6761 cx.background_executor()
6762 .timer(std::time::Duration::from_millis(100))
6763 .await;
6764 editor
6765 .update(cx, |editor, cx| {
6766 editor.inline_blame_popover.take();
6767 cx.notify();
6768 })
6769 .ok();
6770 });
6771 state.hide_task = Some(hide_task);
6772 }
6773 }
6774
6775 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6776 if self.pending_rename.is_some() {
6777 return None;
6778 }
6779
6780 let provider = self.semantics_provider.clone()?;
6781 let buffer = self.buffer.read(cx);
6782 let newest_selection = self.selections.newest_anchor().clone();
6783 let cursor_position = newest_selection.head();
6784 let (cursor_buffer, cursor_buffer_position) =
6785 buffer.text_anchor_for_position(cursor_position, cx)?;
6786 let (tail_buffer, tail_buffer_position) =
6787 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6788 if cursor_buffer != tail_buffer {
6789 return None;
6790 }
6791
6792 let snapshot = cursor_buffer.read(cx).snapshot();
6793 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6794 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6795 if start_word_range != end_word_range {
6796 self.document_highlights_task.take();
6797 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6798 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6799 return None;
6800 }
6801
6802 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6803 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6804 cx.background_executor()
6805 .timer(Duration::from_millis(debounce))
6806 .await;
6807
6808 let highlights = if let Some(highlights) = cx
6809 .update(|cx| {
6810 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6811 })
6812 .ok()
6813 .flatten()
6814 {
6815 highlights.await.log_err()
6816 } else {
6817 None
6818 };
6819
6820 if let Some(highlights) = highlights {
6821 this.update(cx, |this, cx| {
6822 if this.pending_rename.is_some() {
6823 return;
6824 }
6825
6826 let buffer = this.buffer.read(cx);
6827 if buffer
6828 .text_anchor_for_position(cursor_position, cx)
6829 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6830 {
6831 return;
6832 }
6833
6834 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6835 let mut write_ranges = Vec::new();
6836 let mut read_ranges = Vec::new();
6837 for highlight in highlights {
6838 let buffer_id = cursor_buffer.read(cx).remote_id();
6839 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6840 {
6841 let start = highlight
6842 .range
6843 .start
6844 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6845 let end = highlight
6846 .range
6847 .end
6848 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6849 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6850 continue;
6851 }
6852
6853 let range = Anchor {
6854 buffer_id: Some(buffer_id),
6855 excerpt_id,
6856 text_anchor: start,
6857 diff_base_anchor: None,
6858 }..Anchor {
6859 buffer_id: Some(buffer_id),
6860 excerpt_id,
6861 text_anchor: end,
6862 diff_base_anchor: None,
6863 };
6864 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6865 write_ranges.push(range);
6866 } else {
6867 read_ranges.push(range);
6868 }
6869 }
6870 }
6871
6872 this.highlight_background::<DocumentHighlightRead>(
6873 &read_ranges,
6874 |theme| theme.colors().editor_document_highlight_read_background,
6875 cx,
6876 );
6877 this.highlight_background::<DocumentHighlightWrite>(
6878 &write_ranges,
6879 |theme| theme.colors().editor_document_highlight_write_background,
6880 cx,
6881 );
6882 cx.notify();
6883 })
6884 .log_err();
6885 }
6886 }));
6887 None
6888 }
6889
6890 fn prepare_highlight_query_from_selection(
6891 &mut self,
6892 cx: &mut Context<Editor>,
6893 ) -> Option<(String, Range<Anchor>)> {
6894 if matches!(self.mode, EditorMode::SingleLine) {
6895 return None;
6896 }
6897 if !EditorSettings::get_global(cx).selection_highlight {
6898 return None;
6899 }
6900 if self.selections.count() != 1 || self.selections.line_mode {
6901 return None;
6902 }
6903 let selection = self.selections.newest::<Point>(cx);
6904 if selection.is_empty() || selection.start.row != selection.end.row {
6905 return None;
6906 }
6907 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6908 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6909 let query = multi_buffer_snapshot
6910 .text_for_range(selection_anchor_range.clone())
6911 .collect::<String>();
6912 if query.trim().is_empty() {
6913 return None;
6914 }
6915 Some((query, selection_anchor_range))
6916 }
6917
6918 fn update_selection_occurrence_highlights(
6919 &mut self,
6920 query_text: String,
6921 query_range: Range<Anchor>,
6922 multi_buffer_range_to_query: Range<Point>,
6923 use_debounce: bool,
6924 window: &mut Window,
6925 cx: &mut Context<Editor>,
6926 ) -> Task<()> {
6927 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6928 cx.spawn_in(window, async move |editor, cx| {
6929 if use_debounce {
6930 cx.background_executor()
6931 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6932 .await;
6933 }
6934 let match_task = cx.background_spawn(async move {
6935 let buffer_ranges = multi_buffer_snapshot
6936 .range_to_buffer_ranges(multi_buffer_range_to_query)
6937 .into_iter()
6938 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6939 let mut match_ranges = Vec::new();
6940 let Ok(regex) = project::search::SearchQuery::text(
6941 query_text.clone(),
6942 false,
6943 false,
6944 false,
6945 Default::default(),
6946 Default::default(),
6947 false,
6948 None,
6949 ) else {
6950 return Vec::default();
6951 };
6952 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6953 match_ranges.extend(
6954 regex
6955 .search(buffer_snapshot, Some(search_range.clone()))
6956 .await
6957 .into_iter()
6958 .filter_map(|match_range| {
6959 let match_start = buffer_snapshot
6960 .anchor_after(search_range.start + match_range.start);
6961 let match_end = buffer_snapshot
6962 .anchor_before(search_range.start + match_range.end);
6963 let match_anchor_range = Anchor::range_in_buffer(
6964 excerpt_id,
6965 buffer_snapshot.remote_id(),
6966 match_start..match_end,
6967 );
6968 (match_anchor_range != query_range).then_some(match_anchor_range)
6969 }),
6970 );
6971 }
6972 match_ranges
6973 });
6974 let match_ranges = match_task.await;
6975 editor
6976 .update_in(cx, |editor, _, cx| {
6977 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6978 if !match_ranges.is_empty() {
6979 editor.highlight_background::<SelectedTextHighlight>(
6980 &match_ranges,
6981 |theme| theme.colors().editor_document_highlight_bracket_background,
6982 cx,
6983 )
6984 }
6985 })
6986 .log_err();
6987 })
6988 }
6989
6990 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6991 struct NewlineFold;
6992 let type_id = std::any::TypeId::of::<NewlineFold>();
6993 if !self.mode.is_single_line() {
6994 return;
6995 }
6996 let snapshot = self.snapshot(window, cx);
6997 if snapshot.buffer_snapshot.max_point().row == 0 {
6998 return;
6999 }
7000 let task = cx.background_spawn(async move {
7001 let new_newlines = snapshot
7002 .buffer_chars_at(0)
7003 .filter_map(|(c, i)| {
7004 if c == '\n' {
7005 Some(
7006 snapshot.buffer_snapshot.anchor_after(i)
7007 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7008 )
7009 } else {
7010 None
7011 }
7012 })
7013 .collect::<Vec<_>>();
7014 let existing_newlines = snapshot
7015 .folds_in_range(0..snapshot.buffer_snapshot.len())
7016 .filter_map(|fold| {
7017 if fold.placeholder.type_tag == Some(type_id) {
7018 Some(fold.range.start..fold.range.end)
7019 } else {
7020 None
7021 }
7022 })
7023 .collect::<Vec<_>>();
7024
7025 (new_newlines, existing_newlines)
7026 });
7027 self.folding_newlines = cx.spawn(async move |this, cx| {
7028 let (new_newlines, existing_newlines) = task.await;
7029 if new_newlines == existing_newlines {
7030 return;
7031 }
7032 let placeholder = FoldPlaceholder {
7033 render: Arc::new(move |_, _, cx| {
7034 div()
7035 .bg(cx.theme().status().hint_background)
7036 .border_b_1()
7037 .size_full()
7038 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7039 .border_color(cx.theme().status().hint)
7040 .child("\\n")
7041 .into_any()
7042 }),
7043 constrain_width: false,
7044 merge_adjacent: false,
7045 type_tag: Some(type_id),
7046 };
7047 let creases = new_newlines
7048 .into_iter()
7049 .map(|range| Crease::simple(range, placeholder.clone()))
7050 .collect();
7051 this.update(cx, |this, cx| {
7052 this.display_map.update(cx, |display_map, cx| {
7053 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7054 display_map.fold(creases, cx);
7055 });
7056 })
7057 .ok();
7058 });
7059 }
7060
7061 fn refresh_selected_text_highlights(
7062 &mut self,
7063 on_buffer_edit: bool,
7064 window: &mut Window,
7065 cx: &mut Context<Editor>,
7066 ) {
7067 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7068 else {
7069 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7070 self.quick_selection_highlight_task.take();
7071 self.debounced_selection_highlight_task.take();
7072 return;
7073 };
7074 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7075 if on_buffer_edit
7076 || self
7077 .quick_selection_highlight_task
7078 .as_ref()
7079 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7080 {
7081 let multi_buffer_visible_start = self
7082 .scroll_manager
7083 .anchor()
7084 .anchor
7085 .to_point(&multi_buffer_snapshot);
7086 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7087 multi_buffer_visible_start
7088 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7089 Bias::Left,
7090 );
7091 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7092 self.quick_selection_highlight_task = Some((
7093 query_range.clone(),
7094 self.update_selection_occurrence_highlights(
7095 query_text.clone(),
7096 query_range.clone(),
7097 multi_buffer_visible_range,
7098 false,
7099 window,
7100 cx,
7101 ),
7102 ));
7103 }
7104 if on_buffer_edit
7105 || self
7106 .debounced_selection_highlight_task
7107 .as_ref()
7108 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7109 {
7110 let multi_buffer_start = multi_buffer_snapshot
7111 .anchor_before(0)
7112 .to_point(&multi_buffer_snapshot);
7113 let multi_buffer_end = multi_buffer_snapshot
7114 .anchor_after(multi_buffer_snapshot.len())
7115 .to_point(&multi_buffer_snapshot);
7116 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7117 self.debounced_selection_highlight_task = Some((
7118 query_range.clone(),
7119 self.update_selection_occurrence_highlights(
7120 query_text,
7121 query_range,
7122 multi_buffer_full_range,
7123 true,
7124 window,
7125 cx,
7126 ),
7127 ));
7128 }
7129 }
7130
7131 pub fn refresh_edit_prediction(
7132 &mut self,
7133 debounce: bool,
7134 user_requested: bool,
7135 window: &mut Window,
7136 cx: &mut Context<Self>,
7137 ) -> Option<()> {
7138 if DisableAiSettings::get_global(cx).disable_ai {
7139 return None;
7140 }
7141
7142 let provider = self.edit_prediction_provider()?;
7143 let cursor = self.selections.newest_anchor().head();
7144 let (buffer, cursor_buffer_position) =
7145 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7146
7147 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7148 self.discard_edit_prediction(false, cx);
7149 return None;
7150 }
7151
7152 if !user_requested
7153 && (!self.should_show_edit_predictions()
7154 || !self.is_focused(window)
7155 || buffer.read(cx).is_empty())
7156 {
7157 self.discard_edit_prediction(false, cx);
7158 return None;
7159 }
7160
7161 self.update_visible_edit_prediction(window, cx);
7162 provider.refresh(
7163 self.project.clone(),
7164 buffer,
7165 cursor_buffer_position,
7166 debounce,
7167 cx,
7168 );
7169 Some(())
7170 }
7171
7172 fn show_edit_predictions_in_menu(&self) -> bool {
7173 match self.edit_prediction_settings {
7174 EditPredictionSettings::Disabled => false,
7175 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7176 }
7177 }
7178
7179 pub fn edit_predictions_enabled(&self) -> bool {
7180 match self.edit_prediction_settings {
7181 EditPredictionSettings::Disabled => false,
7182 EditPredictionSettings::Enabled { .. } => true,
7183 }
7184 }
7185
7186 fn edit_prediction_requires_modifier(&self) -> bool {
7187 match self.edit_prediction_settings {
7188 EditPredictionSettings::Disabled => false,
7189 EditPredictionSettings::Enabled {
7190 preview_requires_modifier,
7191 ..
7192 } => preview_requires_modifier,
7193 }
7194 }
7195
7196 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7197 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7198 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7199 self.discard_edit_prediction(false, cx);
7200 } else {
7201 let selection = self.selections.newest_anchor();
7202 let cursor = selection.head();
7203
7204 if let Some((buffer, cursor_buffer_position)) =
7205 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7206 {
7207 self.edit_prediction_settings =
7208 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7209 }
7210 }
7211 }
7212
7213 fn edit_prediction_settings_at_position(
7214 &self,
7215 buffer: &Entity<Buffer>,
7216 buffer_position: language::Anchor,
7217 cx: &App,
7218 ) -> EditPredictionSettings {
7219 if !self.mode.is_full()
7220 || !self.show_edit_predictions_override.unwrap_or(true)
7221 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7222 {
7223 return EditPredictionSettings::Disabled;
7224 }
7225
7226 let buffer = buffer.read(cx);
7227
7228 let file = buffer.file();
7229
7230 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7231 return EditPredictionSettings::Disabled;
7232 };
7233
7234 let by_provider = matches!(
7235 self.menu_edit_predictions_policy,
7236 MenuEditPredictionsPolicy::ByProvider
7237 );
7238
7239 let show_in_menu = by_provider
7240 && self
7241 .edit_prediction_provider
7242 .as_ref()
7243 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7244
7245 let preview_requires_modifier =
7246 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7247
7248 EditPredictionSettings::Enabled {
7249 show_in_menu,
7250 preview_requires_modifier,
7251 }
7252 }
7253
7254 fn should_show_edit_predictions(&self) -> bool {
7255 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7256 }
7257
7258 pub fn edit_prediction_preview_is_active(&self) -> bool {
7259 matches!(
7260 self.edit_prediction_preview,
7261 EditPredictionPreview::Active { .. }
7262 )
7263 }
7264
7265 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7266 let cursor = self.selections.newest_anchor().head();
7267 if let Some((buffer, cursor_position)) =
7268 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7269 {
7270 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7271 } else {
7272 false
7273 }
7274 }
7275
7276 pub fn supports_minimap(&self, cx: &App) -> bool {
7277 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7278 }
7279
7280 fn edit_predictions_enabled_in_buffer(
7281 &self,
7282 buffer: &Entity<Buffer>,
7283 buffer_position: language::Anchor,
7284 cx: &App,
7285 ) -> bool {
7286 maybe!({
7287 if self.read_only(cx) {
7288 return Some(false);
7289 }
7290 let provider = self.edit_prediction_provider()?;
7291 if !provider.is_enabled(buffer, buffer_position, cx) {
7292 return Some(false);
7293 }
7294 let buffer = buffer.read(cx);
7295 let Some(file) = buffer.file() else {
7296 return Some(true);
7297 };
7298 let settings = all_language_settings(Some(file), cx);
7299 Some(settings.edit_predictions_enabled_for_file(file, cx))
7300 })
7301 .unwrap_or(false)
7302 }
7303
7304 fn cycle_edit_prediction(
7305 &mut self,
7306 direction: Direction,
7307 window: &mut Window,
7308 cx: &mut Context<Self>,
7309 ) -> Option<()> {
7310 let provider = self.edit_prediction_provider()?;
7311 let cursor = self.selections.newest_anchor().head();
7312 let (buffer, cursor_buffer_position) =
7313 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7314 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7315 return None;
7316 }
7317
7318 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7319 self.update_visible_edit_prediction(window, cx);
7320
7321 Some(())
7322 }
7323
7324 pub fn show_edit_prediction(
7325 &mut self,
7326 _: &ShowEditPrediction,
7327 window: &mut Window,
7328 cx: &mut Context<Self>,
7329 ) {
7330 if !self.has_active_edit_prediction() {
7331 self.refresh_edit_prediction(false, true, window, cx);
7332 return;
7333 }
7334
7335 self.update_visible_edit_prediction(window, cx);
7336 }
7337
7338 pub fn display_cursor_names(
7339 &mut self,
7340 _: &DisplayCursorNames,
7341 window: &mut Window,
7342 cx: &mut Context<Self>,
7343 ) {
7344 self.show_cursor_names(window, cx);
7345 }
7346
7347 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7348 self.show_cursor_names = true;
7349 cx.notify();
7350 cx.spawn_in(window, async move |this, cx| {
7351 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7352 this.update(cx, |this, cx| {
7353 this.show_cursor_names = false;
7354 cx.notify()
7355 })
7356 .ok()
7357 })
7358 .detach();
7359 }
7360
7361 pub fn next_edit_prediction(
7362 &mut self,
7363 _: &NextEditPrediction,
7364 window: &mut Window,
7365 cx: &mut Context<Self>,
7366 ) {
7367 if self.has_active_edit_prediction() {
7368 self.cycle_edit_prediction(Direction::Next, window, cx);
7369 } else {
7370 let is_copilot_disabled = self
7371 .refresh_edit_prediction(false, true, window, cx)
7372 .is_none();
7373 if is_copilot_disabled {
7374 cx.propagate();
7375 }
7376 }
7377 }
7378
7379 pub fn previous_edit_prediction(
7380 &mut self,
7381 _: &PreviousEditPrediction,
7382 window: &mut Window,
7383 cx: &mut Context<Self>,
7384 ) {
7385 if self.has_active_edit_prediction() {
7386 self.cycle_edit_prediction(Direction::Prev, window, cx);
7387 } else {
7388 let is_copilot_disabled = self
7389 .refresh_edit_prediction(false, true, window, cx)
7390 .is_none();
7391 if is_copilot_disabled {
7392 cx.propagate();
7393 }
7394 }
7395 }
7396
7397 pub fn accept_edit_prediction(
7398 &mut self,
7399 _: &AcceptEditPrediction,
7400 window: &mut Window,
7401 cx: &mut Context<Self>,
7402 ) {
7403 if self.show_edit_predictions_in_menu() {
7404 self.hide_context_menu(window, cx);
7405 }
7406
7407 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7408 return;
7409 };
7410
7411 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7412
7413 match &active_edit_prediction.completion {
7414 EditPrediction::Move { target, .. } => {
7415 let target = *target;
7416
7417 if let Some(position_map) = &self.last_position_map {
7418 if position_map
7419 .visible_row_range
7420 .contains(&target.to_display_point(&position_map.snapshot).row())
7421 || !self.edit_prediction_requires_modifier()
7422 {
7423 self.unfold_ranges(&[target..target], true, false, cx);
7424 // Note that this is also done in vim's handler of the Tab action.
7425 self.change_selections(
7426 SelectionEffects::scroll(Autoscroll::newest()),
7427 window,
7428 cx,
7429 |selections| {
7430 selections.select_anchor_ranges([target..target]);
7431 },
7432 );
7433 self.clear_row_highlights::<EditPredictionPreview>();
7434
7435 self.edit_prediction_preview
7436 .set_previous_scroll_position(None);
7437 } else {
7438 self.edit_prediction_preview
7439 .set_previous_scroll_position(Some(
7440 position_map.snapshot.scroll_anchor,
7441 ));
7442
7443 self.highlight_rows::<EditPredictionPreview>(
7444 target..target,
7445 cx.theme().colors().editor_highlighted_line_background,
7446 RowHighlightOptions {
7447 autoscroll: true,
7448 ..Default::default()
7449 },
7450 cx,
7451 );
7452 self.request_autoscroll(Autoscroll::fit(), cx);
7453 }
7454 }
7455 }
7456 EditPrediction::Edit { edits, .. } => {
7457 if let Some(provider) = self.edit_prediction_provider() {
7458 provider.accept(cx);
7459 }
7460
7461 // Store the transaction ID and selections before applying the edit
7462 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7463
7464 let snapshot = self.buffer.read(cx).snapshot(cx);
7465 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7466
7467 self.buffer.update(cx, |buffer, cx| {
7468 buffer.edit(edits.iter().cloned(), None, cx)
7469 });
7470
7471 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7472 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7473 });
7474
7475 let selections = self.selections.disjoint_anchors();
7476 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7477 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7478 if has_new_transaction {
7479 self.selection_history
7480 .insert_transaction(transaction_id_now, selections);
7481 }
7482 }
7483
7484 self.update_visible_edit_prediction(window, cx);
7485 if self.active_edit_prediction.is_none() {
7486 self.refresh_edit_prediction(true, true, window, cx);
7487 }
7488
7489 cx.notify();
7490 }
7491 }
7492
7493 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7494 }
7495
7496 pub fn accept_partial_edit_prediction(
7497 &mut self,
7498 _: &AcceptPartialEditPrediction,
7499 window: &mut Window,
7500 cx: &mut Context<Self>,
7501 ) {
7502 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7503 return;
7504 };
7505 if self.selections.count() != 1 {
7506 return;
7507 }
7508
7509 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7510
7511 match &active_edit_prediction.completion {
7512 EditPrediction::Move { target, .. } => {
7513 let target = *target;
7514 self.change_selections(
7515 SelectionEffects::scroll(Autoscroll::newest()),
7516 window,
7517 cx,
7518 |selections| {
7519 selections.select_anchor_ranges([target..target]);
7520 },
7521 );
7522 }
7523 EditPrediction::Edit { edits, .. } => {
7524 // Find an insertion that starts at the cursor position.
7525 let snapshot = self.buffer.read(cx).snapshot(cx);
7526 let cursor_offset = self.selections.newest::<usize>(cx).head();
7527 let insertion = edits.iter().find_map(|(range, text)| {
7528 let range = range.to_offset(&snapshot);
7529 if range.is_empty() && range.start == cursor_offset {
7530 Some(text)
7531 } else {
7532 None
7533 }
7534 });
7535
7536 if let Some(text) = insertion {
7537 let mut partial_completion = text
7538 .chars()
7539 .by_ref()
7540 .take_while(|c| c.is_alphabetic())
7541 .collect::<String>();
7542 if partial_completion.is_empty() {
7543 partial_completion = text
7544 .chars()
7545 .by_ref()
7546 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7547 .collect::<String>();
7548 }
7549
7550 cx.emit(EditorEvent::InputHandled {
7551 utf16_range_to_replace: None,
7552 text: partial_completion.clone().into(),
7553 });
7554
7555 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7556
7557 self.refresh_edit_prediction(true, true, window, cx);
7558 cx.notify();
7559 } else {
7560 self.accept_edit_prediction(&Default::default(), window, cx);
7561 }
7562 }
7563 }
7564 }
7565
7566 fn discard_edit_prediction(
7567 &mut self,
7568 should_report_edit_prediction_event: bool,
7569 cx: &mut Context<Self>,
7570 ) -> bool {
7571 if should_report_edit_prediction_event {
7572 let completion_id = self
7573 .active_edit_prediction
7574 .as_ref()
7575 .and_then(|active_completion| active_completion.completion_id.clone());
7576
7577 self.report_edit_prediction_event(completion_id, false, cx);
7578 }
7579
7580 if let Some(provider) = self.edit_prediction_provider() {
7581 provider.discard(cx);
7582 }
7583
7584 self.take_active_edit_prediction(cx)
7585 }
7586
7587 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7588 let Some(provider) = self.edit_prediction_provider() else {
7589 return;
7590 };
7591
7592 let Some((_, buffer, _)) = self
7593 .buffer
7594 .read(cx)
7595 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7596 else {
7597 return;
7598 };
7599
7600 let extension = buffer
7601 .read(cx)
7602 .file()
7603 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7604
7605 let event_type = match accepted {
7606 true => "Edit Prediction Accepted",
7607 false => "Edit Prediction Discarded",
7608 };
7609 telemetry::event!(
7610 event_type,
7611 provider = provider.name(),
7612 prediction_id = id,
7613 suggestion_accepted = accepted,
7614 file_extension = extension,
7615 );
7616 }
7617
7618 pub fn has_active_edit_prediction(&self) -> bool {
7619 self.active_edit_prediction.is_some()
7620 }
7621
7622 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7623 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7624 return false;
7625 };
7626
7627 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7628 self.clear_highlights::<EditPredictionHighlight>(cx);
7629 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7630 true
7631 }
7632
7633 /// Returns true when we're displaying the edit prediction popover below the cursor
7634 /// like we are not previewing and the LSP autocomplete menu is visible
7635 /// or we are in `when_holding_modifier` mode.
7636 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7637 if self.edit_prediction_preview_is_active()
7638 || !self.show_edit_predictions_in_menu()
7639 || !self.edit_predictions_enabled()
7640 {
7641 return false;
7642 }
7643
7644 if self.has_visible_completions_menu() {
7645 return true;
7646 }
7647
7648 has_completion && self.edit_prediction_requires_modifier()
7649 }
7650
7651 fn handle_modifiers_changed(
7652 &mut self,
7653 modifiers: Modifiers,
7654 position_map: &PositionMap,
7655 window: &mut Window,
7656 cx: &mut Context<Self>,
7657 ) {
7658 if self.show_edit_predictions_in_menu() {
7659 self.update_edit_prediction_preview(&modifiers, window, cx);
7660 }
7661
7662 self.update_selection_mode(&modifiers, position_map, window, cx);
7663
7664 let mouse_position = window.mouse_position();
7665 if !position_map.text_hitbox.is_hovered(window) {
7666 return;
7667 }
7668
7669 self.update_hovered_link(
7670 position_map.point_for_position(mouse_position),
7671 &position_map.snapshot,
7672 modifiers,
7673 window,
7674 cx,
7675 )
7676 }
7677
7678 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7679 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7680 if invert {
7681 match multi_cursor_setting {
7682 MultiCursorModifier::Alt => modifiers.alt,
7683 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7684 }
7685 } else {
7686 match multi_cursor_setting {
7687 MultiCursorModifier::Alt => modifiers.secondary(),
7688 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7689 }
7690 }
7691 }
7692
7693 fn columnar_selection_mode(
7694 modifiers: &Modifiers,
7695 cx: &mut Context<Self>,
7696 ) -> Option<ColumnarMode> {
7697 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7698 if Self::multi_cursor_modifier(false, modifiers, cx) {
7699 Some(ColumnarMode::FromMouse)
7700 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7701 Some(ColumnarMode::FromSelection)
7702 } else {
7703 None
7704 }
7705 } else {
7706 None
7707 }
7708 }
7709
7710 fn update_selection_mode(
7711 &mut self,
7712 modifiers: &Modifiers,
7713 position_map: &PositionMap,
7714 window: &mut Window,
7715 cx: &mut Context<Self>,
7716 ) {
7717 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7718 return;
7719 };
7720 if self.selections.pending.is_none() {
7721 return;
7722 }
7723
7724 let mouse_position = window.mouse_position();
7725 let point_for_position = position_map.point_for_position(mouse_position);
7726 let position = point_for_position.previous_valid;
7727
7728 self.select(
7729 SelectPhase::BeginColumnar {
7730 position,
7731 reset: false,
7732 mode,
7733 goal_column: point_for_position.exact_unclipped.column(),
7734 },
7735 window,
7736 cx,
7737 );
7738 }
7739
7740 fn update_edit_prediction_preview(
7741 &mut self,
7742 modifiers: &Modifiers,
7743 window: &mut Window,
7744 cx: &mut Context<Self>,
7745 ) {
7746 let mut modifiers_held = false;
7747 if let Some(accept_keystroke) = self
7748 .accept_edit_prediction_keybind(false, window, cx)
7749 .keystroke()
7750 {
7751 modifiers_held = modifiers_held
7752 || (accept_keystroke.modifiers() == modifiers
7753 && accept_keystroke.modifiers().modified());
7754 };
7755 if let Some(accept_partial_keystroke) = self
7756 .accept_edit_prediction_keybind(true, window, cx)
7757 .keystroke()
7758 {
7759 modifiers_held = modifiers_held
7760 || (accept_partial_keystroke.modifiers() == modifiers
7761 && accept_partial_keystroke.modifiers().modified());
7762 }
7763
7764 if modifiers_held {
7765 if matches!(
7766 self.edit_prediction_preview,
7767 EditPredictionPreview::Inactive { .. }
7768 ) {
7769 self.edit_prediction_preview = EditPredictionPreview::Active {
7770 previous_scroll_position: None,
7771 since: Instant::now(),
7772 };
7773
7774 self.update_visible_edit_prediction(window, cx);
7775 cx.notify();
7776 }
7777 } else if let EditPredictionPreview::Active {
7778 previous_scroll_position,
7779 since,
7780 } = self.edit_prediction_preview
7781 {
7782 if let (Some(previous_scroll_position), Some(position_map)) =
7783 (previous_scroll_position, self.last_position_map.as_ref())
7784 {
7785 self.set_scroll_position(
7786 previous_scroll_position
7787 .scroll_position(&position_map.snapshot.display_snapshot),
7788 window,
7789 cx,
7790 );
7791 }
7792
7793 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7794 released_too_fast: since.elapsed() < Duration::from_millis(200),
7795 };
7796 self.clear_row_highlights::<EditPredictionPreview>();
7797 self.update_visible_edit_prediction(window, cx);
7798 cx.notify();
7799 }
7800 }
7801
7802 fn update_visible_edit_prediction(
7803 &mut self,
7804 _window: &mut Window,
7805 cx: &mut Context<Self>,
7806 ) -> Option<()> {
7807 if DisableAiSettings::get_global(cx).disable_ai {
7808 return None;
7809 }
7810
7811 if self.ime_transaction.is_some() {
7812 self.discard_edit_prediction(false, cx);
7813 return None;
7814 }
7815
7816 let selection = self.selections.newest_anchor();
7817 let cursor = selection.head();
7818 let multibuffer = self.buffer.read(cx).snapshot(cx);
7819 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7820 let excerpt_id = cursor.excerpt_id;
7821
7822 let show_in_menu = self.show_edit_predictions_in_menu();
7823 let completions_menu_has_precedence = !show_in_menu
7824 && (self.context_menu.borrow().is_some()
7825 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7826
7827 if completions_menu_has_precedence
7828 || !offset_selection.is_empty()
7829 || self
7830 .active_edit_prediction
7831 .as_ref()
7832 .is_some_and(|completion| {
7833 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7834 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7835 !invalidation_range.contains(&offset_selection.head())
7836 })
7837 {
7838 self.discard_edit_prediction(false, cx);
7839 return None;
7840 }
7841
7842 self.take_active_edit_prediction(cx);
7843 let Some(provider) = self.edit_prediction_provider() else {
7844 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7845 return None;
7846 };
7847
7848 let (buffer, cursor_buffer_position) =
7849 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7850
7851 self.edit_prediction_settings =
7852 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7853
7854 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7855 self.discard_edit_prediction(false, cx);
7856 return None;
7857 };
7858
7859 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7860
7861 if self.edit_prediction_indent_conflict {
7862 let cursor_point = cursor.to_point(&multibuffer);
7863
7864 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7865
7866 if let Some((_, indent)) = indents.iter().next()
7867 && indent.len == cursor_point.column
7868 {
7869 self.edit_prediction_indent_conflict = false;
7870 }
7871 }
7872
7873 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7874 let edits = edit_prediction
7875 .edits
7876 .into_iter()
7877 .flat_map(|(range, new_text)| {
7878 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7879 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7880 Some((start..end, new_text))
7881 })
7882 .collect::<Vec<_>>();
7883 if edits.is_empty() {
7884 return None;
7885 }
7886
7887 let first_edit_start = edits.first().unwrap().0.start;
7888 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7889 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7890
7891 let last_edit_end = edits.last().unwrap().0.end;
7892 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7893 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7894
7895 let cursor_row = cursor.to_point(&multibuffer).row;
7896
7897 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7898
7899 let mut inlay_ids = Vec::new();
7900 let invalidation_row_range;
7901 let move_invalidation_row_range = if cursor_row < edit_start_row {
7902 Some(cursor_row..edit_end_row)
7903 } else if cursor_row > edit_end_row {
7904 Some(edit_start_row..cursor_row)
7905 } else {
7906 None
7907 };
7908 let supports_jump = self
7909 .edit_prediction_provider
7910 .as_ref()
7911 .map(|provider| provider.provider.supports_jump_to_edit())
7912 .unwrap_or(true);
7913
7914 let is_move = supports_jump
7915 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7916 let completion = if is_move {
7917 invalidation_row_range =
7918 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7919 let target = first_edit_start;
7920 EditPrediction::Move { target, snapshot }
7921 } else {
7922 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7923 && !self.edit_predictions_hidden_for_vim_mode;
7924
7925 if show_completions_in_buffer {
7926 if edits
7927 .iter()
7928 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7929 {
7930 let mut inlays = Vec::new();
7931 for (range, new_text) in &edits {
7932 let inlay = Inlay::edit_prediction(
7933 post_inc(&mut self.next_inlay_id),
7934 range.start,
7935 new_text.as_str(),
7936 );
7937 inlay_ids.push(inlay.id);
7938 inlays.push(inlay);
7939 }
7940
7941 self.splice_inlays(&[], inlays, cx);
7942 } else {
7943 let background_color = cx.theme().status().deleted_background;
7944 self.highlight_text::<EditPredictionHighlight>(
7945 edits.iter().map(|(range, _)| range.clone()).collect(),
7946 HighlightStyle {
7947 background_color: Some(background_color),
7948 ..Default::default()
7949 },
7950 cx,
7951 );
7952 }
7953 }
7954
7955 invalidation_row_range = edit_start_row..edit_end_row;
7956
7957 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7958 if provider.show_tab_accept_marker() {
7959 EditDisplayMode::TabAccept
7960 } else {
7961 EditDisplayMode::Inline
7962 }
7963 } else {
7964 EditDisplayMode::DiffPopover
7965 };
7966
7967 EditPrediction::Edit {
7968 edits,
7969 edit_preview: edit_prediction.edit_preview,
7970 display_mode,
7971 snapshot,
7972 }
7973 };
7974
7975 let invalidation_range = multibuffer
7976 .anchor_before(Point::new(invalidation_row_range.start, 0))
7977 ..multibuffer.anchor_after(Point::new(
7978 invalidation_row_range.end,
7979 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7980 ));
7981
7982 self.stale_edit_prediction_in_menu = None;
7983 self.active_edit_prediction = Some(EditPredictionState {
7984 inlay_ids,
7985 completion,
7986 completion_id: edit_prediction.id,
7987 invalidation_range,
7988 });
7989
7990 cx.notify();
7991
7992 Some(())
7993 }
7994
7995 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7996 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7997 }
7998
7999 fn clear_tasks(&mut self) {
8000 self.tasks.clear()
8001 }
8002
8003 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8004 if self.tasks.insert(key, value).is_some() {
8005 // This case should hopefully be rare, but just in case...
8006 log::error!(
8007 "multiple different run targets found on a single line, only the last target will be rendered"
8008 )
8009 }
8010 }
8011
8012 /// Get all display points of breakpoints that will be rendered within editor
8013 ///
8014 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8015 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8016 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8017 fn active_breakpoints(
8018 &self,
8019 range: Range<DisplayRow>,
8020 window: &mut Window,
8021 cx: &mut Context<Self>,
8022 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8023 let mut breakpoint_display_points = HashMap::default();
8024
8025 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8026 return breakpoint_display_points;
8027 };
8028
8029 let snapshot = self.snapshot(window, cx);
8030
8031 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8032 let Some(project) = self.project() else {
8033 return breakpoint_display_points;
8034 };
8035
8036 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8037 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8038
8039 for (buffer_snapshot, range, excerpt_id) in
8040 multi_buffer_snapshot.range_to_buffer_ranges(range)
8041 {
8042 let Some(buffer) = project
8043 .read(cx)
8044 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8045 else {
8046 continue;
8047 };
8048 let breakpoints = breakpoint_store.read(cx).breakpoints(
8049 &buffer,
8050 Some(
8051 buffer_snapshot.anchor_before(range.start)
8052 ..buffer_snapshot.anchor_after(range.end),
8053 ),
8054 buffer_snapshot,
8055 cx,
8056 );
8057 for (breakpoint, state) in breakpoints {
8058 let multi_buffer_anchor =
8059 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8060 let position = multi_buffer_anchor
8061 .to_point(multi_buffer_snapshot)
8062 .to_display_point(&snapshot);
8063
8064 breakpoint_display_points.insert(
8065 position.row(),
8066 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8067 );
8068 }
8069 }
8070
8071 breakpoint_display_points
8072 }
8073
8074 fn breakpoint_context_menu(
8075 &self,
8076 anchor: Anchor,
8077 window: &mut Window,
8078 cx: &mut Context<Self>,
8079 ) -> Entity<ui::ContextMenu> {
8080 let weak_editor = cx.weak_entity();
8081 let focus_handle = self.focus_handle(cx);
8082
8083 let row = self
8084 .buffer
8085 .read(cx)
8086 .snapshot(cx)
8087 .summary_for_anchor::<Point>(&anchor)
8088 .row;
8089
8090 let breakpoint = self
8091 .breakpoint_at_row(row, window, cx)
8092 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8093
8094 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8095 "Edit Log Breakpoint"
8096 } else {
8097 "Set Log Breakpoint"
8098 };
8099
8100 let condition_breakpoint_msg = if breakpoint
8101 .as_ref()
8102 .is_some_and(|bp| bp.1.condition.is_some())
8103 {
8104 "Edit Condition Breakpoint"
8105 } else {
8106 "Set Condition Breakpoint"
8107 };
8108
8109 let hit_condition_breakpoint_msg = if breakpoint
8110 .as_ref()
8111 .is_some_and(|bp| bp.1.hit_condition.is_some())
8112 {
8113 "Edit Hit Condition Breakpoint"
8114 } else {
8115 "Set Hit Condition Breakpoint"
8116 };
8117
8118 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8119 "Unset Breakpoint"
8120 } else {
8121 "Set Breakpoint"
8122 };
8123
8124 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8125
8126 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8127 BreakpointState::Enabled => Some("Disable"),
8128 BreakpointState::Disabled => Some("Enable"),
8129 });
8130
8131 let (anchor, breakpoint) =
8132 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8133
8134 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8135 menu.on_blur_subscription(Subscription::new(|| {}))
8136 .context(focus_handle)
8137 .when(run_to_cursor, |this| {
8138 let weak_editor = weak_editor.clone();
8139 this.entry("Run to cursor", None, move |window, cx| {
8140 weak_editor
8141 .update(cx, |editor, cx| {
8142 editor.change_selections(
8143 SelectionEffects::no_scroll(),
8144 window,
8145 cx,
8146 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8147 );
8148 })
8149 .ok();
8150
8151 window.dispatch_action(Box::new(RunToCursor), cx);
8152 })
8153 .separator()
8154 })
8155 .when_some(toggle_state_msg, |this, msg| {
8156 this.entry(msg, None, {
8157 let weak_editor = weak_editor.clone();
8158 let breakpoint = breakpoint.clone();
8159 move |_window, cx| {
8160 weak_editor
8161 .update(cx, |this, cx| {
8162 this.edit_breakpoint_at_anchor(
8163 anchor,
8164 breakpoint.as_ref().clone(),
8165 BreakpointEditAction::InvertState,
8166 cx,
8167 );
8168 })
8169 .log_err();
8170 }
8171 })
8172 })
8173 .entry(set_breakpoint_msg, None, {
8174 let weak_editor = weak_editor.clone();
8175 let breakpoint = breakpoint.clone();
8176 move |_window, cx| {
8177 weak_editor
8178 .update(cx, |this, cx| {
8179 this.edit_breakpoint_at_anchor(
8180 anchor,
8181 breakpoint.as_ref().clone(),
8182 BreakpointEditAction::Toggle,
8183 cx,
8184 );
8185 })
8186 .log_err();
8187 }
8188 })
8189 .entry(log_breakpoint_msg, None, {
8190 let breakpoint = breakpoint.clone();
8191 let weak_editor = weak_editor.clone();
8192 move |window, cx| {
8193 weak_editor
8194 .update(cx, |this, cx| {
8195 this.add_edit_breakpoint_block(
8196 anchor,
8197 breakpoint.as_ref(),
8198 BreakpointPromptEditAction::Log,
8199 window,
8200 cx,
8201 );
8202 })
8203 .log_err();
8204 }
8205 })
8206 .entry(condition_breakpoint_msg, None, {
8207 let breakpoint = breakpoint.clone();
8208 let weak_editor = weak_editor.clone();
8209 move |window, cx| {
8210 weak_editor
8211 .update(cx, |this, cx| {
8212 this.add_edit_breakpoint_block(
8213 anchor,
8214 breakpoint.as_ref(),
8215 BreakpointPromptEditAction::Condition,
8216 window,
8217 cx,
8218 );
8219 })
8220 .log_err();
8221 }
8222 })
8223 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8224 weak_editor
8225 .update(cx, |this, cx| {
8226 this.add_edit_breakpoint_block(
8227 anchor,
8228 breakpoint.as_ref(),
8229 BreakpointPromptEditAction::HitCondition,
8230 window,
8231 cx,
8232 );
8233 })
8234 .log_err();
8235 })
8236 })
8237 }
8238
8239 fn render_breakpoint(
8240 &self,
8241 position: Anchor,
8242 row: DisplayRow,
8243 breakpoint: &Breakpoint,
8244 state: Option<BreakpointSessionState>,
8245 cx: &mut Context<Self>,
8246 ) -> IconButton {
8247 let is_rejected = state.is_some_and(|s| !s.verified);
8248 // Is it a breakpoint that shows up when hovering over gutter?
8249 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8250 (false, false),
8251 |PhantomBreakpointIndicator {
8252 is_active,
8253 display_row,
8254 collides_with_existing_breakpoint,
8255 }| {
8256 (
8257 is_active && display_row == row,
8258 collides_with_existing_breakpoint,
8259 )
8260 },
8261 );
8262
8263 let (color, icon) = {
8264 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8265 (false, false) => ui::IconName::DebugBreakpoint,
8266 (true, false) => ui::IconName::DebugLogBreakpoint,
8267 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8268 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8269 };
8270
8271 let color = if is_phantom {
8272 Color::Hint
8273 } else if is_rejected {
8274 Color::Disabled
8275 } else {
8276 Color::Debugger
8277 };
8278
8279 (color, icon)
8280 };
8281
8282 let breakpoint = Arc::from(breakpoint.clone());
8283
8284 let alt_as_text = gpui::Keystroke {
8285 modifiers: Modifiers::secondary_key(),
8286 ..Default::default()
8287 };
8288 let primary_action_text = if breakpoint.is_disabled() {
8289 "Enable breakpoint"
8290 } else if is_phantom && !collides_with_existing {
8291 "Set breakpoint"
8292 } else {
8293 "Unset breakpoint"
8294 };
8295 let focus_handle = self.focus_handle.clone();
8296
8297 let meta = if is_rejected {
8298 SharedString::from("No executable code is associated with this line.")
8299 } else if collides_with_existing && !breakpoint.is_disabled() {
8300 SharedString::from(format!(
8301 "{alt_as_text}-click to disable,\nright-click for more options."
8302 ))
8303 } else {
8304 SharedString::from("Right-click for more options.")
8305 };
8306 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8307 .icon_size(IconSize::XSmall)
8308 .size(ui::ButtonSize::None)
8309 .when(is_rejected, |this| {
8310 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8311 })
8312 .icon_color(color)
8313 .style(ButtonStyle::Transparent)
8314 .on_click(cx.listener({
8315 move |editor, event: &ClickEvent, window, cx| {
8316 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8317 BreakpointEditAction::InvertState
8318 } else {
8319 BreakpointEditAction::Toggle
8320 };
8321
8322 window.focus(&editor.focus_handle(cx));
8323 editor.edit_breakpoint_at_anchor(
8324 position,
8325 breakpoint.as_ref().clone(),
8326 edit_action,
8327 cx,
8328 );
8329 }
8330 }))
8331 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8332 editor.set_breakpoint_context_menu(
8333 row,
8334 Some(position),
8335 event.position(),
8336 window,
8337 cx,
8338 );
8339 }))
8340 .tooltip(move |window, cx| {
8341 Tooltip::with_meta_in(
8342 primary_action_text,
8343 Some(&ToggleBreakpoint),
8344 meta.clone(),
8345 &focus_handle,
8346 window,
8347 cx,
8348 )
8349 })
8350 }
8351
8352 fn build_tasks_context(
8353 project: &Entity<Project>,
8354 buffer: &Entity<Buffer>,
8355 buffer_row: u32,
8356 tasks: &Arc<RunnableTasks>,
8357 cx: &mut Context<Self>,
8358 ) -> Task<Option<task::TaskContext>> {
8359 let position = Point::new(buffer_row, tasks.column);
8360 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8361 let location = Location {
8362 buffer: buffer.clone(),
8363 range: range_start..range_start,
8364 };
8365 // Fill in the environmental variables from the tree-sitter captures
8366 let mut captured_task_variables = TaskVariables::default();
8367 for (capture_name, value) in tasks.extra_variables.clone() {
8368 captured_task_variables.insert(
8369 task::VariableName::Custom(capture_name.into()),
8370 value.clone(),
8371 );
8372 }
8373 project.update(cx, |project, cx| {
8374 project.task_store().update(cx, |task_store, cx| {
8375 task_store.task_context_for_location(captured_task_variables, location, cx)
8376 })
8377 })
8378 }
8379
8380 pub fn spawn_nearest_task(
8381 &mut self,
8382 action: &SpawnNearestTask,
8383 window: &mut Window,
8384 cx: &mut Context<Self>,
8385 ) {
8386 let Some((workspace, _)) = self.workspace.clone() else {
8387 return;
8388 };
8389 let Some(project) = self.project.clone() else {
8390 return;
8391 };
8392
8393 // Try to find a closest, enclosing node using tree-sitter that has a task
8394 let Some((buffer, buffer_row, tasks)) = self
8395 .find_enclosing_node_task(cx)
8396 // Or find the task that's closest in row-distance.
8397 .or_else(|| self.find_closest_task(cx))
8398 else {
8399 return;
8400 };
8401
8402 let reveal_strategy = action.reveal;
8403 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8404 cx.spawn_in(window, async move |_, cx| {
8405 let context = task_context.await?;
8406 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8407
8408 let resolved = &mut resolved_task.resolved;
8409 resolved.reveal = reveal_strategy;
8410
8411 workspace
8412 .update_in(cx, |workspace, window, cx| {
8413 workspace.schedule_resolved_task(
8414 task_source_kind,
8415 resolved_task,
8416 false,
8417 window,
8418 cx,
8419 );
8420 })
8421 .ok()
8422 })
8423 .detach();
8424 }
8425
8426 fn find_closest_task(
8427 &mut self,
8428 cx: &mut Context<Self>,
8429 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8430 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8431
8432 let ((buffer_id, row), tasks) = self
8433 .tasks
8434 .iter()
8435 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8436
8437 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8438 let tasks = Arc::new(tasks.to_owned());
8439 Some((buffer, *row, tasks))
8440 }
8441
8442 fn find_enclosing_node_task(
8443 &mut self,
8444 cx: &mut Context<Self>,
8445 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8446 let snapshot = self.buffer.read(cx).snapshot(cx);
8447 let offset = self.selections.newest::<usize>(cx).head();
8448 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8449 let buffer_id = excerpt.buffer().remote_id();
8450
8451 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8452 let mut cursor = layer.node().walk();
8453
8454 while cursor.goto_first_child_for_byte(offset).is_some() {
8455 if cursor.node().end_byte() == offset {
8456 cursor.goto_next_sibling();
8457 }
8458 }
8459
8460 // Ascend to the smallest ancestor that contains the range and has a task.
8461 loop {
8462 let node = cursor.node();
8463 let node_range = node.byte_range();
8464 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8465
8466 // Check if this node contains our offset
8467 if node_range.start <= offset && node_range.end >= offset {
8468 // If it contains offset, check for task
8469 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8470 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8471 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8472 }
8473 }
8474
8475 if !cursor.goto_parent() {
8476 break;
8477 }
8478 }
8479 None
8480 }
8481
8482 fn render_run_indicator(
8483 &self,
8484 _style: &EditorStyle,
8485 is_active: bool,
8486 row: DisplayRow,
8487 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8488 cx: &mut Context<Self>,
8489 ) -> IconButton {
8490 let color = Color::Muted;
8491 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8492
8493 IconButton::new(
8494 ("run_indicator", row.0 as usize),
8495 ui::IconName::PlayOutlined,
8496 )
8497 .shape(ui::IconButtonShape::Square)
8498 .icon_size(IconSize::XSmall)
8499 .icon_color(color)
8500 .toggle_state(is_active)
8501 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8502 let quick_launch = match e {
8503 ClickEvent::Keyboard(_) => true,
8504 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8505 };
8506
8507 window.focus(&editor.focus_handle(cx));
8508 editor.toggle_code_actions(
8509 &ToggleCodeActions {
8510 deployed_from: Some(CodeActionSource::RunMenu(row)),
8511 quick_launch,
8512 },
8513 window,
8514 cx,
8515 );
8516 }))
8517 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8518 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8519 }))
8520 }
8521
8522 pub fn context_menu_visible(&self) -> bool {
8523 !self.edit_prediction_preview_is_active()
8524 && self
8525 .context_menu
8526 .borrow()
8527 .as_ref()
8528 .is_some_and(|menu| menu.visible())
8529 }
8530
8531 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8532 self.context_menu
8533 .borrow()
8534 .as_ref()
8535 .map(|menu| menu.origin())
8536 }
8537
8538 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8539 self.context_menu_options = Some(options);
8540 }
8541
8542 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8543 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8544
8545 fn render_edit_prediction_popover(
8546 &mut self,
8547 text_bounds: &Bounds<Pixels>,
8548 content_origin: gpui::Point<Pixels>,
8549 right_margin: Pixels,
8550 editor_snapshot: &EditorSnapshot,
8551 visible_row_range: Range<DisplayRow>,
8552 scroll_top: f32,
8553 scroll_bottom: f32,
8554 line_layouts: &[LineWithInvisibles],
8555 line_height: Pixels,
8556 scroll_pixel_position: gpui::Point<Pixels>,
8557 newest_selection_head: Option<DisplayPoint>,
8558 editor_width: Pixels,
8559 style: &EditorStyle,
8560 window: &mut Window,
8561 cx: &mut App,
8562 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8563 if self.mode().is_minimap() {
8564 return None;
8565 }
8566 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8567
8568 if self.edit_prediction_visible_in_cursor_popover(true) {
8569 return None;
8570 }
8571
8572 match &active_edit_prediction.completion {
8573 EditPrediction::Move { target, .. } => {
8574 let target_display_point = target.to_display_point(editor_snapshot);
8575
8576 if self.edit_prediction_requires_modifier() {
8577 if !self.edit_prediction_preview_is_active() {
8578 return None;
8579 }
8580
8581 self.render_edit_prediction_modifier_jump_popover(
8582 text_bounds,
8583 content_origin,
8584 visible_row_range,
8585 line_layouts,
8586 line_height,
8587 scroll_pixel_position,
8588 newest_selection_head,
8589 target_display_point,
8590 window,
8591 cx,
8592 )
8593 } else {
8594 self.render_edit_prediction_eager_jump_popover(
8595 text_bounds,
8596 content_origin,
8597 editor_snapshot,
8598 visible_row_range,
8599 scroll_top,
8600 scroll_bottom,
8601 line_height,
8602 scroll_pixel_position,
8603 target_display_point,
8604 editor_width,
8605 window,
8606 cx,
8607 )
8608 }
8609 }
8610 EditPrediction::Edit {
8611 display_mode: EditDisplayMode::Inline,
8612 ..
8613 } => None,
8614 EditPrediction::Edit {
8615 display_mode: EditDisplayMode::TabAccept,
8616 edits,
8617 ..
8618 } => {
8619 let range = &edits.first()?.0;
8620 let target_display_point = range.end.to_display_point(editor_snapshot);
8621
8622 self.render_edit_prediction_end_of_line_popover(
8623 "Accept",
8624 editor_snapshot,
8625 visible_row_range,
8626 target_display_point,
8627 line_height,
8628 scroll_pixel_position,
8629 content_origin,
8630 editor_width,
8631 window,
8632 cx,
8633 )
8634 }
8635 EditPrediction::Edit {
8636 edits,
8637 edit_preview,
8638 display_mode: EditDisplayMode::DiffPopover,
8639 snapshot,
8640 } => self.render_edit_prediction_diff_popover(
8641 text_bounds,
8642 content_origin,
8643 right_margin,
8644 editor_snapshot,
8645 visible_row_range,
8646 line_layouts,
8647 line_height,
8648 scroll_pixel_position,
8649 newest_selection_head,
8650 editor_width,
8651 style,
8652 edits,
8653 edit_preview,
8654 snapshot,
8655 window,
8656 cx,
8657 ),
8658 }
8659 }
8660
8661 fn render_edit_prediction_modifier_jump_popover(
8662 &mut self,
8663 text_bounds: &Bounds<Pixels>,
8664 content_origin: gpui::Point<Pixels>,
8665 visible_row_range: Range<DisplayRow>,
8666 line_layouts: &[LineWithInvisibles],
8667 line_height: Pixels,
8668 scroll_pixel_position: gpui::Point<Pixels>,
8669 newest_selection_head: Option<DisplayPoint>,
8670 target_display_point: DisplayPoint,
8671 window: &mut Window,
8672 cx: &mut App,
8673 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8674 let scrolled_content_origin =
8675 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8676
8677 const SCROLL_PADDING_Y: Pixels = px(12.);
8678
8679 if target_display_point.row() < visible_row_range.start {
8680 return self.render_edit_prediction_scroll_popover(
8681 |_| SCROLL_PADDING_Y,
8682 IconName::ArrowUp,
8683 visible_row_range,
8684 line_layouts,
8685 newest_selection_head,
8686 scrolled_content_origin,
8687 window,
8688 cx,
8689 );
8690 } else if target_display_point.row() >= visible_row_range.end {
8691 return self.render_edit_prediction_scroll_popover(
8692 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8693 IconName::ArrowDown,
8694 visible_row_range,
8695 line_layouts,
8696 newest_selection_head,
8697 scrolled_content_origin,
8698 window,
8699 cx,
8700 );
8701 }
8702
8703 const POLE_WIDTH: Pixels = px(2.);
8704
8705 let line_layout =
8706 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8707 let target_column = target_display_point.column() as usize;
8708
8709 let target_x = line_layout.x_for_index(target_column);
8710 let target_y =
8711 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8712
8713 let flag_on_right = target_x < text_bounds.size.width / 2.;
8714
8715 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8716 border_color.l += 0.001;
8717
8718 let mut element = v_flex()
8719 .items_end()
8720 .when(flag_on_right, |el| el.items_start())
8721 .child(if flag_on_right {
8722 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8723 .rounded_bl(px(0.))
8724 .rounded_tl(px(0.))
8725 .border_l_2()
8726 .border_color(border_color)
8727 } else {
8728 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8729 .rounded_br(px(0.))
8730 .rounded_tr(px(0.))
8731 .border_r_2()
8732 .border_color(border_color)
8733 })
8734 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8735 .into_any();
8736
8737 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8738
8739 let mut origin = scrolled_content_origin + point(target_x, target_y)
8740 - point(
8741 if flag_on_right {
8742 POLE_WIDTH
8743 } else {
8744 size.width - POLE_WIDTH
8745 },
8746 size.height - line_height,
8747 );
8748
8749 origin.x = origin.x.max(content_origin.x);
8750
8751 element.prepaint_at(origin, window, cx);
8752
8753 Some((element, origin))
8754 }
8755
8756 fn render_edit_prediction_scroll_popover(
8757 &mut self,
8758 to_y: impl Fn(Size<Pixels>) -> Pixels,
8759 scroll_icon: IconName,
8760 visible_row_range: Range<DisplayRow>,
8761 line_layouts: &[LineWithInvisibles],
8762 newest_selection_head: Option<DisplayPoint>,
8763 scrolled_content_origin: gpui::Point<Pixels>,
8764 window: &mut Window,
8765 cx: &mut App,
8766 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8767 let mut element = self
8768 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8769 .into_any();
8770
8771 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8772
8773 let cursor = newest_selection_head?;
8774 let cursor_row_layout =
8775 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8776 let cursor_column = cursor.column() as usize;
8777
8778 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8779
8780 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8781
8782 element.prepaint_at(origin, window, cx);
8783 Some((element, origin))
8784 }
8785
8786 fn render_edit_prediction_eager_jump_popover(
8787 &mut self,
8788 text_bounds: &Bounds<Pixels>,
8789 content_origin: gpui::Point<Pixels>,
8790 editor_snapshot: &EditorSnapshot,
8791 visible_row_range: Range<DisplayRow>,
8792 scroll_top: f32,
8793 scroll_bottom: f32,
8794 line_height: Pixels,
8795 scroll_pixel_position: gpui::Point<Pixels>,
8796 target_display_point: DisplayPoint,
8797 editor_width: Pixels,
8798 window: &mut Window,
8799 cx: &mut App,
8800 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8801 if target_display_point.row().as_f32() < scroll_top {
8802 let mut element = self
8803 .render_edit_prediction_line_popover(
8804 "Jump to Edit",
8805 Some(IconName::ArrowUp),
8806 window,
8807 cx,
8808 )?
8809 .into_any();
8810
8811 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8812 let offset = point(
8813 (text_bounds.size.width - size.width) / 2.,
8814 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8815 );
8816
8817 let origin = text_bounds.origin + offset;
8818 element.prepaint_at(origin, window, cx);
8819 Some((element, origin))
8820 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8821 let mut element = self
8822 .render_edit_prediction_line_popover(
8823 "Jump to Edit",
8824 Some(IconName::ArrowDown),
8825 window,
8826 cx,
8827 )?
8828 .into_any();
8829
8830 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8831 let offset = point(
8832 (text_bounds.size.width - size.width) / 2.,
8833 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8834 );
8835
8836 let origin = text_bounds.origin + offset;
8837 element.prepaint_at(origin, window, cx);
8838 Some((element, origin))
8839 } else {
8840 self.render_edit_prediction_end_of_line_popover(
8841 "Jump to Edit",
8842 editor_snapshot,
8843 visible_row_range,
8844 target_display_point,
8845 line_height,
8846 scroll_pixel_position,
8847 content_origin,
8848 editor_width,
8849 window,
8850 cx,
8851 )
8852 }
8853 }
8854
8855 fn render_edit_prediction_end_of_line_popover(
8856 self: &mut Editor,
8857 label: &'static str,
8858 editor_snapshot: &EditorSnapshot,
8859 visible_row_range: Range<DisplayRow>,
8860 target_display_point: DisplayPoint,
8861 line_height: Pixels,
8862 scroll_pixel_position: gpui::Point<Pixels>,
8863 content_origin: gpui::Point<Pixels>,
8864 editor_width: Pixels,
8865 window: &mut Window,
8866 cx: &mut App,
8867 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8868 let target_line_end = DisplayPoint::new(
8869 target_display_point.row(),
8870 editor_snapshot.line_len(target_display_point.row()),
8871 );
8872
8873 let mut element = self
8874 .render_edit_prediction_line_popover(label, None, window, cx)?
8875 .into_any();
8876
8877 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8878
8879 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8880
8881 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8882 let mut origin = start_point
8883 + line_origin
8884 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8885 origin.x = origin.x.max(content_origin.x);
8886
8887 let max_x = content_origin.x + editor_width - size.width;
8888
8889 if origin.x > max_x {
8890 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8891
8892 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8893 origin.y += offset;
8894 IconName::ArrowUp
8895 } else {
8896 origin.y -= offset;
8897 IconName::ArrowDown
8898 };
8899
8900 element = self
8901 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8902 .into_any();
8903
8904 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8905
8906 origin.x = content_origin.x + editor_width - size.width - px(2.);
8907 }
8908
8909 element.prepaint_at(origin, window, cx);
8910 Some((element, origin))
8911 }
8912
8913 fn render_edit_prediction_diff_popover(
8914 self: &Editor,
8915 text_bounds: &Bounds<Pixels>,
8916 content_origin: gpui::Point<Pixels>,
8917 right_margin: Pixels,
8918 editor_snapshot: &EditorSnapshot,
8919 visible_row_range: Range<DisplayRow>,
8920 line_layouts: &[LineWithInvisibles],
8921 line_height: Pixels,
8922 scroll_pixel_position: gpui::Point<Pixels>,
8923 newest_selection_head: Option<DisplayPoint>,
8924 editor_width: Pixels,
8925 style: &EditorStyle,
8926 edits: &Vec<(Range<Anchor>, String)>,
8927 edit_preview: &Option<language::EditPreview>,
8928 snapshot: &language::BufferSnapshot,
8929 window: &mut Window,
8930 cx: &mut App,
8931 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8932 let edit_start = edits
8933 .first()
8934 .unwrap()
8935 .0
8936 .start
8937 .to_display_point(editor_snapshot);
8938 let edit_end = edits
8939 .last()
8940 .unwrap()
8941 .0
8942 .end
8943 .to_display_point(editor_snapshot);
8944
8945 let is_visible = visible_row_range.contains(&edit_start.row())
8946 || visible_row_range.contains(&edit_end.row());
8947 if !is_visible {
8948 return None;
8949 }
8950
8951 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8952 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8953 } else {
8954 // Fallback for providers without edit_preview
8955 crate::edit_prediction_fallback_text(edits, cx)
8956 };
8957
8958 let styled_text = highlighted_edits.to_styled_text(&style.text);
8959 let line_count = highlighted_edits.text.lines().count();
8960
8961 const BORDER_WIDTH: Pixels = px(1.);
8962
8963 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8964 let has_keybind = keybind.is_some();
8965
8966 let mut element = h_flex()
8967 .items_start()
8968 .child(
8969 h_flex()
8970 .bg(cx.theme().colors().editor_background)
8971 .border(BORDER_WIDTH)
8972 .shadow_xs()
8973 .border_color(cx.theme().colors().border)
8974 .rounded_l_lg()
8975 .when(line_count > 1, |el| el.rounded_br_lg())
8976 .pr_1()
8977 .child(styled_text),
8978 )
8979 .child(
8980 h_flex()
8981 .h(line_height + BORDER_WIDTH * 2.)
8982 .px_1p5()
8983 .gap_1()
8984 // Workaround: For some reason, there's a gap if we don't do this
8985 .ml(-BORDER_WIDTH)
8986 .shadow(vec![gpui::BoxShadow {
8987 color: gpui::black().opacity(0.05),
8988 offset: point(px(1.), px(1.)),
8989 blur_radius: px(2.),
8990 spread_radius: px(0.),
8991 }])
8992 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8993 .border(BORDER_WIDTH)
8994 .border_color(cx.theme().colors().border)
8995 .rounded_r_lg()
8996 .id("edit_prediction_diff_popover_keybind")
8997 .when(!has_keybind, |el| {
8998 let status_colors = cx.theme().status();
8999
9000 el.bg(status_colors.error_background)
9001 .border_color(status_colors.error.opacity(0.6))
9002 .child(Icon::new(IconName::Info).color(Color::Error))
9003 .cursor_default()
9004 .hoverable_tooltip(move |_window, cx| {
9005 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9006 })
9007 })
9008 .children(keybind),
9009 )
9010 .into_any();
9011
9012 let longest_row =
9013 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9014 let longest_line_width = if visible_row_range.contains(&longest_row) {
9015 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9016 } else {
9017 layout_line(
9018 longest_row,
9019 editor_snapshot,
9020 style,
9021 editor_width,
9022 |_| false,
9023 window,
9024 cx,
9025 )
9026 .width
9027 };
9028
9029 let viewport_bounds =
9030 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9031 right: -right_margin,
9032 ..Default::default()
9033 });
9034
9035 let x_after_longest =
9036 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9037 - scroll_pixel_position.x;
9038
9039 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9040
9041 // Fully visible if it can be displayed within the window (allow overlapping other
9042 // panes). However, this is only allowed if the popover starts within text_bounds.
9043 let can_position_to_the_right = x_after_longest < text_bounds.right()
9044 && x_after_longest + element_bounds.width < viewport_bounds.right();
9045
9046 let mut origin = if can_position_to_the_right {
9047 point(
9048 x_after_longest,
9049 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9050 - scroll_pixel_position.y,
9051 )
9052 } else {
9053 let cursor_row = newest_selection_head.map(|head| head.row());
9054 let above_edit = edit_start
9055 .row()
9056 .0
9057 .checked_sub(line_count as u32)
9058 .map(DisplayRow);
9059 let below_edit = Some(edit_end.row() + 1);
9060 let above_cursor =
9061 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9062 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9063
9064 // Place the edit popover adjacent to the edit if there is a location
9065 // available that is onscreen and does not obscure the cursor. Otherwise,
9066 // place it adjacent to the cursor.
9067 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9068 .into_iter()
9069 .flatten()
9070 .find(|&start_row| {
9071 let end_row = start_row + line_count as u32;
9072 visible_row_range.contains(&start_row)
9073 && visible_row_range.contains(&end_row)
9074 && cursor_row
9075 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9076 })?;
9077
9078 content_origin
9079 + point(
9080 -scroll_pixel_position.x,
9081 row_target.as_f32() * line_height - scroll_pixel_position.y,
9082 )
9083 };
9084
9085 origin.x -= BORDER_WIDTH;
9086
9087 window.defer_draw(element, origin, 1);
9088
9089 // Do not return an element, since it will already be drawn due to defer_draw.
9090 None
9091 }
9092
9093 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9094 px(30.)
9095 }
9096
9097 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9098 if self.read_only(cx) {
9099 cx.theme().players().read_only()
9100 } else {
9101 self.style.as_ref().unwrap().local_player
9102 }
9103 }
9104
9105 fn render_edit_prediction_accept_keybind(
9106 &self,
9107 window: &mut Window,
9108 cx: &App,
9109 ) -> Option<AnyElement> {
9110 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9111 let accept_keystroke = accept_binding.keystroke()?;
9112
9113 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9114
9115 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9116 Color::Accent
9117 } else {
9118 Color::Muted
9119 };
9120
9121 h_flex()
9122 .px_0p5()
9123 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9124 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9125 .text_size(TextSize::XSmall.rems(cx))
9126 .child(h_flex().children(ui::render_modifiers(
9127 accept_keystroke.modifiers(),
9128 PlatformStyle::platform(),
9129 Some(modifiers_color),
9130 Some(IconSize::XSmall.rems().into()),
9131 true,
9132 )))
9133 .when(is_platform_style_mac, |parent| {
9134 parent.child(accept_keystroke.key().to_string())
9135 })
9136 .when(!is_platform_style_mac, |parent| {
9137 parent.child(
9138 Key::new(
9139 util::capitalize(accept_keystroke.key()),
9140 Some(Color::Default),
9141 )
9142 .size(Some(IconSize::XSmall.rems().into())),
9143 )
9144 })
9145 .into_any()
9146 .into()
9147 }
9148
9149 fn render_edit_prediction_line_popover(
9150 &self,
9151 label: impl Into<SharedString>,
9152 icon: Option<IconName>,
9153 window: &mut Window,
9154 cx: &App,
9155 ) -> Option<Stateful<Div>> {
9156 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9157
9158 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9159 let has_keybind = keybind.is_some();
9160
9161 let result = h_flex()
9162 .id("ep-line-popover")
9163 .py_0p5()
9164 .pl_1()
9165 .pr(padding_right)
9166 .gap_1()
9167 .rounded_md()
9168 .border_1()
9169 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9170 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9171 .shadow_xs()
9172 .when(!has_keybind, |el| {
9173 let status_colors = cx.theme().status();
9174
9175 el.bg(status_colors.error_background)
9176 .border_color(status_colors.error.opacity(0.6))
9177 .pl_2()
9178 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9179 .cursor_default()
9180 .hoverable_tooltip(move |_window, cx| {
9181 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9182 })
9183 })
9184 .children(keybind)
9185 .child(
9186 Label::new(label)
9187 .size(LabelSize::Small)
9188 .when(!has_keybind, |el| {
9189 el.color(cx.theme().status().error.into()).strikethrough()
9190 }),
9191 )
9192 .when(!has_keybind, |el| {
9193 el.child(
9194 h_flex().ml_1().child(
9195 Icon::new(IconName::Info)
9196 .size(IconSize::Small)
9197 .color(cx.theme().status().error.into()),
9198 ),
9199 )
9200 })
9201 .when_some(icon, |element, icon| {
9202 element.child(
9203 div()
9204 .mt(px(1.5))
9205 .child(Icon::new(icon).size(IconSize::Small)),
9206 )
9207 });
9208
9209 Some(result)
9210 }
9211
9212 fn edit_prediction_line_popover_bg_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.1))
9216 }
9217
9218 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9219 let accent_color = cx.theme().colors().text_accent;
9220 let editor_bg_color = cx.theme().colors().editor_background;
9221 editor_bg_color.blend(accent_color.opacity(0.6))
9222 }
9223 fn get_prediction_provider_icon_name(
9224 provider: &Option<RegisteredEditPredictionProvider>,
9225 ) -> IconName {
9226 match provider {
9227 Some(provider) => match provider.provider.name() {
9228 "copilot" => IconName::Copilot,
9229 "supermaven" => IconName::Supermaven,
9230 _ => IconName::ZedPredict,
9231 },
9232 None => IconName::ZedPredict,
9233 }
9234 }
9235
9236 fn render_edit_prediction_cursor_popover(
9237 &self,
9238 min_width: Pixels,
9239 max_width: Pixels,
9240 cursor_point: Point,
9241 style: &EditorStyle,
9242 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9243 _window: &Window,
9244 cx: &mut Context<Editor>,
9245 ) -> Option<AnyElement> {
9246 let provider = self.edit_prediction_provider.as_ref()?;
9247 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9248
9249 let is_refreshing = provider.provider.is_refreshing(cx);
9250
9251 fn pending_completion_container(icon: IconName) -> Div {
9252 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9253 }
9254
9255 let completion = match &self.active_edit_prediction {
9256 Some(prediction) => {
9257 if !self.has_visible_completions_menu() {
9258 const RADIUS: Pixels = px(6.);
9259 const BORDER_WIDTH: Pixels = px(1.);
9260
9261 return Some(
9262 h_flex()
9263 .elevation_2(cx)
9264 .border(BORDER_WIDTH)
9265 .border_color(cx.theme().colors().border)
9266 .when(accept_keystroke.is_none(), |el| {
9267 el.border_color(cx.theme().status().error)
9268 })
9269 .rounded(RADIUS)
9270 .rounded_tl(px(0.))
9271 .overflow_hidden()
9272 .child(div().px_1p5().child(match &prediction.completion {
9273 EditPrediction::Move { target, snapshot } => {
9274 use text::ToPoint as _;
9275 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9276 {
9277 Icon::new(IconName::ZedPredictDown)
9278 } else {
9279 Icon::new(IconName::ZedPredictUp)
9280 }
9281 }
9282 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9283 }))
9284 .child(
9285 h_flex()
9286 .gap_1()
9287 .py_1()
9288 .px_2()
9289 .rounded_r(RADIUS - BORDER_WIDTH)
9290 .border_l_1()
9291 .border_color(cx.theme().colors().border)
9292 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9293 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9294 el.child(
9295 Label::new("Hold")
9296 .size(LabelSize::Small)
9297 .when(accept_keystroke.is_none(), |el| {
9298 el.strikethrough()
9299 })
9300 .line_height_style(LineHeightStyle::UiLabel),
9301 )
9302 })
9303 .id("edit_prediction_cursor_popover_keybind")
9304 .when(accept_keystroke.is_none(), |el| {
9305 let status_colors = cx.theme().status();
9306
9307 el.bg(status_colors.error_background)
9308 .border_color(status_colors.error.opacity(0.6))
9309 .child(Icon::new(IconName::Info).color(Color::Error))
9310 .cursor_default()
9311 .hoverable_tooltip(move |_window, cx| {
9312 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9313 .into()
9314 })
9315 })
9316 .when_some(
9317 accept_keystroke.as_ref(),
9318 |el, accept_keystroke| {
9319 el.child(h_flex().children(ui::render_modifiers(
9320 accept_keystroke.modifiers(),
9321 PlatformStyle::platform(),
9322 Some(Color::Default),
9323 Some(IconSize::XSmall.rems().into()),
9324 false,
9325 )))
9326 },
9327 ),
9328 )
9329 .into_any(),
9330 );
9331 }
9332
9333 self.render_edit_prediction_cursor_popover_preview(
9334 prediction,
9335 cursor_point,
9336 style,
9337 cx,
9338 )?
9339 }
9340
9341 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9342 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9343 stale_completion,
9344 cursor_point,
9345 style,
9346 cx,
9347 )?,
9348
9349 None => pending_completion_container(provider_icon)
9350 .child(Label::new("...").size(LabelSize::Small)),
9351 },
9352
9353 None => pending_completion_container(provider_icon)
9354 .child(Label::new("...").size(LabelSize::Small)),
9355 };
9356
9357 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9358 completion
9359 .with_animation(
9360 "loading-completion",
9361 Animation::new(Duration::from_secs(2))
9362 .repeat()
9363 .with_easing(pulsating_between(0.4, 0.8)),
9364 |label, delta| label.opacity(delta),
9365 )
9366 .into_any_element()
9367 } else {
9368 completion.into_any_element()
9369 };
9370
9371 let has_completion = self.active_edit_prediction.is_some();
9372
9373 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9374 Some(
9375 h_flex()
9376 .min_w(min_width)
9377 .max_w(max_width)
9378 .flex_1()
9379 .elevation_2(cx)
9380 .border_color(cx.theme().colors().border)
9381 .child(
9382 div()
9383 .flex_1()
9384 .py_1()
9385 .px_2()
9386 .overflow_hidden()
9387 .child(completion),
9388 )
9389 .when_some(accept_keystroke, |el, accept_keystroke| {
9390 if !accept_keystroke.modifiers().modified() {
9391 return el;
9392 }
9393
9394 el.child(
9395 h_flex()
9396 .h_full()
9397 .border_l_1()
9398 .rounded_r_lg()
9399 .border_color(cx.theme().colors().border)
9400 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9401 .gap_1()
9402 .py_1()
9403 .px_2()
9404 .child(
9405 h_flex()
9406 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9407 .when(is_platform_style_mac, |parent| parent.gap_1())
9408 .child(h_flex().children(ui::render_modifiers(
9409 accept_keystroke.modifiers(),
9410 PlatformStyle::platform(),
9411 Some(if !has_completion {
9412 Color::Muted
9413 } else {
9414 Color::Default
9415 }),
9416 None,
9417 false,
9418 ))),
9419 )
9420 .child(Label::new("Preview").into_any_element())
9421 .opacity(if has_completion { 1.0 } else { 0.4 }),
9422 )
9423 })
9424 .into_any(),
9425 )
9426 }
9427
9428 fn render_edit_prediction_cursor_popover_preview(
9429 &self,
9430 completion: &EditPredictionState,
9431 cursor_point: Point,
9432 style: &EditorStyle,
9433 cx: &mut Context<Editor>,
9434 ) -> Option<Div> {
9435 use text::ToPoint as _;
9436
9437 fn render_relative_row_jump(
9438 prefix: impl Into<String>,
9439 current_row: u32,
9440 target_row: u32,
9441 ) -> Div {
9442 let (row_diff, arrow) = if target_row < current_row {
9443 (current_row - target_row, IconName::ArrowUp)
9444 } else {
9445 (target_row - current_row, IconName::ArrowDown)
9446 };
9447
9448 h_flex()
9449 .child(
9450 Label::new(format!("{}{}", prefix.into(), row_diff))
9451 .color(Color::Muted)
9452 .size(LabelSize::Small),
9453 )
9454 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9455 }
9456
9457 let supports_jump = self
9458 .edit_prediction_provider
9459 .as_ref()
9460 .map(|provider| provider.provider.supports_jump_to_edit())
9461 .unwrap_or(true);
9462
9463 match &completion.completion {
9464 EditPrediction::Move {
9465 target, snapshot, ..
9466 } => {
9467 if !supports_jump {
9468 return None;
9469 }
9470
9471 Some(
9472 h_flex()
9473 .px_2()
9474 .gap_2()
9475 .flex_1()
9476 .child(
9477 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9478 Icon::new(IconName::ZedPredictDown)
9479 } else {
9480 Icon::new(IconName::ZedPredictUp)
9481 },
9482 )
9483 .child(Label::new("Jump to Edit")),
9484 )
9485 }
9486
9487 EditPrediction::Edit {
9488 edits,
9489 edit_preview,
9490 snapshot,
9491 display_mode: _,
9492 } => {
9493 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9494
9495 let (highlighted_edits, has_more_lines) =
9496 if let Some(edit_preview) = edit_preview.as_ref() {
9497 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9498 .first_line_preview()
9499 } else {
9500 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9501 };
9502
9503 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9504 .with_default_highlights(&style.text, highlighted_edits.highlights);
9505
9506 let preview = h_flex()
9507 .gap_1()
9508 .min_w_16()
9509 .child(styled_text)
9510 .when(has_more_lines, |parent| parent.child("…"));
9511
9512 let left = if supports_jump && first_edit_row != cursor_point.row {
9513 render_relative_row_jump("", cursor_point.row, first_edit_row)
9514 .into_any_element()
9515 } else {
9516 let icon_name =
9517 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9518 Icon::new(icon_name).into_any_element()
9519 };
9520
9521 Some(
9522 h_flex()
9523 .h_full()
9524 .flex_1()
9525 .gap_2()
9526 .pr_1()
9527 .overflow_x_hidden()
9528 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9529 .child(left)
9530 .child(preview),
9531 )
9532 }
9533 }
9534 }
9535
9536 pub fn render_context_menu(
9537 &self,
9538 style: &EditorStyle,
9539 max_height_in_lines: u32,
9540 window: &mut Window,
9541 cx: &mut Context<Editor>,
9542 ) -> Option<AnyElement> {
9543 let menu = self.context_menu.borrow();
9544 let menu = menu.as_ref()?;
9545 if !menu.visible() {
9546 return None;
9547 };
9548 Some(menu.render(style, max_height_in_lines, window, cx))
9549 }
9550
9551 fn render_context_menu_aside(
9552 &mut self,
9553 max_size: Size<Pixels>,
9554 window: &mut Window,
9555 cx: &mut Context<Editor>,
9556 ) -> Option<AnyElement> {
9557 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9558 if menu.visible() {
9559 menu.render_aside(max_size, window, cx)
9560 } else {
9561 None
9562 }
9563 })
9564 }
9565
9566 fn hide_context_menu(
9567 &mut self,
9568 window: &mut Window,
9569 cx: &mut Context<Self>,
9570 ) -> Option<CodeContextMenu> {
9571 cx.notify();
9572 self.completion_tasks.clear();
9573 let context_menu = self.context_menu.borrow_mut().take();
9574 self.stale_edit_prediction_in_menu.take();
9575 self.update_visible_edit_prediction(window, cx);
9576 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9577 && let Some(completion_provider) = &self.completion_provider
9578 {
9579 completion_provider.selection_changed(None, window, cx);
9580 }
9581 context_menu
9582 }
9583
9584 fn show_snippet_choices(
9585 &mut self,
9586 choices: &Vec<String>,
9587 selection: Range<Anchor>,
9588 cx: &mut Context<Self>,
9589 ) {
9590 let Some((_, buffer, _)) = self
9591 .buffer()
9592 .read(cx)
9593 .excerpt_containing(selection.start, cx)
9594 else {
9595 return;
9596 };
9597 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9598 else {
9599 return;
9600 };
9601 if buffer != end_buffer {
9602 log::error!("expected anchor range to have matching buffer IDs");
9603 return;
9604 }
9605
9606 let id = post_inc(&mut self.next_completion_id);
9607 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9608 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9609 CompletionsMenu::new_snippet_choices(
9610 id,
9611 true,
9612 choices,
9613 selection,
9614 buffer,
9615 snippet_sort_order,
9616 ),
9617 ));
9618 }
9619
9620 pub fn insert_snippet(
9621 &mut self,
9622 insertion_ranges: &[Range<usize>],
9623 snippet: Snippet,
9624 window: &mut Window,
9625 cx: &mut Context<Self>,
9626 ) -> Result<()> {
9627 struct Tabstop<T> {
9628 is_end_tabstop: bool,
9629 ranges: Vec<Range<T>>,
9630 choices: Option<Vec<String>>,
9631 }
9632
9633 let tabstops = self.buffer.update(cx, |buffer, cx| {
9634 let snippet_text: Arc<str> = snippet.text.clone().into();
9635 let edits = insertion_ranges
9636 .iter()
9637 .cloned()
9638 .map(|range| (range, snippet_text.clone()));
9639 let autoindent_mode = AutoindentMode::Block {
9640 original_indent_columns: Vec::new(),
9641 };
9642 buffer.edit(edits, Some(autoindent_mode), cx);
9643
9644 let snapshot = &*buffer.read(cx);
9645 let snippet = &snippet;
9646 snippet
9647 .tabstops
9648 .iter()
9649 .map(|tabstop| {
9650 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9651 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9652 });
9653 let mut tabstop_ranges = tabstop
9654 .ranges
9655 .iter()
9656 .flat_map(|tabstop_range| {
9657 let mut delta = 0_isize;
9658 insertion_ranges.iter().map(move |insertion_range| {
9659 let insertion_start = insertion_range.start as isize + delta;
9660 delta +=
9661 snippet.text.len() as isize - insertion_range.len() as isize;
9662
9663 let start = ((insertion_start + tabstop_range.start) as usize)
9664 .min(snapshot.len());
9665 let end = ((insertion_start + tabstop_range.end) as usize)
9666 .min(snapshot.len());
9667 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9668 })
9669 })
9670 .collect::<Vec<_>>();
9671 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9672
9673 Tabstop {
9674 is_end_tabstop,
9675 ranges: tabstop_ranges,
9676 choices: tabstop.choices.clone(),
9677 }
9678 })
9679 .collect::<Vec<_>>()
9680 });
9681 if let Some(tabstop) = tabstops.first() {
9682 self.change_selections(Default::default(), window, cx, |s| {
9683 // Reverse order so that the first range is the newest created selection.
9684 // Completions will use it and autoscroll will prioritize it.
9685 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9686 });
9687
9688 if let Some(choices) = &tabstop.choices
9689 && let Some(selection) = tabstop.ranges.first()
9690 {
9691 self.show_snippet_choices(choices, selection.clone(), cx)
9692 }
9693
9694 // If we're already at the last tabstop and it's at the end of the snippet,
9695 // we're done, we don't need to keep the state around.
9696 if !tabstop.is_end_tabstop {
9697 let choices = tabstops
9698 .iter()
9699 .map(|tabstop| tabstop.choices.clone())
9700 .collect();
9701
9702 let ranges = tabstops
9703 .into_iter()
9704 .map(|tabstop| tabstop.ranges)
9705 .collect::<Vec<_>>();
9706
9707 self.snippet_stack.push(SnippetState {
9708 active_index: 0,
9709 ranges,
9710 choices,
9711 });
9712 }
9713
9714 // Check whether the just-entered snippet ends with an auto-closable bracket.
9715 if self.autoclose_regions.is_empty() {
9716 let snapshot = self.buffer.read(cx).snapshot(cx);
9717 let mut all_selections = self.selections.all::<Point>(cx);
9718 for selection in &mut all_selections {
9719 let selection_head = selection.head();
9720 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9721 continue;
9722 };
9723
9724 let mut bracket_pair = None;
9725 let max_lookup_length = scope
9726 .brackets()
9727 .map(|(pair, _)| {
9728 pair.start
9729 .as_str()
9730 .chars()
9731 .count()
9732 .max(pair.end.as_str().chars().count())
9733 })
9734 .max();
9735 if let Some(max_lookup_length) = max_lookup_length {
9736 let next_text = snapshot
9737 .chars_at(selection_head)
9738 .take(max_lookup_length)
9739 .collect::<String>();
9740 let prev_text = snapshot
9741 .reversed_chars_at(selection_head)
9742 .take(max_lookup_length)
9743 .collect::<String>();
9744
9745 for (pair, enabled) in scope.brackets() {
9746 if enabled
9747 && pair.close
9748 && prev_text.starts_with(pair.start.as_str())
9749 && next_text.starts_with(pair.end.as_str())
9750 {
9751 bracket_pair = Some(pair.clone());
9752 break;
9753 }
9754 }
9755 }
9756
9757 if let Some(pair) = bracket_pair {
9758 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9759 let autoclose_enabled =
9760 self.use_autoclose && snapshot_settings.use_autoclose;
9761 if autoclose_enabled {
9762 let start = snapshot.anchor_after(selection_head);
9763 let end = snapshot.anchor_after(selection_head);
9764 self.autoclose_regions.push(AutocloseRegion {
9765 selection_id: selection.id,
9766 range: start..end,
9767 pair,
9768 });
9769 }
9770 }
9771 }
9772 }
9773 }
9774 Ok(())
9775 }
9776
9777 pub fn move_to_next_snippet_tabstop(
9778 &mut self,
9779 window: &mut Window,
9780 cx: &mut Context<Self>,
9781 ) -> bool {
9782 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9783 }
9784
9785 pub fn move_to_prev_snippet_tabstop(
9786 &mut self,
9787 window: &mut Window,
9788 cx: &mut Context<Self>,
9789 ) -> bool {
9790 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9791 }
9792
9793 pub fn move_to_snippet_tabstop(
9794 &mut self,
9795 bias: Bias,
9796 window: &mut Window,
9797 cx: &mut Context<Self>,
9798 ) -> bool {
9799 if let Some(mut snippet) = self.snippet_stack.pop() {
9800 match bias {
9801 Bias::Left => {
9802 if snippet.active_index > 0 {
9803 snippet.active_index -= 1;
9804 } else {
9805 self.snippet_stack.push(snippet);
9806 return false;
9807 }
9808 }
9809 Bias::Right => {
9810 if snippet.active_index + 1 < snippet.ranges.len() {
9811 snippet.active_index += 1;
9812 } else {
9813 self.snippet_stack.push(snippet);
9814 return false;
9815 }
9816 }
9817 }
9818 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9819 self.change_selections(Default::default(), window, cx, |s| {
9820 // Reverse order so that the first range is the newest created selection.
9821 // Completions will use it and autoscroll will prioritize it.
9822 s.select_ranges(current_ranges.iter().rev().cloned())
9823 });
9824
9825 if let Some(choices) = &snippet.choices[snippet.active_index]
9826 && let Some(selection) = current_ranges.first()
9827 {
9828 self.show_snippet_choices(choices, selection.clone(), cx);
9829 }
9830
9831 // If snippet state is not at the last tabstop, push it back on the stack
9832 if snippet.active_index + 1 < snippet.ranges.len() {
9833 self.snippet_stack.push(snippet);
9834 }
9835 return true;
9836 }
9837 }
9838
9839 false
9840 }
9841
9842 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9843 self.transact(window, cx, |this, window, cx| {
9844 this.select_all(&SelectAll, window, cx);
9845 this.insert("", window, cx);
9846 });
9847 }
9848
9849 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9850 if self.read_only(cx) {
9851 return;
9852 }
9853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9854 self.transact(window, cx, |this, window, cx| {
9855 this.select_autoclose_pair(window, cx);
9856 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9857 if !this.linked_edit_ranges.is_empty() {
9858 let selections = this.selections.all::<MultiBufferPoint>(cx);
9859 let snapshot = this.buffer.read(cx).snapshot(cx);
9860
9861 for selection in selections.iter() {
9862 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9863 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9864 if selection_start.buffer_id != selection_end.buffer_id {
9865 continue;
9866 }
9867 if let Some(ranges) =
9868 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9869 {
9870 for (buffer, entries) in ranges {
9871 linked_ranges.entry(buffer).or_default().extend(entries);
9872 }
9873 }
9874 }
9875 }
9876
9877 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9878 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9879 for selection in &mut selections {
9880 if selection.is_empty() {
9881 let old_head = selection.head();
9882 let mut new_head =
9883 movement::left(&display_map, old_head.to_display_point(&display_map))
9884 .to_point(&display_map);
9885 if let Some((buffer, line_buffer_range)) = display_map
9886 .buffer_snapshot
9887 .buffer_line_for_row(MultiBufferRow(old_head.row))
9888 {
9889 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9890 let indent_len = match indent_size.kind {
9891 IndentKind::Space => {
9892 buffer.settings_at(line_buffer_range.start, cx).tab_size
9893 }
9894 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9895 };
9896 if old_head.column <= indent_size.len && old_head.column > 0 {
9897 let indent_len = indent_len.get();
9898 new_head = cmp::min(
9899 new_head,
9900 MultiBufferPoint::new(
9901 old_head.row,
9902 ((old_head.column - 1) / indent_len) * indent_len,
9903 ),
9904 );
9905 }
9906 }
9907
9908 selection.set_head(new_head, SelectionGoal::None);
9909 }
9910 }
9911
9912 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9913 this.insert("", window, cx);
9914 let empty_str: Arc<str> = Arc::from("");
9915 for (buffer, edits) in linked_ranges {
9916 let snapshot = buffer.read(cx).snapshot();
9917 use text::ToPoint as TP;
9918
9919 let edits = edits
9920 .into_iter()
9921 .map(|range| {
9922 let end_point = TP::to_point(&range.end, &snapshot);
9923 let mut start_point = TP::to_point(&range.start, &snapshot);
9924
9925 if end_point == start_point {
9926 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9927 .saturating_sub(1);
9928 start_point =
9929 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9930 };
9931
9932 (start_point..end_point, empty_str.clone())
9933 })
9934 .sorted_by_key(|(range, _)| range.start)
9935 .collect::<Vec<_>>();
9936 buffer.update(cx, |this, cx| {
9937 this.edit(edits, None, cx);
9938 })
9939 }
9940 this.refresh_edit_prediction(true, false, window, cx);
9941 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9942 });
9943 }
9944
9945 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9946 if self.read_only(cx) {
9947 return;
9948 }
9949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9950 self.transact(window, cx, |this, window, cx| {
9951 this.change_selections(Default::default(), window, cx, |s| {
9952 s.move_with(|map, selection| {
9953 if selection.is_empty() {
9954 let cursor = movement::right(map, selection.head());
9955 selection.end = cursor;
9956 selection.reversed = true;
9957 selection.goal = SelectionGoal::None;
9958 }
9959 })
9960 });
9961 this.insert("", window, cx);
9962 this.refresh_edit_prediction(true, false, window, cx);
9963 });
9964 }
9965
9966 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9967 if self.mode.is_single_line() {
9968 cx.propagate();
9969 return;
9970 }
9971
9972 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9973 if self.move_to_prev_snippet_tabstop(window, cx) {
9974 return;
9975 }
9976 self.outdent(&Outdent, window, cx);
9977 }
9978
9979 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9980 if self.mode.is_single_line() {
9981 cx.propagate();
9982 return;
9983 }
9984
9985 if self.move_to_next_snippet_tabstop(window, cx) {
9986 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9987 return;
9988 }
9989 if self.read_only(cx) {
9990 return;
9991 }
9992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9993 let mut selections = self.selections.all_adjusted(cx);
9994 let buffer = self.buffer.read(cx);
9995 let snapshot = buffer.snapshot(cx);
9996 let rows_iter = selections.iter().map(|s| s.head().row);
9997 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9998
9999 let has_some_cursor_in_whitespace = selections
10000 .iter()
10001 .filter(|selection| selection.is_empty())
10002 .any(|selection| {
10003 let cursor = selection.head();
10004 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10005 cursor.column < current_indent.len
10006 });
10007
10008 let mut edits = Vec::new();
10009 let mut prev_edited_row = 0;
10010 let mut row_delta = 0;
10011 for selection in &mut selections {
10012 if selection.start.row != prev_edited_row {
10013 row_delta = 0;
10014 }
10015 prev_edited_row = selection.end.row;
10016
10017 // If the selection is non-empty, then increase the indentation of the selected lines.
10018 if !selection.is_empty() {
10019 row_delta =
10020 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10021 continue;
10022 }
10023
10024 let cursor = selection.head();
10025 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10026 if let Some(suggested_indent) =
10027 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10028 {
10029 // Don't do anything if already at suggested indent
10030 // and there is any other cursor which is not
10031 if has_some_cursor_in_whitespace
10032 && cursor.column == current_indent.len
10033 && current_indent.len == suggested_indent.len
10034 {
10035 continue;
10036 }
10037
10038 // Adjust line and move cursor to suggested indent
10039 // if cursor is not at suggested indent
10040 if cursor.column < suggested_indent.len
10041 && cursor.column <= current_indent.len
10042 && current_indent.len <= suggested_indent.len
10043 {
10044 selection.start = Point::new(cursor.row, suggested_indent.len);
10045 selection.end = selection.start;
10046 if row_delta == 0 {
10047 edits.extend(Buffer::edit_for_indent_size_adjustment(
10048 cursor.row,
10049 current_indent,
10050 suggested_indent,
10051 ));
10052 row_delta = suggested_indent.len - current_indent.len;
10053 }
10054 continue;
10055 }
10056
10057 // If current indent is more than suggested indent
10058 // only move cursor to current indent and skip indent
10059 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10060 selection.start = Point::new(cursor.row, current_indent.len);
10061 selection.end = selection.start;
10062 continue;
10063 }
10064 }
10065
10066 // Otherwise, insert a hard or soft tab.
10067 let settings = buffer.language_settings_at(cursor, cx);
10068 let tab_size = if settings.hard_tabs {
10069 IndentSize::tab()
10070 } else {
10071 let tab_size = settings.tab_size.get();
10072 let indent_remainder = snapshot
10073 .text_for_range(Point::new(cursor.row, 0)..cursor)
10074 .flat_map(str::chars)
10075 .fold(row_delta % tab_size, |counter: u32, c| {
10076 if c == '\t' {
10077 0
10078 } else {
10079 (counter + 1) % tab_size
10080 }
10081 });
10082
10083 let chars_to_next_tab_stop = tab_size - indent_remainder;
10084 IndentSize::spaces(chars_to_next_tab_stop)
10085 };
10086 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10087 selection.end = selection.start;
10088 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10089 row_delta += tab_size.len;
10090 }
10091
10092 self.transact(window, cx, |this, window, cx| {
10093 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10094 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10095 this.refresh_edit_prediction(true, false, window, cx);
10096 });
10097 }
10098
10099 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10100 if self.read_only(cx) {
10101 return;
10102 }
10103 if self.mode.is_single_line() {
10104 cx.propagate();
10105 return;
10106 }
10107
10108 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10109 let mut selections = self.selections.all::<Point>(cx);
10110 let mut prev_edited_row = 0;
10111 let mut row_delta = 0;
10112 let mut edits = Vec::new();
10113 let buffer = self.buffer.read(cx);
10114 let snapshot = buffer.snapshot(cx);
10115 for selection in &mut selections {
10116 if selection.start.row != prev_edited_row {
10117 row_delta = 0;
10118 }
10119 prev_edited_row = selection.end.row;
10120
10121 row_delta =
10122 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10123 }
10124
10125 self.transact(window, cx, |this, window, cx| {
10126 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10127 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10128 });
10129 }
10130
10131 fn indent_selection(
10132 buffer: &MultiBuffer,
10133 snapshot: &MultiBufferSnapshot,
10134 selection: &mut Selection<Point>,
10135 edits: &mut Vec<(Range<Point>, String)>,
10136 delta_for_start_row: u32,
10137 cx: &App,
10138 ) -> u32 {
10139 let settings = buffer.language_settings_at(selection.start, cx);
10140 let tab_size = settings.tab_size.get();
10141 let indent_kind = if settings.hard_tabs {
10142 IndentKind::Tab
10143 } else {
10144 IndentKind::Space
10145 };
10146 let mut start_row = selection.start.row;
10147 let mut end_row = selection.end.row + 1;
10148
10149 // If a selection ends at the beginning of a line, don't indent
10150 // that last line.
10151 if selection.end.column == 0 && selection.end.row > selection.start.row {
10152 end_row -= 1;
10153 }
10154
10155 // Avoid re-indenting a row that has already been indented by a
10156 // previous selection, but still update this selection's column
10157 // to reflect that indentation.
10158 if delta_for_start_row > 0 {
10159 start_row += 1;
10160 selection.start.column += delta_for_start_row;
10161 if selection.end.row == selection.start.row {
10162 selection.end.column += delta_for_start_row;
10163 }
10164 }
10165
10166 let mut delta_for_end_row = 0;
10167 let has_multiple_rows = start_row + 1 != end_row;
10168 for row in start_row..end_row {
10169 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10170 let indent_delta = match (current_indent.kind, indent_kind) {
10171 (IndentKind::Space, IndentKind::Space) => {
10172 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10173 IndentSize::spaces(columns_to_next_tab_stop)
10174 }
10175 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10176 (_, IndentKind::Tab) => IndentSize::tab(),
10177 };
10178
10179 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10180 0
10181 } else {
10182 selection.start.column
10183 };
10184 let row_start = Point::new(row, start);
10185 edits.push((
10186 row_start..row_start,
10187 indent_delta.chars().collect::<String>(),
10188 ));
10189
10190 // Update this selection's endpoints to reflect the indentation.
10191 if row == selection.start.row {
10192 selection.start.column += indent_delta.len;
10193 }
10194 if row == selection.end.row {
10195 selection.end.column += indent_delta.len;
10196 delta_for_end_row = indent_delta.len;
10197 }
10198 }
10199
10200 if selection.start.row == selection.end.row {
10201 delta_for_start_row + delta_for_end_row
10202 } else {
10203 delta_for_end_row
10204 }
10205 }
10206
10207 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10208 if self.read_only(cx) {
10209 return;
10210 }
10211 if self.mode.is_single_line() {
10212 cx.propagate();
10213 return;
10214 }
10215
10216 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10217 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10218 let selections = self.selections.all::<Point>(cx);
10219 let mut deletion_ranges = Vec::new();
10220 let mut last_outdent = None;
10221 {
10222 let buffer = self.buffer.read(cx);
10223 let snapshot = buffer.snapshot(cx);
10224 for selection in &selections {
10225 let settings = buffer.language_settings_at(selection.start, cx);
10226 let tab_size = settings.tab_size.get();
10227 let mut rows = selection.spanned_rows(false, &display_map);
10228
10229 // Avoid re-outdenting a row that has already been outdented by a
10230 // previous selection.
10231 if let Some(last_row) = last_outdent
10232 && last_row == rows.start
10233 {
10234 rows.start = rows.start.next_row();
10235 }
10236 let has_multiple_rows = rows.len() > 1;
10237 for row in rows.iter_rows() {
10238 let indent_size = snapshot.indent_size_for_line(row);
10239 if indent_size.len > 0 {
10240 let deletion_len = match indent_size.kind {
10241 IndentKind::Space => {
10242 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10243 if columns_to_prev_tab_stop == 0 {
10244 tab_size
10245 } else {
10246 columns_to_prev_tab_stop
10247 }
10248 }
10249 IndentKind::Tab => 1,
10250 };
10251 let start = if has_multiple_rows
10252 || deletion_len > selection.start.column
10253 || indent_size.len < selection.start.column
10254 {
10255 0
10256 } else {
10257 selection.start.column - deletion_len
10258 };
10259 deletion_ranges.push(
10260 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10261 );
10262 last_outdent = Some(row);
10263 }
10264 }
10265 }
10266 }
10267
10268 self.transact(window, cx, |this, window, cx| {
10269 this.buffer.update(cx, |buffer, cx| {
10270 let empty_str: Arc<str> = Arc::default();
10271 buffer.edit(
10272 deletion_ranges
10273 .into_iter()
10274 .map(|range| (range, empty_str.clone())),
10275 None,
10276 cx,
10277 );
10278 });
10279 let selections = this.selections.all::<usize>(cx);
10280 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10281 });
10282 }
10283
10284 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10285 if self.read_only(cx) {
10286 return;
10287 }
10288 if self.mode.is_single_line() {
10289 cx.propagate();
10290 return;
10291 }
10292
10293 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10294 let selections = self
10295 .selections
10296 .all::<usize>(cx)
10297 .into_iter()
10298 .map(|s| s.range());
10299
10300 self.transact(window, cx, |this, window, cx| {
10301 this.buffer.update(cx, |buffer, cx| {
10302 buffer.autoindent_ranges(selections, cx);
10303 });
10304 let selections = this.selections.all::<usize>(cx);
10305 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10306 });
10307 }
10308
10309 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10310 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10311 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10312 let selections = self.selections.all::<Point>(cx);
10313
10314 let mut new_cursors = Vec::new();
10315 let mut edit_ranges = Vec::new();
10316 let mut selections = selections.iter().peekable();
10317 while let Some(selection) = selections.next() {
10318 let mut rows = selection.spanned_rows(false, &display_map);
10319 let goal_display_column = selection.head().to_display_point(&display_map).column();
10320
10321 // Accumulate contiguous regions of rows that we want to delete.
10322 while let Some(next_selection) = selections.peek() {
10323 let next_rows = next_selection.spanned_rows(false, &display_map);
10324 if next_rows.start <= rows.end {
10325 rows.end = next_rows.end;
10326 selections.next().unwrap();
10327 } else {
10328 break;
10329 }
10330 }
10331
10332 let buffer = &display_map.buffer_snapshot;
10333 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10334 let edit_end;
10335 let cursor_buffer_row;
10336 if buffer.max_point().row >= rows.end.0 {
10337 // If there's a line after the range, delete the \n from the end of the row range
10338 // and position the cursor on the next line.
10339 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10340 cursor_buffer_row = rows.end;
10341 } else {
10342 // If there isn't a line after the range, delete the \n from the line before the
10343 // start of the row range and position the cursor there.
10344 edit_start = edit_start.saturating_sub(1);
10345 edit_end = buffer.len();
10346 cursor_buffer_row = rows.start.previous_row();
10347 }
10348
10349 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10350 *cursor.column_mut() =
10351 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10352
10353 new_cursors.push((
10354 selection.id,
10355 buffer.anchor_after(cursor.to_point(&display_map)),
10356 ));
10357 edit_ranges.push(edit_start..edit_end);
10358 }
10359
10360 self.transact(window, cx, |this, window, cx| {
10361 let buffer = this.buffer.update(cx, |buffer, cx| {
10362 let empty_str: Arc<str> = Arc::default();
10363 buffer.edit(
10364 edit_ranges
10365 .into_iter()
10366 .map(|range| (range, empty_str.clone())),
10367 None,
10368 cx,
10369 );
10370 buffer.snapshot(cx)
10371 });
10372 let new_selections = new_cursors
10373 .into_iter()
10374 .map(|(id, cursor)| {
10375 let cursor = cursor.to_point(&buffer);
10376 Selection {
10377 id,
10378 start: cursor,
10379 end: cursor,
10380 reversed: false,
10381 goal: SelectionGoal::None,
10382 }
10383 })
10384 .collect();
10385
10386 this.change_selections(Default::default(), window, cx, |s| {
10387 s.select(new_selections);
10388 });
10389 });
10390 }
10391
10392 pub fn join_lines_impl(
10393 &mut self,
10394 insert_whitespace: bool,
10395 window: &mut Window,
10396 cx: &mut Context<Self>,
10397 ) {
10398 if self.read_only(cx) {
10399 return;
10400 }
10401 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10402 for selection in self.selections.all::<Point>(cx) {
10403 let start = MultiBufferRow(selection.start.row);
10404 // Treat single line selections as if they include the next line. Otherwise this action
10405 // would do nothing for single line selections individual cursors.
10406 let end = if selection.start.row == selection.end.row {
10407 MultiBufferRow(selection.start.row + 1)
10408 } else {
10409 MultiBufferRow(selection.end.row)
10410 };
10411
10412 if let Some(last_row_range) = row_ranges.last_mut()
10413 && start <= last_row_range.end
10414 {
10415 last_row_range.end = end;
10416 continue;
10417 }
10418 row_ranges.push(start..end);
10419 }
10420
10421 let snapshot = self.buffer.read(cx).snapshot(cx);
10422 let mut cursor_positions = Vec::new();
10423 for row_range in &row_ranges {
10424 let anchor = snapshot.anchor_before(Point::new(
10425 row_range.end.previous_row().0,
10426 snapshot.line_len(row_range.end.previous_row()),
10427 ));
10428 cursor_positions.push(anchor..anchor);
10429 }
10430
10431 self.transact(window, cx, |this, window, cx| {
10432 for row_range in row_ranges.into_iter().rev() {
10433 for row in row_range.iter_rows().rev() {
10434 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10435 let next_line_row = row.next_row();
10436 let indent = snapshot.indent_size_for_line(next_line_row);
10437 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10438
10439 let replace =
10440 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10441 " "
10442 } else {
10443 ""
10444 };
10445
10446 this.buffer.update(cx, |buffer, cx| {
10447 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10448 });
10449 }
10450 }
10451
10452 this.change_selections(Default::default(), window, cx, |s| {
10453 s.select_anchor_ranges(cursor_positions)
10454 });
10455 });
10456 }
10457
10458 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10459 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10460 self.join_lines_impl(true, window, cx);
10461 }
10462
10463 pub fn sort_lines_case_sensitive(
10464 &mut self,
10465 _: &SortLinesCaseSensitive,
10466 window: &mut Window,
10467 cx: &mut Context<Self>,
10468 ) {
10469 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10470 }
10471
10472 pub fn sort_lines_by_length(
10473 &mut self,
10474 _: &SortLinesByLength,
10475 window: &mut Window,
10476 cx: &mut Context<Self>,
10477 ) {
10478 self.manipulate_immutable_lines(window, cx, |lines| {
10479 lines.sort_by_key(|&line| line.chars().count())
10480 })
10481 }
10482
10483 pub fn sort_lines_case_insensitive(
10484 &mut self,
10485 _: &SortLinesCaseInsensitive,
10486 window: &mut Window,
10487 cx: &mut Context<Self>,
10488 ) {
10489 self.manipulate_immutable_lines(window, cx, |lines| {
10490 lines.sort_by_key(|line| line.to_lowercase())
10491 })
10492 }
10493
10494 pub fn unique_lines_case_insensitive(
10495 &mut self,
10496 _: &UniqueLinesCaseInsensitive,
10497 window: &mut Window,
10498 cx: &mut Context<Self>,
10499 ) {
10500 self.manipulate_immutable_lines(window, cx, |lines| {
10501 let mut seen = HashSet::default();
10502 lines.retain(|line| seen.insert(line.to_lowercase()));
10503 })
10504 }
10505
10506 pub fn unique_lines_case_sensitive(
10507 &mut self,
10508 _: &UniqueLinesCaseSensitive,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 ) {
10512 self.manipulate_immutable_lines(window, cx, |lines| {
10513 let mut seen = HashSet::default();
10514 lines.retain(|line| seen.insert(*line));
10515 })
10516 }
10517
10518 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10519 let snapshot = self.buffer.read(cx).snapshot(cx);
10520 for selection in self.selections.disjoint_anchors().iter() {
10521 if snapshot
10522 .language_at(selection.start)
10523 .and_then(|lang| lang.config().wrap_characters.as_ref())
10524 .is_some()
10525 {
10526 return true;
10527 }
10528 }
10529 false
10530 }
10531
10532 fn wrap_selections_in_tag(
10533 &mut self,
10534 _: &WrapSelectionsInTag,
10535 window: &mut Window,
10536 cx: &mut Context<Self>,
10537 ) {
10538 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10539
10540 let snapshot = self.buffer.read(cx).snapshot(cx);
10541
10542 let mut edits = Vec::new();
10543 let mut boundaries = Vec::new();
10544
10545 for selection in self.selections.all::<Point>(cx).iter() {
10546 let Some(wrap_config) = snapshot
10547 .language_at(selection.start)
10548 .and_then(|lang| lang.config().wrap_characters.clone())
10549 else {
10550 continue;
10551 };
10552
10553 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10554 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10555
10556 let start_before = snapshot.anchor_before(selection.start);
10557 let end_after = snapshot.anchor_after(selection.end);
10558
10559 edits.push((start_before..start_before, open_tag));
10560 edits.push((end_after..end_after, close_tag));
10561
10562 boundaries.push((
10563 start_before,
10564 end_after,
10565 wrap_config.start_prefix.len(),
10566 wrap_config.end_suffix.len(),
10567 ));
10568 }
10569
10570 if edits.is_empty() {
10571 return;
10572 }
10573
10574 self.transact(window, cx, |this, window, cx| {
10575 let buffer = this.buffer.update(cx, |buffer, cx| {
10576 buffer.edit(edits, None, cx);
10577 buffer.snapshot(cx)
10578 });
10579
10580 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10581 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10582 boundaries.into_iter()
10583 {
10584 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10585 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10586 new_selections.push(open_offset..open_offset);
10587 new_selections.push(close_offset..close_offset);
10588 }
10589
10590 this.change_selections(Default::default(), window, cx, |s| {
10591 s.select_ranges(new_selections);
10592 });
10593
10594 this.request_autoscroll(Autoscroll::fit(), cx);
10595 });
10596 }
10597
10598 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10599 let Some(project) = self.project.clone() else {
10600 return;
10601 };
10602 self.reload(project, window, cx)
10603 .detach_and_notify_err(window, cx);
10604 }
10605
10606 pub fn restore_file(
10607 &mut self,
10608 _: &::git::RestoreFile,
10609 window: &mut Window,
10610 cx: &mut Context<Self>,
10611 ) {
10612 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10613 let mut buffer_ids = HashSet::default();
10614 let snapshot = self.buffer().read(cx).snapshot(cx);
10615 for selection in self.selections.all::<usize>(cx) {
10616 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10617 }
10618
10619 let buffer = self.buffer().read(cx);
10620 let ranges = buffer_ids
10621 .into_iter()
10622 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10623 .collect::<Vec<_>>();
10624
10625 self.restore_hunks_in_ranges(ranges, window, cx);
10626 }
10627
10628 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10630 let selections = self
10631 .selections
10632 .all(cx)
10633 .into_iter()
10634 .map(|s| s.range())
10635 .collect();
10636 self.restore_hunks_in_ranges(selections, window, cx);
10637 }
10638
10639 pub fn restore_hunks_in_ranges(
10640 &mut self,
10641 ranges: Vec<Range<Point>>,
10642 window: &mut Window,
10643 cx: &mut Context<Editor>,
10644 ) {
10645 let mut revert_changes = HashMap::default();
10646 let chunk_by = self
10647 .snapshot(window, cx)
10648 .hunks_for_ranges(ranges)
10649 .into_iter()
10650 .chunk_by(|hunk| hunk.buffer_id);
10651 for (buffer_id, hunks) in &chunk_by {
10652 let hunks = hunks.collect::<Vec<_>>();
10653 for hunk in &hunks {
10654 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10655 }
10656 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10657 }
10658 drop(chunk_by);
10659 if !revert_changes.is_empty() {
10660 self.transact(window, cx, |editor, window, cx| {
10661 editor.restore(revert_changes, window, cx);
10662 });
10663 }
10664 }
10665
10666 pub fn open_active_item_in_terminal(
10667 &mut self,
10668 _: &OpenInTerminal,
10669 window: &mut Window,
10670 cx: &mut Context<Self>,
10671 ) {
10672 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10673 let project_path = buffer.read(cx).project_path(cx)?;
10674 let project = self.project()?.read(cx);
10675 let entry = project.entry_for_path(&project_path, cx)?;
10676 let parent = match &entry.canonical_path {
10677 Some(canonical_path) => canonical_path.to_path_buf(),
10678 None => project.absolute_path(&project_path, cx)?,
10679 }
10680 .parent()?
10681 .to_path_buf();
10682 Some(parent)
10683 }) {
10684 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10685 }
10686 }
10687
10688 fn set_breakpoint_context_menu(
10689 &mut self,
10690 display_row: DisplayRow,
10691 position: Option<Anchor>,
10692 clicked_point: gpui::Point<Pixels>,
10693 window: &mut Window,
10694 cx: &mut Context<Self>,
10695 ) {
10696 let source = self
10697 .buffer
10698 .read(cx)
10699 .snapshot(cx)
10700 .anchor_before(Point::new(display_row.0, 0u32));
10701
10702 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10703
10704 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10705 self,
10706 source,
10707 clicked_point,
10708 context_menu,
10709 window,
10710 cx,
10711 );
10712 }
10713
10714 fn add_edit_breakpoint_block(
10715 &mut self,
10716 anchor: Anchor,
10717 breakpoint: &Breakpoint,
10718 edit_action: BreakpointPromptEditAction,
10719 window: &mut Window,
10720 cx: &mut Context<Self>,
10721 ) {
10722 let weak_editor = cx.weak_entity();
10723 let bp_prompt = cx.new(|cx| {
10724 BreakpointPromptEditor::new(
10725 weak_editor,
10726 anchor,
10727 breakpoint.clone(),
10728 edit_action,
10729 window,
10730 cx,
10731 )
10732 });
10733
10734 let height = bp_prompt.update(cx, |this, cx| {
10735 this.prompt
10736 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10737 });
10738 let cloned_prompt = bp_prompt.clone();
10739 let blocks = vec![BlockProperties {
10740 style: BlockStyle::Sticky,
10741 placement: BlockPlacement::Above(anchor),
10742 height: Some(height),
10743 render: Arc::new(move |cx| {
10744 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10745 cloned_prompt.clone().into_any_element()
10746 }),
10747 priority: 0,
10748 }];
10749
10750 let focus_handle = bp_prompt.focus_handle(cx);
10751 window.focus(&focus_handle);
10752
10753 let block_ids = self.insert_blocks(blocks, None, cx);
10754 bp_prompt.update(cx, |prompt, _| {
10755 prompt.add_block_ids(block_ids);
10756 });
10757 }
10758
10759 pub(crate) fn breakpoint_at_row(
10760 &self,
10761 row: u32,
10762 window: &mut Window,
10763 cx: &mut Context<Self>,
10764 ) -> Option<(Anchor, Breakpoint)> {
10765 let snapshot = self.snapshot(window, cx);
10766 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10767
10768 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10769 }
10770
10771 pub(crate) fn breakpoint_at_anchor(
10772 &self,
10773 breakpoint_position: Anchor,
10774 snapshot: &EditorSnapshot,
10775 cx: &mut Context<Self>,
10776 ) -> Option<(Anchor, Breakpoint)> {
10777 let buffer = self
10778 .buffer
10779 .read(cx)
10780 .buffer_for_anchor(breakpoint_position, cx)?;
10781
10782 let enclosing_excerpt = breakpoint_position.excerpt_id;
10783 let buffer_snapshot = buffer.read(cx).snapshot();
10784
10785 let row = buffer_snapshot
10786 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10787 .row;
10788
10789 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10790 let anchor_end = snapshot
10791 .buffer_snapshot
10792 .anchor_after(Point::new(row, line_len));
10793
10794 self.breakpoint_store
10795 .as_ref()?
10796 .read_with(cx, |breakpoint_store, cx| {
10797 breakpoint_store
10798 .breakpoints(
10799 &buffer,
10800 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10801 &buffer_snapshot,
10802 cx,
10803 )
10804 .next()
10805 .and_then(|(bp, _)| {
10806 let breakpoint_row = buffer_snapshot
10807 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10808 .row;
10809
10810 if breakpoint_row == row {
10811 snapshot
10812 .buffer_snapshot
10813 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10814 .map(|position| (position, bp.bp.clone()))
10815 } else {
10816 None
10817 }
10818 })
10819 })
10820 }
10821
10822 pub fn edit_log_breakpoint(
10823 &mut self,
10824 _: &EditLogBreakpoint,
10825 window: &mut Window,
10826 cx: &mut Context<Self>,
10827 ) {
10828 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10829 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10830 message: None,
10831 state: BreakpointState::Enabled,
10832 condition: None,
10833 hit_condition: None,
10834 });
10835
10836 self.add_edit_breakpoint_block(
10837 anchor,
10838 &breakpoint,
10839 BreakpointPromptEditAction::Log,
10840 window,
10841 cx,
10842 );
10843 }
10844 }
10845
10846 fn breakpoints_at_cursors(
10847 &self,
10848 window: &mut Window,
10849 cx: &mut Context<Self>,
10850 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10851 let snapshot = self.snapshot(window, cx);
10852 let cursors = self
10853 .selections
10854 .disjoint_anchors()
10855 .iter()
10856 .map(|selection| {
10857 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10858
10859 let breakpoint_position = self
10860 .breakpoint_at_row(cursor_position.row, window, cx)
10861 .map(|bp| bp.0)
10862 .unwrap_or_else(|| {
10863 snapshot
10864 .display_snapshot
10865 .buffer_snapshot
10866 .anchor_after(Point::new(cursor_position.row, 0))
10867 });
10868
10869 let breakpoint = self
10870 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10871 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10872
10873 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10874 })
10875 // 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.
10876 .collect::<HashMap<Anchor, _>>();
10877
10878 cursors.into_iter().collect()
10879 }
10880
10881 pub fn enable_breakpoint(
10882 &mut self,
10883 _: &crate::actions::EnableBreakpoint,
10884 window: &mut Window,
10885 cx: &mut Context<Self>,
10886 ) {
10887 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10888 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10889 continue;
10890 };
10891 self.edit_breakpoint_at_anchor(
10892 anchor,
10893 breakpoint,
10894 BreakpointEditAction::InvertState,
10895 cx,
10896 );
10897 }
10898 }
10899
10900 pub fn disable_breakpoint(
10901 &mut self,
10902 _: &crate::actions::DisableBreakpoint,
10903 window: &mut Window,
10904 cx: &mut Context<Self>,
10905 ) {
10906 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10907 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10908 continue;
10909 };
10910 self.edit_breakpoint_at_anchor(
10911 anchor,
10912 breakpoint,
10913 BreakpointEditAction::InvertState,
10914 cx,
10915 );
10916 }
10917 }
10918
10919 pub fn toggle_breakpoint(
10920 &mut self,
10921 _: &crate::actions::ToggleBreakpoint,
10922 window: &mut Window,
10923 cx: &mut Context<Self>,
10924 ) {
10925 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10926 if let Some(breakpoint) = breakpoint {
10927 self.edit_breakpoint_at_anchor(
10928 anchor,
10929 breakpoint,
10930 BreakpointEditAction::Toggle,
10931 cx,
10932 );
10933 } else {
10934 self.edit_breakpoint_at_anchor(
10935 anchor,
10936 Breakpoint::new_standard(),
10937 BreakpointEditAction::Toggle,
10938 cx,
10939 );
10940 }
10941 }
10942 }
10943
10944 pub fn edit_breakpoint_at_anchor(
10945 &mut self,
10946 breakpoint_position: Anchor,
10947 breakpoint: Breakpoint,
10948 edit_action: BreakpointEditAction,
10949 cx: &mut Context<Self>,
10950 ) {
10951 let Some(breakpoint_store) = &self.breakpoint_store else {
10952 return;
10953 };
10954
10955 let Some(buffer) = self
10956 .buffer
10957 .read(cx)
10958 .buffer_for_anchor(breakpoint_position, cx)
10959 else {
10960 return;
10961 };
10962
10963 breakpoint_store.update(cx, |breakpoint_store, cx| {
10964 breakpoint_store.toggle_breakpoint(
10965 buffer,
10966 BreakpointWithPosition {
10967 position: breakpoint_position.text_anchor,
10968 bp: breakpoint,
10969 },
10970 edit_action,
10971 cx,
10972 );
10973 });
10974
10975 cx.notify();
10976 }
10977
10978 #[cfg(any(test, feature = "test-support"))]
10979 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10980 self.breakpoint_store.clone()
10981 }
10982
10983 pub fn prepare_restore_change(
10984 &self,
10985 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10986 hunk: &MultiBufferDiffHunk,
10987 cx: &mut App,
10988 ) -> Option<()> {
10989 if hunk.is_created_file() {
10990 return None;
10991 }
10992 let buffer = self.buffer.read(cx);
10993 let diff = buffer.diff_for(hunk.buffer_id)?;
10994 let buffer = buffer.buffer(hunk.buffer_id)?;
10995 let buffer = buffer.read(cx);
10996 let original_text = diff
10997 .read(cx)
10998 .base_text()
10999 .as_rope()
11000 .slice(hunk.diff_base_byte_range.clone());
11001 let buffer_snapshot = buffer.snapshot();
11002 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11003 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11004 probe
11005 .0
11006 .start
11007 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11008 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11009 }) {
11010 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11011 Some(())
11012 } else {
11013 None
11014 }
11015 }
11016
11017 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11018 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11019 }
11020
11021 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11022 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11023 }
11024
11025 fn manipulate_lines<M>(
11026 &mut self,
11027 window: &mut Window,
11028 cx: &mut Context<Self>,
11029 mut manipulate: M,
11030 ) where
11031 M: FnMut(&str) -> LineManipulationResult,
11032 {
11033 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11034
11035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11036 let buffer = self.buffer.read(cx).snapshot(cx);
11037
11038 let mut edits = Vec::new();
11039
11040 let selections = self.selections.all::<Point>(cx);
11041 let mut selections = selections.iter().peekable();
11042 let mut contiguous_row_selections = Vec::new();
11043 let mut new_selections = Vec::new();
11044 let mut added_lines = 0;
11045 let mut removed_lines = 0;
11046
11047 while let Some(selection) = selections.next() {
11048 let (start_row, end_row) = consume_contiguous_rows(
11049 &mut contiguous_row_selections,
11050 selection,
11051 &display_map,
11052 &mut selections,
11053 );
11054
11055 let start_point = Point::new(start_row.0, 0);
11056 let end_point = Point::new(
11057 end_row.previous_row().0,
11058 buffer.line_len(end_row.previous_row()),
11059 );
11060 let text = buffer
11061 .text_for_range(start_point..end_point)
11062 .collect::<String>();
11063
11064 let LineManipulationResult {
11065 new_text,
11066 line_count_before,
11067 line_count_after,
11068 } = manipulate(&text);
11069
11070 edits.push((start_point..end_point, new_text));
11071
11072 // Selections must change based on added and removed line count
11073 let start_row =
11074 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11075 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11076 new_selections.push(Selection {
11077 id: selection.id,
11078 start: start_row,
11079 end: end_row,
11080 goal: SelectionGoal::None,
11081 reversed: selection.reversed,
11082 });
11083
11084 if line_count_after > line_count_before {
11085 added_lines += line_count_after - line_count_before;
11086 } else if line_count_before > line_count_after {
11087 removed_lines += line_count_before - line_count_after;
11088 }
11089 }
11090
11091 self.transact(window, cx, |this, window, cx| {
11092 let buffer = this.buffer.update(cx, |buffer, cx| {
11093 buffer.edit(edits, None, cx);
11094 buffer.snapshot(cx)
11095 });
11096
11097 // Recalculate offsets on newly edited buffer
11098 let new_selections = new_selections
11099 .iter()
11100 .map(|s| {
11101 let start_point = Point::new(s.start.0, 0);
11102 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11103 Selection {
11104 id: s.id,
11105 start: buffer.point_to_offset(start_point),
11106 end: buffer.point_to_offset(end_point),
11107 goal: s.goal,
11108 reversed: s.reversed,
11109 }
11110 })
11111 .collect();
11112
11113 this.change_selections(Default::default(), window, cx, |s| {
11114 s.select(new_selections);
11115 });
11116
11117 this.request_autoscroll(Autoscroll::fit(), cx);
11118 });
11119 }
11120
11121 fn manipulate_immutable_lines<Fn>(
11122 &mut self,
11123 window: &mut Window,
11124 cx: &mut Context<Self>,
11125 mut callback: Fn,
11126 ) where
11127 Fn: FnMut(&mut Vec<&str>),
11128 {
11129 self.manipulate_lines(window, cx, |text| {
11130 let mut lines: Vec<&str> = text.split('\n').collect();
11131 let line_count_before = lines.len();
11132
11133 callback(&mut lines);
11134
11135 LineManipulationResult {
11136 new_text: lines.join("\n"),
11137 line_count_before,
11138 line_count_after: lines.len(),
11139 }
11140 });
11141 }
11142
11143 fn manipulate_mutable_lines<Fn>(
11144 &mut self,
11145 window: &mut Window,
11146 cx: &mut Context<Self>,
11147 mut callback: Fn,
11148 ) where
11149 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11150 {
11151 self.manipulate_lines(window, cx, |text| {
11152 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11153 let line_count_before = lines.len();
11154
11155 callback(&mut lines);
11156
11157 LineManipulationResult {
11158 new_text: lines.join("\n"),
11159 line_count_before,
11160 line_count_after: lines.len(),
11161 }
11162 });
11163 }
11164
11165 pub fn convert_indentation_to_spaces(
11166 &mut self,
11167 _: &ConvertIndentationToSpaces,
11168 window: &mut Window,
11169 cx: &mut Context<Self>,
11170 ) {
11171 let settings = self.buffer.read(cx).language_settings(cx);
11172 let tab_size = settings.tab_size.get() as usize;
11173
11174 self.manipulate_mutable_lines(window, cx, |lines| {
11175 // Allocates a reasonably sized scratch buffer once for the whole loop
11176 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11177 // Avoids recomputing spaces that could be inserted many times
11178 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11179 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11180 .collect();
11181
11182 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11183 let mut chars = line.as_ref().chars();
11184 let mut col = 0;
11185 let mut changed = false;
11186
11187 for ch in chars.by_ref() {
11188 match ch {
11189 ' ' => {
11190 reindented_line.push(' ');
11191 col += 1;
11192 }
11193 '\t' => {
11194 // \t are converted to spaces depending on the current column
11195 let spaces_len = tab_size - (col % tab_size);
11196 reindented_line.extend(&space_cache[spaces_len - 1]);
11197 col += spaces_len;
11198 changed = true;
11199 }
11200 _ => {
11201 // If we dont append before break, the character is consumed
11202 reindented_line.push(ch);
11203 break;
11204 }
11205 }
11206 }
11207
11208 if !changed {
11209 reindented_line.clear();
11210 continue;
11211 }
11212 // Append the rest of the line and replace old reference with new one
11213 reindented_line.extend(chars);
11214 *line = Cow::Owned(reindented_line.clone());
11215 reindented_line.clear();
11216 }
11217 });
11218 }
11219
11220 pub fn convert_indentation_to_tabs(
11221 &mut self,
11222 _: &ConvertIndentationToTabs,
11223 window: &mut Window,
11224 cx: &mut Context<Self>,
11225 ) {
11226 let settings = self.buffer.read(cx).language_settings(cx);
11227 let tab_size = settings.tab_size.get() as usize;
11228
11229 self.manipulate_mutable_lines(window, cx, |lines| {
11230 // Allocates a reasonably sized buffer once for the whole loop
11231 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11232 // Avoids recomputing spaces that could be inserted many times
11233 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11234 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11235 .collect();
11236
11237 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11238 let mut chars = line.chars();
11239 let mut spaces_count = 0;
11240 let mut first_non_indent_char = None;
11241 let mut changed = false;
11242
11243 for ch in chars.by_ref() {
11244 match ch {
11245 ' ' => {
11246 // Keep track of spaces. Append \t when we reach tab_size
11247 spaces_count += 1;
11248 changed = true;
11249 if spaces_count == tab_size {
11250 reindented_line.push('\t');
11251 spaces_count = 0;
11252 }
11253 }
11254 '\t' => {
11255 reindented_line.push('\t');
11256 spaces_count = 0;
11257 }
11258 _ => {
11259 // Dont append it yet, we might have remaining spaces
11260 first_non_indent_char = Some(ch);
11261 break;
11262 }
11263 }
11264 }
11265
11266 if !changed {
11267 reindented_line.clear();
11268 continue;
11269 }
11270 // Remaining spaces that didn't make a full tab stop
11271 if spaces_count > 0 {
11272 reindented_line.extend(&space_cache[spaces_count - 1]);
11273 }
11274 // If we consume an extra character that was not indentation, add it back
11275 if let Some(extra_char) = first_non_indent_char {
11276 reindented_line.push(extra_char);
11277 }
11278 // Append the rest of the line and replace old reference with new one
11279 reindented_line.extend(chars);
11280 *line = Cow::Owned(reindented_line.clone());
11281 reindented_line.clear();
11282 }
11283 });
11284 }
11285
11286 pub fn convert_to_upper_case(
11287 &mut self,
11288 _: &ConvertToUpperCase,
11289 window: &mut Window,
11290 cx: &mut Context<Self>,
11291 ) {
11292 self.manipulate_text(window, cx, |text| text.to_uppercase())
11293 }
11294
11295 pub fn convert_to_lower_case(
11296 &mut self,
11297 _: &ConvertToLowerCase,
11298 window: &mut Window,
11299 cx: &mut Context<Self>,
11300 ) {
11301 self.manipulate_text(window, cx, |text| text.to_lowercase())
11302 }
11303
11304 pub fn convert_to_title_case(
11305 &mut self,
11306 _: &ConvertToTitleCase,
11307 window: &mut Window,
11308 cx: &mut Context<Self>,
11309 ) {
11310 self.manipulate_text(window, cx, |text| {
11311 text.split('\n')
11312 .map(|line| line.to_case(Case::Title))
11313 .join("\n")
11314 })
11315 }
11316
11317 pub fn convert_to_snake_case(
11318 &mut self,
11319 _: &ConvertToSnakeCase,
11320 window: &mut Window,
11321 cx: &mut Context<Self>,
11322 ) {
11323 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11324 }
11325
11326 pub fn convert_to_kebab_case(
11327 &mut self,
11328 _: &ConvertToKebabCase,
11329 window: &mut Window,
11330 cx: &mut Context<Self>,
11331 ) {
11332 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11333 }
11334
11335 pub fn convert_to_upper_camel_case(
11336 &mut self,
11337 _: &ConvertToUpperCamelCase,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) {
11341 self.manipulate_text(window, cx, |text| {
11342 text.split('\n')
11343 .map(|line| line.to_case(Case::UpperCamel))
11344 .join("\n")
11345 })
11346 }
11347
11348 pub fn convert_to_lower_camel_case(
11349 &mut self,
11350 _: &ConvertToLowerCamelCase,
11351 window: &mut Window,
11352 cx: &mut Context<Self>,
11353 ) {
11354 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11355 }
11356
11357 pub fn convert_to_opposite_case(
11358 &mut self,
11359 _: &ConvertToOppositeCase,
11360 window: &mut Window,
11361 cx: &mut Context<Self>,
11362 ) {
11363 self.manipulate_text(window, cx, |text| {
11364 text.chars()
11365 .fold(String::with_capacity(text.len()), |mut t, c| {
11366 if c.is_uppercase() {
11367 t.extend(c.to_lowercase());
11368 } else {
11369 t.extend(c.to_uppercase());
11370 }
11371 t
11372 })
11373 })
11374 }
11375
11376 pub fn convert_to_sentence_case(
11377 &mut self,
11378 _: &ConvertToSentenceCase,
11379 window: &mut Window,
11380 cx: &mut Context<Self>,
11381 ) {
11382 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11383 }
11384
11385 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11386 self.manipulate_text(window, cx, |text| {
11387 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11388 if has_upper_case_characters {
11389 text.to_lowercase()
11390 } else {
11391 text.to_uppercase()
11392 }
11393 })
11394 }
11395
11396 pub fn convert_to_rot13(
11397 &mut self,
11398 _: &ConvertToRot13,
11399 window: &mut Window,
11400 cx: &mut Context<Self>,
11401 ) {
11402 self.manipulate_text(window, cx, |text| {
11403 text.chars()
11404 .map(|c| match c {
11405 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11406 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11407 _ => c,
11408 })
11409 .collect()
11410 })
11411 }
11412
11413 pub fn convert_to_rot47(
11414 &mut self,
11415 _: &ConvertToRot47,
11416 window: &mut Window,
11417 cx: &mut Context<Self>,
11418 ) {
11419 self.manipulate_text(window, cx, |text| {
11420 text.chars()
11421 .map(|c| {
11422 let code_point = c as u32;
11423 if code_point >= 33 && code_point <= 126 {
11424 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11425 }
11426 c
11427 })
11428 .collect()
11429 })
11430 }
11431
11432 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11433 where
11434 Fn: FnMut(&str) -> String,
11435 {
11436 let buffer = self.buffer.read(cx).snapshot(cx);
11437
11438 let mut new_selections = Vec::new();
11439 let mut edits = Vec::new();
11440 let mut selection_adjustment = 0i32;
11441
11442 for selection in self.selections.all_adjusted(cx) {
11443 let selection_is_empty = selection.is_empty();
11444
11445 let (start, end) = if selection_is_empty {
11446 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11447 (word_range.start, word_range.end)
11448 } else {
11449 (
11450 buffer.point_to_offset(selection.start),
11451 buffer.point_to_offset(selection.end),
11452 )
11453 };
11454
11455 let text = buffer.text_for_range(start..end).collect::<String>();
11456 let old_length = text.len() as i32;
11457 let text = callback(&text);
11458
11459 new_selections.push(Selection {
11460 start: (start as i32 - selection_adjustment) as usize,
11461 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11462 goal: SelectionGoal::None,
11463 id: selection.id,
11464 reversed: selection.reversed,
11465 });
11466
11467 selection_adjustment += old_length - text.len() as i32;
11468
11469 edits.push((start..end, text));
11470 }
11471
11472 self.transact(window, cx, |this, window, cx| {
11473 this.buffer.update(cx, |buffer, cx| {
11474 buffer.edit(edits, None, cx);
11475 });
11476
11477 this.change_selections(Default::default(), window, cx, |s| {
11478 s.select(new_selections);
11479 });
11480
11481 this.request_autoscroll(Autoscroll::fit(), cx);
11482 });
11483 }
11484
11485 pub fn move_selection_on_drop(
11486 &mut self,
11487 selection: &Selection<Anchor>,
11488 target: DisplayPoint,
11489 is_cut: bool,
11490 window: &mut Window,
11491 cx: &mut Context<Self>,
11492 ) {
11493 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11494 let buffer = &display_map.buffer_snapshot;
11495 let mut edits = Vec::new();
11496 let insert_point = display_map
11497 .clip_point(target, Bias::Left)
11498 .to_point(&display_map);
11499 let text = buffer
11500 .text_for_range(selection.start..selection.end)
11501 .collect::<String>();
11502 if is_cut {
11503 edits.push(((selection.start..selection.end), String::new()));
11504 }
11505 let insert_anchor = buffer.anchor_before(insert_point);
11506 edits.push(((insert_anchor..insert_anchor), text));
11507 let last_edit_start = insert_anchor.bias_left(buffer);
11508 let last_edit_end = insert_anchor.bias_right(buffer);
11509 self.transact(window, cx, |this, window, cx| {
11510 this.buffer.update(cx, |buffer, cx| {
11511 buffer.edit(edits, None, cx);
11512 });
11513 this.change_selections(Default::default(), window, cx, |s| {
11514 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11515 });
11516 });
11517 }
11518
11519 pub fn clear_selection_drag_state(&mut self) {
11520 self.selection_drag_state = SelectionDragState::None;
11521 }
11522
11523 pub fn duplicate(
11524 &mut self,
11525 upwards: bool,
11526 whole_lines: bool,
11527 window: &mut Window,
11528 cx: &mut Context<Self>,
11529 ) {
11530 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11531
11532 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11533 let buffer = &display_map.buffer_snapshot;
11534 let selections = self.selections.all::<Point>(cx);
11535
11536 let mut edits = Vec::new();
11537 let mut selections_iter = selections.iter().peekable();
11538 while let Some(selection) = selections_iter.next() {
11539 let mut rows = selection.spanned_rows(false, &display_map);
11540 // duplicate line-wise
11541 if whole_lines || selection.start == selection.end {
11542 // Avoid duplicating the same lines twice.
11543 while let Some(next_selection) = selections_iter.peek() {
11544 let next_rows = next_selection.spanned_rows(false, &display_map);
11545 if next_rows.start < rows.end {
11546 rows.end = next_rows.end;
11547 selections_iter.next().unwrap();
11548 } else {
11549 break;
11550 }
11551 }
11552
11553 // Copy the text from the selected row region and splice it either at the start
11554 // or end of the region.
11555 let start = Point::new(rows.start.0, 0);
11556 let end = Point::new(
11557 rows.end.previous_row().0,
11558 buffer.line_len(rows.end.previous_row()),
11559 );
11560 let text = buffer
11561 .text_for_range(start..end)
11562 .chain(Some("\n"))
11563 .collect::<String>();
11564 let insert_location = if upwards {
11565 Point::new(rows.end.0, 0)
11566 } else {
11567 start
11568 };
11569 edits.push((insert_location..insert_location, text));
11570 } else {
11571 // duplicate character-wise
11572 let start = selection.start;
11573 let end = selection.end;
11574 let text = buffer.text_for_range(start..end).collect::<String>();
11575 edits.push((selection.end..selection.end, text));
11576 }
11577 }
11578
11579 self.transact(window, cx, |this, _, cx| {
11580 this.buffer.update(cx, |buffer, cx| {
11581 buffer.edit(edits, None, cx);
11582 });
11583
11584 this.request_autoscroll(Autoscroll::fit(), cx);
11585 });
11586 }
11587
11588 pub fn duplicate_line_up(
11589 &mut self,
11590 _: &DuplicateLineUp,
11591 window: &mut Window,
11592 cx: &mut Context<Self>,
11593 ) {
11594 self.duplicate(true, true, window, cx);
11595 }
11596
11597 pub fn duplicate_line_down(
11598 &mut self,
11599 _: &DuplicateLineDown,
11600 window: &mut Window,
11601 cx: &mut Context<Self>,
11602 ) {
11603 self.duplicate(false, true, window, cx);
11604 }
11605
11606 pub fn duplicate_selection(
11607 &mut self,
11608 _: &DuplicateSelection,
11609 window: &mut Window,
11610 cx: &mut Context<Self>,
11611 ) {
11612 self.duplicate(false, false, window, cx);
11613 }
11614
11615 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11616 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11617 if self.mode.is_single_line() {
11618 cx.propagate();
11619 return;
11620 }
11621
11622 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11623 let buffer = self.buffer.read(cx).snapshot(cx);
11624
11625 let mut edits = Vec::new();
11626 let mut unfold_ranges = Vec::new();
11627 let mut refold_creases = Vec::new();
11628
11629 let selections = self.selections.all::<Point>(cx);
11630 let mut selections = selections.iter().peekable();
11631 let mut contiguous_row_selections = Vec::new();
11632 let mut new_selections = Vec::new();
11633
11634 while let Some(selection) = selections.next() {
11635 // Find all the selections that span a contiguous row range
11636 let (start_row, end_row) = consume_contiguous_rows(
11637 &mut contiguous_row_selections,
11638 selection,
11639 &display_map,
11640 &mut selections,
11641 );
11642
11643 // Move the text spanned by the row range to be before the line preceding the row range
11644 if start_row.0 > 0 {
11645 let range_to_move = Point::new(
11646 start_row.previous_row().0,
11647 buffer.line_len(start_row.previous_row()),
11648 )
11649 ..Point::new(
11650 end_row.previous_row().0,
11651 buffer.line_len(end_row.previous_row()),
11652 );
11653 let insertion_point = display_map
11654 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11655 .0;
11656
11657 // Don't move lines across excerpts
11658 if buffer
11659 .excerpt_containing(insertion_point..range_to_move.end)
11660 .is_some()
11661 {
11662 let text = buffer
11663 .text_for_range(range_to_move.clone())
11664 .flat_map(|s| s.chars())
11665 .skip(1)
11666 .chain(['\n'])
11667 .collect::<String>();
11668
11669 edits.push((
11670 buffer.anchor_after(range_to_move.start)
11671 ..buffer.anchor_before(range_to_move.end),
11672 String::new(),
11673 ));
11674 let insertion_anchor = buffer.anchor_after(insertion_point);
11675 edits.push((insertion_anchor..insertion_anchor, text));
11676
11677 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11678
11679 // Move selections up
11680 new_selections.extend(contiguous_row_selections.drain(..).map(
11681 |mut selection| {
11682 selection.start.row -= row_delta;
11683 selection.end.row -= row_delta;
11684 selection
11685 },
11686 ));
11687
11688 // Move folds up
11689 unfold_ranges.push(range_to_move.clone());
11690 for fold in display_map.folds_in_range(
11691 buffer.anchor_before(range_to_move.start)
11692 ..buffer.anchor_after(range_to_move.end),
11693 ) {
11694 let mut start = fold.range.start.to_point(&buffer);
11695 let mut end = fold.range.end.to_point(&buffer);
11696 start.row -= row_delta;
11697 end.row -= row_delta;
11698 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11699 }
11700 }
11701 }
11702
11703 // If we didn't move line(s), preserve the existing selections
11704 new_selections.append(&mut contiguous_row_selections);
11705 }
11706
11707 self.transact(window, cx, |this, window, cx| {
11708 this.unfold_ranges(&unfold_ranges, true, true, cx);
11709 this.buffer.update(cx, |buffer, cx| {
11710 for (range, text) in edits {
11711 buffer.edit([(range, text)], None, cx);
11712 }
11713 });
11714 this.fold_creases(refold_creases, true, window, cx);
11715 this.change_selections(Default::default(), window, cx, |s| {
11716 s.select(new_selections);
11717 })
11718 });
11719 }
11720
11721 pub fn move_line_down(
11722 &mut self,
11723 _: &MoveLineDown,
11724 window: &mut Window,
11725 cx: &mut Context<Self>,
11726 ) {
11727 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11728 if self.mode.is_single_line() {
11729 cx.propagate();
11730 return;
11731 }
11732
11733 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11734 let buffer = self.buffer.read(cx).snapshot(cx);
11735
11736 let mut edits = Vec::new();
11737 let mut unfold_ranges = Vec::new();
11738 let mut refold_creases = Vec::new();
11739
11740 let selections = self.selections.all::<Point>(cx);
11741 let mut selections = selections.iter().peekable();
11742 let mut contiguous_row_selections = Vec::new();
11743 let mut new_selections = Vec::new();
11744
11745 while let Some(selection) = selections.next() {
11746 // Find all the selections that span a contiguous row range
11747 let (start_row, end_row) = consume_contiguous_rows(
11748 &mut contiguous_row_selections,
11749 selection,
11750 &display_map,
11751 &mut selections,
11752 );
11753
11754 // Move the text spanned by the row range to be after the last line of the row range
11755 if end_row.0 <= buffer.max_point().row {
11756 let range_to_move =
11757 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11758 let insertion_point = display_map
11759 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11760 .0;
11761
11762 // Don't move lines across excerpt boundaries
11763 if buffer
11764 .excerpt_containing(range_to_move.start..insertion_point)
11765 .is_some()
11766 {
11767 let mut text = String::from("\n");
11768 text.extend(buffer.text_for_range(range_to_move.clone()));
11769 text.pop(); // Drop trailing newline
11770 edits.push((
11771 buffer.anchor_after(range_to_move.start)
11772 ..buffer.anchor_before(range_to_move.end),
11773 String::new(),
11774 ));
11775 let insertion_anchor = buffer.anchor_after(insertion_point);
11776 edits.push((insertion_anchor..insertion_anchor, text));
11777
11778 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11779
11780 // Move selections down
11781 new_selections.extend(contiguous_row_selections.drain(..).map(
11782 |mut selection| {
11783 selection.start.row += row_delta;
11784 selection.end.row += row_delta;
11785 selection
11786 },
11787 ));
11788
11789 // Move folds down
11790 unfold_ranges.push(range_to_move.clone());
11791 for fold in display_map.folds_in_range(
11792 buffer.anchor_before(range_to_move.start)
11793 ..buffer.anchor_after(range_to_move.end),
11794 ) {
11795 let mut start = fold.range.start.to_point(&buffer);
11796 let mut end = fold.range.end.to_point(&buffer);
11797 start.row += row_delta;
11798 end.row += row_delta;
11799 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11800 }
11801 }
11802 }
11803
11804 // If we didn't move line(s), preserve the existing selections
11805 new_selections.append(&mut contiguous_row_selections);
11806 }
11807
11808 self.transact(window, cx, |this, window, cx| {
11809 this.unfold_ranges(&unfold_ranges, true, true, cx);
11810 this.buffer.update(cx, |buffer, cx| {
11811 for (range, text) in edits {
11812 buffer.edit([(range, text)], None, cx);
11813 }
11814 });
11815 this.fold_creases(refold_creases, true, window, cx);
11816 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11817 });
11818 }
11819
11820 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11821 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11822 let text_layout_details = &self.text_layout_details(window);
11823 self.transact(window, cx, |this, window, cx| {
11824 let edits = this.change_selections(Default::default(), window, cx, |s| {
11825 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11826 s.move_with(|display_map, selection| {
11827 if !selection.is_empty() {
11828 return;
11829 }
11830
11831 let mut head = selection.head();
11832 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11833 if head.column() == display_map.line_len(head.row()) {
11834 transpose_offset = display_map
11835 .buffer_snapshot
11836 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11837 }
11838
11839 if transpose_offset == 0 {
11840 return;
11841 }
11842
11843 *head.column_mut() += 1;
11844 head = display_map.clip_point(head, Bias::Right);
11845 let goal = SelectionGoal::HorizontalPosition(
11846 display_map
11847 .x_for_display_point(head, text_layout_details)
11848 .into(),
11849 );
11850 selection.collapse_to(head, goal);
11851
11852 let transpose_start = display_map
11853 .buffer_snapshot
11854 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11855 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11856 let transpose_end = display_map
11857 .buffer_snapshot
11858 .clip_offset(transpose_offset + 1, Bias::Right);
11859 if let Some(ch) =
11860 display_map.buffer_snapshot.chars_at(transpose_start).next()
11861 {
11862 edits.push((transpose_start..transpose_offset, String::new()));
11863 edits.push((transpose_end..transpose_end, ch.to_string()));
11864 }
11865 }
11866 });
11867 edits
11868 });
11869 this.buffer
11870 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11871 let selections = this.selections.all::<usize>(cx);
11872 this.change_selections(Default::default(), window, cx, |s| {
11873 s.select(selections);
11874 });
11875 });
11876 }
11877
11878 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11879 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11880 if self.mode.is_single_line() {
11881 cx.propagate();
11882 return;
11883 }
11884
11885 self.rewrap_impl(RewrapOptions::default(), cx)
11886 }
11887
11888 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11889 let buffer = self.buffer.read(cx).snapshot(cx);
11890 let selections = self.selections.all::<Point>(cx);
11891
11892 #[derive(Clone, Debug, PartialEq)]
11893 enum CommentFormat {
11894 /// single line comment, with prefix for line
11895 Line(String),
11896 /// single line within a block comment, with prefix for line
11897 BlockLine(String),
11898 /// a single line of a block comment that includes the initial delimiter
11899 BlockCommentWithStart(BlockCommentConfig),
11900 /// a single line of a block comment that includes the ending delimiter
11901 BlockCommentWithEnd(BlockCommentConfig),
11902 }
11903
11904 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11905 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11906 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11907 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11908 .peekable();
11909
11910 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11911 row
11912 } else {
11913 return Vec::new();
11914 };
11915
11916 let language_settings = buffer.language_settings_at(selection.head(), cx);
11917 let language_scope = buffer.language_scope_at(selection.head());
11918
11919 let indent_and_prefix_for_row =
11920 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11921 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11922 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11923 &language_scope
11924 {
11925 let indent_end = Point::new(row, indent.len);
11926 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11927 let line_text_after_indent = buffer
11928 .text_for_range(indent_end..line_end)
11929 .collect::<String>();
11930
11931 let is_within_comment_override = buffer
11932 .language_scope_at(indent_end)
11933 .is_some_and(|scope| scope.override_name() == Some("comment"));
11934 let comment_delimiters = if is_within_comment_override {
11935 // we are within a comment syntax node, but we don't
11936 // yet know what kind of comment: block, doc or line
11937 match (
11938 language_scope.documentation_comment(),
11939 language_scope.block_comment(),
11940 ) {
11941 (Some(config), _) | (_, Some(config))
11942 if buffer.contains_str_at(indent_end, &config.start) =>
11943 {
11944 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11945 }
11946 (Some(config), _) | (_, Some(config))
11947 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11948 {
11949 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11950 }
11951 (Some(config), _) | (_, Some(config))
11952 if buffer.contains_str_at(indent_end, &config.prefix) =>
11953 {
11954 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11955 }
11956 (_, _) => language_scope
11957 .line_comment_prefixes()
11958 .iter()
11959 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11960 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11961 }
11962 } else {
11963 // we not in an overridden comment node, but we may
11964 // be within a non-overridden line comment node
11965 language_scope
11966 .line_comment_prefixes()
11967 .iter()
11968 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11969 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11970 };
11971
11972 let rewrap_prefix = language_scope
11973 .rewrap_prefixes()
11974 .iter()
11975 .find_map(|prefix_regex| {
11976 prefix_regex.find(&line_text_after_indent).map(|mat| {
11977 if mat.start() == 0 {
11978 Some(mat.as_str().to_string())
11979 } else {
11980 None
11981 }
11982 })
11983 })
11984 .flatten();
11985 (comment_delimiters, rewrap_prefix)
11986 } else {
11987 (None, None)
11988 };
11989 (indent, comment_prefix, rewrap_prefix)
11990 };
11991
11992 let mut ranges = Vec::new();
11993 let from_empty_selection = selection.is_empty();
11994
11995 let mut current_range_start = first_row;
11996 let mut prev_row = first_row;
11997 let (
11998 mut current_range_indent,
11999 mut current_range_comment_delimiters,
12000 mut current_range_rewrap_prefix,
12001 ) = indent_and_prefix_for_row(first_row);
12002
12003 for row in non_blank_rows_iter.skip(1) {
12004 let has_paragraph_break = row > prev_row + 1;
12005
12006 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12007 indent_and_prefix_for_row(row);
12008
12009 let has_indent_change = row_indent != current_range_indent;
12010 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12011
12012 let has_boundary_change = has_comment_change
12013 || row_rewrap_prefix.is_some()
12014 || (has_indent_change && current_range_comment_delimiters.is_some());
12015
12016 if has_paragraph_break || has_boundary_change {
12017 ranges.push((
12018 language_settings.clone(),
12019 Point::new(current_range_start, 0)
12020 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12021 current_range_indent,
12022 current_range_comment_delimiters.clone(),
12023 current_range_rewrap_prefix.clone(),
12024 from_empty_selection,
12025 ));
12026 current_range_start = row;
12027 current_range_indent = row_indent;
12028 current_range_comment_delimiters = row_comment_delimiters;
12029 current_range_rewrap_prefix = row_rewrap_prefix;
12030 }
12031 prev_row = row;
12032 }
12033
12034 ranges.push((
12035 language_settings.clone(),
12036 Point::new(current_range_start, 0)
12037 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12038 current_range_indent,
12039 current_range_comment_delimiters,
12040 current_range_rewrap_prefix,
12041 from_empty_selection,
12042 ));
12043
12044 ranges
12045 });
12046
12047 let mut edits = Vec::new();
12048 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12049
12050 for (
12051 language_settings,
12052 wrap_range,
12053 mut indent_size,
12054 comment_prefix,
12055 rewrap_prefix,
12056 from_empty_selection,
12057 ) in wrap_ranges
12058 {
12059 let mut start_row = wrap_range.start.row;
12060 let mut end_row = wrap_range.end.row;
12061
12062 // Skip selections that overlap with a range that has already been rewrapped.
12063 let selection_range = start_row..end_row;
12064 if rewrapped_row_ranges
12065 .iter()
12066 .any(|range| range.overlaps(&selection_range))
12067 {
12068 continue;
12069 }
12070
12071 let tab_size = language_settings.tab_size;
12072
12073 let (line_prefix, inside_comment) = match &comment_prefix {
12074 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12075 (Some(prefix.as_str()), true)
12076 }
12077 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12078 (Some(prefix.as_ref()), true)
12079 }
12080 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12081 start: _,
12082 end: _,
12083 prefix,
12084 tab_size,
12085 })) => {
12086 indent_size.len += tab_size;
12087 (Some(prefix.as_ref()), true)
12088 }
12089 None => (None, false),
12090 };
12091 let indent_prefix = indent_size.chars().collect::<String>();
12092 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12093
12094 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12095 RewrapBehavior::InComments => inside_comment,
12096 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12097 RewrapBehavior::Anywhere => true,
12098 };
12099
12100 let should_rewrap = options.override_language_settings
12101 || allow_rewrap_based_on_language
12102 || self.hard_wrap.is_some();
12103 if !should_rewrap {
12104 continue;
12105 }
12106
12107 if from_empty_selection {
12108 'expand_upwards: while start_row > 0 {
12109 let prev_row = start_row - 1;
12110 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12111 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12112 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12113 {
12114 start_row = prev_row;
12115 } else {
12116 break 'expand_upwards;
12117 }
12118 }
12119
12120 'expand_downwards: while end_row < buffer.max_point().row {
12121 let next_row = end_row + 1;
12122 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12123 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12124 && !buffer.is_line_blank(MultiBufferRow(next_row))
12125 {
12126 end_row = next_row;
12127 } else {
12128 break 'expand_downwards;
12129 }
12130 }
12131 }
12132
12133 let start = Point::new(start_row, 0);
12134 let start_offset = start.to_offset(&buffer);
12135 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12136 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12137 let mut first_line_delimiter = None;
12138 let mut last_line_delimiter = None;
12139 let Some(lines_without_prefixes) = selection_text
12140 .lines()
12141 .enumerate()
12142 .map(|(ix, line)| {
12143 let line_trimmed = line.trim_start();
12144 if rewrap_prefix.is_some() && ix > 0 {
12145 Ok(line_trimmed)
12146 } else if let Some(
12147 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12148 start,
12149 prefix,
12150 end,
12151 tab_size,
12152 })
12153 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12154 start,
12155 prefix,
12156 end,
12157 tab_size,
12158 }),
12159 ) = &comment_prefix
12160 {
12161 let line_trimmed = line_trimmed
12162 .strip_prefix(start.as_ref())
12163 .map(|s| {
12164 let mut indent_size = indent_size;
12165 indent_size.len -= tab_size;
12166 let indent_prefix: String = indent_size.chars().collect();
12167 first_line_delimiter = Some((indent_prefix, start));
12168 s.trim_start()
12169 })
12170 .unwrap_or(line_trimmed);
12171 let line_trimmed = line_trimmed
12172 .strip_suffix(end.as_ref())
12173 .map(|s| {
12174 last_line_delimiter = Some(end);
12175 s.trim_end()
12176 })
12177 .unwrap_or(line_trimmed);
12178 let line_trimmed = line_trimmed
12179 .strip_prefix(prefix.as_ref())
12180 .unwrap_or(line_trimmed);
12181 Ok(line_trimmed)
12182 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12183 line_trimmed.strip_prefix(prefix).with_context(|| {
12184 format!("line did not start with prefix {prefix:?}: {line:?}")
12185 })
12186 } else {
12187 line_trimmed
12188 .strip_prefix(&line_prefix.trim_start())
12189 .with_context(|| {
12190 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12191 })
12192 }
12193 })
12194 .collect::<Result<Vec<_>, _>>()
12195 .log_err()
12196 else {
12197 continue;
12198 };
12199
12200 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12201 buffer
12202 .language_settings_at(Point::new(start_row, 0), cx)
12203 .preferred_line_length as usize
12204 });
12205
12206 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12207 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12208 } else {
12209 line_prefix.clone()
12210 };
12211
12212 let wrapped_text = {
12213 let mut wrapped_text = wrap_with_prefix(
12214 line_prefix,
12215 subsequent_lines_prefix,
12216 lines_without_prefixes.join("\n"),
12217 wrap_column,
12218 tab_size,
12219 options.preserve_existing_whitespace,
12220 );
12221
12222 if let Some((indent, delimiter)) = first_line_delimiter {
12223 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12224 }
12225 if let Some(last_line) = last_line_delimiter {
12226 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12227 }
12228
12229 wrapped_text
12230 };
12231
12232 // TODO: should always use char-based diff while still supporting cursor behavior that
12233 // matches vim.
12234 let mut diff_options = DiffOptions::default();
12235 if options.override_language_settings {
12236 diff_options.max_word_diff_len = 0;
12237 diff_options.max_word_diff_line_count = 0;
12238 } else {
12239 diff_options.max_word_diff_len = usize::MAX;
12240 diff_options.max_word_diff_line_count = usize::MAX;
12241 }
12242
12243 for (old_range, new_text) in
12244 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12245 {
12246 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12247 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12248 edits.push((edit_start..edit_end, new_text));
12249 }
12250
12251 rewrapped_row_ranges.push(start_row..=end_row);
12252 }
12253
12254 self.buffer
12255 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12256 }
12257
12258 pub fn cut_common(
12259 &mut self,
12260 cut_no_selection_line: bool,
12261 window: &mut Window,
12262 cx: &mut Context<Self>,
12263 ) -> ClipboardItem {
12264 let mut text = String::new();
12265 let buffer = self.buffer.read(cx).snapshot(cx);
12266 let mut selections = self.selections.all::<Point>(cx);
12267 let mut clipboard_selections = Vec::with_capacity(selections.len());
12268 {
12269 let max_point = buffer.max_point();
12270 let mut is_first = true;
12271 for selection in &mut selections {
12272 let is_entire_line =
12273 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode;
12274 if is_entire_line {
12275 selection.start = Point::new(selection.start.row, 0);
12276 if !selection.is_empty() && selection.end.column == 0 {
12277 selection.end = cmp::min(max_point, selection.end);
12278 } else {
12279 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12280 }
12281 selection.goal = SelectionGoal::None;
12282 }
12283 if is_first {
12284 is_first = false;
12285 } else {
12286 text += "\n";
12287 }
12288 let mut len = 0;
12289 for chunk in buffer.text_for_range(selection.start..selection.end) {
12290 text.push_str(chunk);
12291 len += chunk.len();
12292 }
12293 clipboard_selections.push(ClipboardSelection {
12294 len,
12295 is_entire_line,
12296 first_line_indent: buffer
12297 .indent_size_for_line(MultiBufferRow(selection.start.row))
12298 .len,
12299 });
12300 }
12301 }
12302
12303 self.transact(window, cx, |this, window, cx| {
12304 this.change_selections(Default::default(), window, cx, |s| {
12305 s.select(selections);
12306 });
12307 this.insert("", window, cx);
12308 });
12309 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12310 }
12311
12312 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12313 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12314 let item = self.cut_common(true, window, cx);
12315 cx.write_to_clipboard(item);
12316 }
12317
12318 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12320 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12321 s.move_with(|snapshot, sel| {
12322 if sel.is_empty() {
12323 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12324 }
12325 if sel.is_empty() {
12326 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12327 }
12328 });
12329 });
12330 let item = self.cut_common(true, window, cx);
12331 cx.set_global(KillRing(item))
12332 }
12333
12334 pub fn kill_ring_yank(
12335 &mut self,
12336 _: &KillRingYank,
12337 window: &mut Window,
12338 cx: &mut Context<Self>,
12339 ) {
12340 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12341 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12342 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12343 (kill_ring.text().to_string(), kill_ring.metadata_json())
12344 } else {
12345 return;
12346 }
12347 } else {
12348 return;
12349 };
12350 self.do_paste(&text, metadata, false, window, cx);
12351 }
12352
12353 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12354 self.do_copy(true, cx);
12355 }
12356
12357 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12358 self.do_copy(false, cx);
12359 }
12360
12361 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12362 let selections = self.selections.all::<Point>(cx);
12363 let buffer = self.buffer.read(cx).read(cx);
12364 let mut text = String::new();
12365
12366 let mut clipboard_selections = Vec::with_capacity(selections.len());
12367 {
12368 let max_point = buffer.max_point();
12369 let mut is_first = true;
12370 for selection in &selections {
12371 let mut start = selection.start;
12372 let mut end = selection.end;
12373 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12374 if is_entire_line {
12375 start = Point::new(start.row, 0);
12376 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12377 }
12378
12379 let mut trimmed_selections = Vec::new();
12380 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12381 let row = MultiBufferRow(start.row);
12382 let first_indent = buffer.indent_size_for_line(row);
12383 if first_indent.len == 0 || start.column > first_indent.len {
12384 trimmed_selections.push(start..end);
12385 } else {
12386 trimmed_selections.push(
12387 Point::new(row.0, first_indent.len)
12388 ..Point::new(row.0, buffer.line_len(row)),
12389 );
12390 for row in start.row + 1..=end.row {
12391 let mut line_len = buffer.line_len(MultiBufferRow(row));
12392 if row == end.row {
12393 line_len = end.column;
12394 }
12395 if line_len == 0 {
12396 trimmed_selections
12397 .push(Point::new(row, 0)..Point::new(row, line_len));
12398 continue;
12399 }
12400 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12401 if row_indent_size.len >= first_indent.len {
12402 trimmed_selections.push(
12403 Point::new(row, first_indent.len)..Point::new(row, line_len),
12404 );
12405 } else {
12406 trimmed_selections.clear();
12407 trimmed_selections.push(start..end);
12408 break;
12409 }
12410 }
12411 }
12412 } else {
12413 trimmed_selections.push(start..end);
12414 }
12415
12416 for trimmed_range in trimmed_selections {
12417 if is_first {
12418 is_first = false;
12419 } else {
12420 text += "\n";
12421 }
12422 let mut len = 0;
12423 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12424 text.push_str(chunk);
12425 len += chunk.len();
12426 }
12427 clipboard_selections.push(ClipboardSelection {
12428 len,
12429 is_entire_line,
12430 first_line_indent: buffer
12431 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12432 .len,
12433 });
12434 }
12435 }
12436 }
12437
12438 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12439 text,
12440 clipboard_selections,
12441 ));
12442 }
12443
12444 pub fn do_paste(
12445 &mut self,
12446 text: &String,
12447 clipboard_selections: Option<Vec<ClipboardSelection>>,
12448 handle_entire_lines: bool,
12449 window: &mut Window,
12450 cx: &mut Context<Self>,
12451 ) {
12452 if self.read_only(cx) {
12453 return;
12454 }
12455
12456 let clipboard_text = Cow::Borrowed(text);
12457
12458 self.transact(window, cx, |this, window, cx| {
12459 let had_active_edit_prediction = this.has_active_edit_prediction();
12460
12461 if let Some(mut clipboard_selections) = clipboard_selections {
12462 let old_selections = this.selections.all::<usize>(cx);
12463 let all_selections_were_entire_line =
12464 clipboard_selections.iter().all(|s| s.is_entire_line);
12465 let first_selection_indent_column =
12466 clipboard_selections.first().map(|s| s.first_line_indent);
12467 if clipboard_selections.len() != old_selections.len() {
12468 clipboard_selections.drain(..);
12469 }
12470 let cursor_offset = this.selections.last::<usize>(cx).head();
12471 let mut auto_indent_on_paste = true;
12472
12473 this.buffer.update(cx, |buffer, cx| {
12474 let snapshot = buffer.read(cx);
12475 auto_indent_on_paste = snapshot
12476 .language_settings_at(cursor_offset, cx)
12477 .auto_indent_on_paste;
12478
12479 let mut start_offset = 0;
12480 let mut edits = Vec::new();
12481 let mut original_indent_columns = Vec::new();
12482 for (ix, selection) in old_selections.iter().enumerate() {
12483 let to_insert;
12484 let entire_line;
12485 let original_indent_column;
12486 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12487 let end_offset = start_offset + clipboard_selection.len;
12488 to_insert = &clipboard_text[start_offset..end_offset];
12489 entire_line = clipboard_selection.is_entire_line;
12490 start_offset = end_offset + 1;
12491 original_indent_column = Some(clipboard_selection.first_line_indent);
12492 } else {
12493 to_insert = clipboard_text.as_str();
12494 entire_line = all_selections_were_entire_line;
12495 original_indent_column = first_selection_indent_column
12496 }
12497
12498 // If the corresponding selection was empty when this slice of the
12499 // clipboard text was written, then the entire line containing the
12500 // selection was copied. If this selection is also currently empty,
12501 // then paste the line before the current line of the buffer.
12502 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12503 let column = selection.start.to_point(&snapshot).column as usize;
12504 let line_start = selection.start - column;
12505 line_start..line_start
12506 } else {
12507 selection.range()
12508 };
12509
12510 edits.push((range, to_insert));
12511 original_indent_columns.push(original_indent_column);
12512 }
12513 drop(snapshot);
12514
12515 buffer.edit(
12516 edits,
12517 if auto_indent_on_paste {
12518 Some(AutoindentMode::Block {
12519 original_indent_columns,
12520 })
12521 } else {
12522 None
12523 },
12524 cx,
12525 );
12526 });
12527
12528 let selections = this.selections.all::<usize>(cx);
12529 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12530 } else {
12531 this.insert(&clipboard_text, window, cx);
12532 }
12533
12534 let trigger_in_words =
12535 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12536
12537 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12538 });
12539 }
12540
12541 pub fn diff_clipboard_with_selection(
12542 &mut self,
12543 _: &DiffClipboardWithSelection,
12544 window: &mut Window,
12545 cx: &mut Context<Self>,
12546 ) {
12547 let selections = self.selections.all::<usize>(cx);
12548
12549 if selections.is_empty() {
12550 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12551 return;
12552 };
12553
12554 let clipboard_text = match cx.read_from_clipboard() {
12555 Some(item) => match item.entries().first() {
12556 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12557 _ => None,
12558 },
12559 None => None,
12560 };
12561
12562 let Some(clipboard_text) = clipboard_text else {
12563 log::warn!("Clipboard doesn't contain text.");
12564 return;
12565 };
12566
12567 window.dispatch_action(
12568 Box::new(DiffClipboardWithSelectionData {
12569 clipboard_text,
12570 editor: cx.entity(),
12571 }),
12572 cx,
12573 );
12574 }
12575
12576 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12577 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12578 if let Some(item) = cx.read_from_clipboard() {
12579 let entries = item.entries();
12580
12581 match entries.first() {
12582 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12583 // of all the pasted entries.
12584 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12585 .do_paste(
12586 clipboard_string.text(),
12587 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12588 true,
12589 window,
12590 cx,
12591 ),
12592 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12593 }
12594 }
12595 }
12596
12597 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12598 if self.read_only(cx) {
12599 return;
12600 }
12601
12602 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12603
12604 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12605 if let Some((selections, _)) =
12606 self.selection_history.transaction(transaction_id).cloned()
12607 {
12608 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12609 s.select_anchors(selections.to_vec());
12610 });
12611 } else {
12612 log::error!(
12613 "No entry in selection_history found for undo. \
12614 This may correspond to a bug where undo does not update the selection. \
12615 If this is occurring, please add details to \
12616 https://github.com/zed-industries/zed/issues/22692"
12617 );
12618 }
12619 self.request_autoscroll(Autoscroll::fit(), cx);
12620 self.unmark_text(window, cx);
12621 self.refresh_edit_prediction(true, false, window, cx);
12622 cx.emit(EditorEvent::Edited { transaction_id });
12623 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12624 }
12625 }
12626
12627 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12628 if self.read_only(cx) {
12629 return;
12630 }
12631
12632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12633
12634 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12635 if let Some((_, Some(selections))) =
12636 self.selection_history.transaction(transaction_id).cloned()
12637 {
12638 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12639 s.select_anchors(selections.to_vec());
12640 });
12641 } else {
12642 log::error!(
12643 "No entry in selection_history found for redo. \
12644 This may correspond to a bug where undo does not update the selection. \
12645 If this is occurring, please add details to \
12646 https://github.com/zed-industries/zed/issues/22692"
12647 );
12648 }
12649 self.request_autoscroll(Autoscroll::fit(), cx);
12650 self.unmark_text(window, cx);
12651 self.refresh_edit_prediction(true, false, window, cx);
12652 cx.emit(EditorEvent::Edited { transaction_id });
12653 }
12654 }
12655
12656 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12657 self.buffer
12658 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12659 }
12660
12661 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12662 self.buffer
12663 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12664 }
12665
12666 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12667 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12668 self.change_selections(Default::default(), window, cx, |s| {
12669 s.move_with(|map, selection| {
12670 let cursor = if selection.is_empty() {
12671 movement::left(map, selection.start)
12672 } else {
12673 selection.start
12674 };
12675 selection.collapse_to(cursor, SelectionGoal::None);
12676 });
12677 })
12678 }
12679
12680 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12681 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12682 self.change_selections(Default::default(), window, cx, |s| {
12683 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12684 })
12685 }
12686
12687 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12688 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12689 self.change_selections(Default::default(), window, cx, |s| {
12690 s.move_with(|map, selection| {
12691 let cursor = if selection.is_empty() {
12692 movement::right(map, selection.end)
12693 } else {
12694 selection.end
12695 };
12696 selection.collapse_to(cursor, SelectionGoal::None)
12697 });
12698 })
12699 }
12700
12701 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12703 self.change_selections(Default::default(), window, cx, |s| {
12704 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12705 })
12706 }
12707
12708 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12709 if self.take_rename(true, window, cx).is_some() {
12710 return;
12711 }
12712
12713 if self.mode.is_single_line() {
12714 cx.propagate();
12715 return;
12716 }
12717
12718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12719
12720 let text_layout_details = &self.text_layout_details(window);
12721 let selection_count = self.selections.count();
12722 let first_selection = self.selections.first_anchor();
12723
12724 self.change_selections(Default::default(), window, cx, |s| {
12725 s.move_with(|map, selection| {
12726 if !selection.is_empty() {
12727 selection.goal = SelectionGoal::None;
12728 }
12729 let (cursor, goal) = movement::up(
12730 map,
12731 selection.start,
12732 selection.goal,
12733 false,
12734 text_layout_details,
12735 );
12736 selection.collapse_to(cursor, goal);
12737 });
12738 });
12739
12740 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12741 {
12742 cx.propagate();
12743 }
12744 }
12745
12746 pub fn move_up_by_lines(
12747 &mut self,
12748 action: &MoveUpByLines,
12749 window: &mut Window,
12750 cx: &mut Context<Self>,
12751 ) {
12752 if self.take_rename(true, window, cx).is_some() {
12753 return;
12754 }
12755
12756 if self.mode.is_single_line() {
12757 cx.propagate();
12758 return;
12759 }
12760
12761 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12762
12763 let text_layout_details = &self.text_layout_details(window);
12764
12765 self.change_selections(Default::default(), window, cx, |s| {
12766 s.move_with(|map, selection| {
12767 if !selection.is_empty() {
12768 selection.goal = SelectionGoal::None;
12769 }
12770 let (cursor, goal) = movement::up_by_rows(
12771 map,
12772 selection.start,
12773 action.lines,
12774 selection.goal,
12775 false,
12776 text_layout_details,
12777 );
12778 selection.collapse_to(cursor, goal);
12779 });
12780 })
12781 }
12782
12783 pub fn move_down_by_lines(
12784 &mut self,
12785 action: &MoveDownByLines,
12786 window: &mut Window,
12787 cx: &mut Context<Self>,
12788 ) {
12789 if self.take_rename(true, window, cx).is_some() {
12790 return;
12791 }
12792
12793 if self.mode.is_single_line() {
12794 cx.propagate();
12795 return;
12796 }
12797
12798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12799
12800 let text_layout_details = &self.text_layout_details(window);
12801
12802 self.change_selections(Default::default(), window, cx, |s| {
12803 s.move_with(|map, selection| {
12804 if !selection.is_empty() {
12805 selection.goal = SelectionGoal::None;
12806 }
12807 let (cursor, goal) = movement::down_by_rows(
12808 map,
12809 selection.start,
12810 action.lines,
12811 selection.goal,
12812 false,
12813 text_layout_details,
12814 );
12815 selection.collapse_to(cursor, goal);
12816 });
12817 })
12818 }
12819
12820 pub fn select_down_by_lines(
12821 &mut self,
12822 action: &SelectDownByLines,
12823 window: &mut Window,
12824 cx: &mut Context<Self>,
12825 ) {
12826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12827 let text_layout_details = &self.text_layout_details(window);
12828 self.change_selections(Default::default(), window, cx, |s| {
12829 s.move_heads_with(|map, head, goal| {
12830 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12831 })
12832 })
12833 }
12834
12835 pub fn select_up_by_lines(
12836 &mut self,
12837 action: &SelectUpByLines,
12838 window: &mut Window,
12839 cx: &mut Context<Self>,
12840 ) {
12841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12842 let text_layout_details = &self.text_layout_details(window);
12843 self.change_selections(Default::default(), window, cx, |s| {
12844 s.move_heads_with(|map, head, goal| {
12845 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12846 })
12847 })
12848 }
12849
12850 pub fn select_page_up(
12851 &mut self,
12852 _: &SelectPageUp,
12853 window: &mut Window,
12854 cx: &mut Context<Self>,
12855 ) {
12856 let Some(row_count) = self.visible_row_count() else {
12857 return;
12858 };
12859
12860 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12861
12862 let text_layout_details = &self.text_layout_details(window);
12863
12864 self.change_selections(Default::default(), window, cx, |s| {
12865 s.move_heads_with(|map, head, goal| {
12866 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12867 })
12868 })
12869 }
12870
12871 pub fn move_page_up(
12872 &mut self,
12873 action: &MovePageUp,
12874 window: &mut Window,
12875 cx: &mut Context<Self>,
12876 ) {
12877 if self.take_rename(true, window, cx).is_some() {
12878 return;
12879 }
12880
12881 if self
12882 .context_menu
12883 .borrow_mut()
12884 .as_mut()
12885 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12886 .unwrap_or(false)
12887 {
12888 return;
12889 }
12890
12891 if matches!(self.mode, EditorMode::SingleLine) {
12892 cx.propagate();
12893 return;
12894 }
12895
12896 let Some(row_count) = self.visible_row_count() else {
12897 return;
12898 };
12899
12900 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12901
12902 let effects = if action.center_cursor {
12903 SelectionEffects::scroll(Autoscroll::center())
12904 } else {
12905 SelectionEffects::default()
12906 };
12907
12908 let text_layout_details = &self.text_layout_details(window);
12909
12910 self.change_selections(effects, window, cx, |s| {
12911 s.move_with(|map, selection| {
12912 if !selection.is_empty() {
12913 selection.goal = SelectionGoal::None;
12914 }
12915 let (cursor, goal) = movement::up_by_rows(
12916 map,
12917 selection.end,
12918 row_count,
12919 selection.goal,
12920 false,
12921 text_layout_details,
12922 );
12923 selection.collapse_to(cursor, goal);
12924 });
12925 });
12926 }
12927
12928 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12930 let text_layout_details = &self.text_layout_details(window);
12931 self.change_selections(Default::default(), window, cx, |s| {
12932 s.move_heads_with(|map, head, goal| {
12933 movement::up(map, head, goal, false, text_layout_details)
12934 })
12935 })
12936 }
12937
12938 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12939 self.take_rename(true, window, cx);
12940
12941 if self.mode.is_single_line() {
12942 cx.propagate();
12943 return;
12944 }
12945
12946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12947
12948 let text_layout_details = &self.text_layout_details(window);
12949 let selection_count = self.selections.count();
12950 let first_selection = self.selections.first_anchor();
12951
12952 self.change_selections(Default::default(), window, cx, |s| {
12953 s.move_with(|map, selection| {
12954 if !selection.is_empty() {
12955 selection.goal = SelectionGoal::None;
12956 }
12957 let (cursor, goal) = movement::down(
12958 map,
12959 selection.end,
12960 selection.goal,
12961 false,
12962 text_layout_details,
12963 );
12964 selection.collapse_to(cursor, goal);
12965 });
12966 });
12967
12968 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12969 {
12970 cx.propagate();
12971 }
12972 }
12973
12974 pub fn select_page_down(
12975 &mut self,
12976 _: &SelectPageDown,
12977 window: &mut Window,
12978 cx: &mut Context<Self>,
12979 ) {
12980 let Some(row_count) = self.visible_row_count() else {
12981 return;
12982 };
12983
12984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12985
12986 let text_layout_details = &self.text_layout_details(window);
12987
12988 self.change_selections(Default::default(), window, cx, |s| {
12989 s.move_heads_with(|map, head, goal| {
12990 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12991 })
12992 })
12993 }
12994
12995 pub fn move_page_down(
12996 &mut self,
12997 action: &MovePageDown,
12998 window: &mut Window,
12999 cx: &mut Context<Self>,
13000 ) {
13001 if self.take_rename(true, window, cx).is_some() {
13002 return;
13003 }
13004
13005 if self
13006 .context_menu
13007 .borrow_mut()
13008 .as_mut()
13009 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13010 .unwrap_or(false)
13011 {
13012 return;
13013 }
13014
13015 if matches!(self.mode, EditorMode::SingleLine) {
13016 cx.propagate();
13017 return;
13018 }
13019
13020 let Some(row_count) = self.visible_row_count() else {
13021 return;
13022 };
13023
13024 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13025
13026 let effects = if action.center_cursor {
13027 SelectionEffects::scroll(Autoscroll::center())
13028 } else {
13029 SelectionEffects::default()
13030 };
13031
13032 let text_layout_details = &self.text_layout_details(window);
13033 self.change_selections(effects, window, cx, |s| {
13034 s.move_with(|map, selection| {
13035 if !selection.is_empty() {
13036 selection.goal = SelectionGoal::None;
13037 }
13038 let (cursor, goal) = movement::down_by_rows(
13039 map,
13040 selection.end,
13041 row_count,
13042 selection.goal,
13043 false,
13044 text_layout_details,
13045 );
13046 selection.collapse_to(cursor, goal);
13047 });
13048 });
13049 }
13050
13051 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13052 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13053 let text_layout_details = &self.text_layout_details(window);
13054 self.change_selections(Default::default(), window, cx, |s| {
13055 s.move_heads_with(|map, head, goal| {
13056 movement::down(map, head, goal, false, text_layout_details)
13057 })
13058 });
13059 }
13060
13061 pub fn context_menu_first(
13062 &mut self,
13063 _: &ContextMenuFirst,
13064 window: &mut Window,
13065 cx: &mut Context<Self>,
13066 ) {
13067 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13068 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13069 }
13070 }
13071
13072 pub fn context_menu_prev(
13073 &mut self,
13074 _: &ContextMenuPrevious,
13075 window: &mut Window,
13076 cx: &mut Context<Self>,
13077 ) {
13078 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13079 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13080 }
13081 }
13082
13083 pub fn context_menu_next(
13084 &mut self,
13085 _: &ContextMenuNext,
13086 window: &mut Window,
13087 cx: &mut Context<Self>,
13088 ) {
13089 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13090 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13091 }
13092 }
13093
13094 pub fn context_menu_last(
13095 &mut self,
13096 _: &ContextMenuLast,
13097 window: &mut Window,
13098 cx: &mut Context<Self>,
13099 ) {
13100 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13101 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13102 }
13103 }
13104
13105 pub fn signature_help_prev(
13106 &mut self,
13107 _: &SignatureHelpPrevious,
13108 _: &mut Window,
13109 cx: &mut Context<Self>,
13110 ) {
13111 if let Some(popover) = self.signature_help_state.popover_mut() {
13112 if popover.current_signature == 0 {
13113 popover.current_signature = popover.signatures.len() - 1;
13114 } else {
13115 popover.current_signature -= 1;
13116 }
13117 cx.notify();
13118 }
13119 }
13120
13121 pub fn signature_help_next(
13122 &mut self,
13123 _: &SignatureHelpNext,
13124 _: &mut Window,
13125 cx: &mut Context<Self>,
13126 ) {
13127 if let Some(popover) = self.signature_help_state.popover_mut() {
13128 if popover.current_signature + 1 == popover.signatures.len() {
13129 popover.current_signature = 0;
13130 } else {
13131 popover.current_signature += 1;
13132 }
13133 cx.notify();
13134 }
13135 }
13136
13137 pub fn move_to_previous_word_start(
13138 &mut self,
13139 _: &MoveToPreviousWordStart,
13140 window: &mut Window,
13141 cx: &mut Context<Self>,
13142 ) {
13143 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13144 self.change_selections(Default::default(), window, cx, |s| {
13145 s.move_cursors_with(|map, head, _| {
13146 (
13147 movement::previous_word_start(map, head),
13148 SelectionGoal::None,
13149 )
13150 });
13151 })
13152 }
13153
13154 pub fn move_to_previous_subword_start(
13155 &mut self,
13156 _: &MoveToPreviousSubwordStart,
13157 window: &mut Window,
13158 cx: &mut Context<Self>,
13159 ) {
13160 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13161 self.change_selections(Default::default(), window, cx, |s| {
13162 s.move_cursors_with(|map, head, _| {
13163 (
13164 movement::previous_subword_start(map, head),
13165 SelectionGoal::None,
13166 )
13167 });
13168 })
13169 }
13170
13171 pub fn select_to_previous_word_start(
13172 &mut self,
13173 _: &SelectToPreviousWordStart,
13174 window: &mut Window,
13175 cx: &mut Context<Self>,
13176 ) {
13177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13178 self.change_selections(Default::default(), window, cx, |s| {
13179 s.move_heads_with(|map, head, _| {
13180 (
13181 movement::previous_word_start(map, head),
13182 SelectionGoal::None,
13183 )
13184 });
13185 })
13186 }
13187
13188 pub fn select_to_previous_subword_start(
13189 &mut self,
13190 _: &SelectToPreviousSubwordStart,
13191 window: &mut Window,
13192 cx: &mut Context<Self>,
13193 ) {
13194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13195 self.change_selections(Default::default(), window, cx, |s| {
13196 s.move_heads_with(|map, head, _| {
13197 (
13198 movement::previous_subword_start(map, head),
13199 SelectionGoal::None,
13200 )
13201 });
13202 })
13203 }
13204
13205 pub fn delete_to_previous_word_start(
13206 &mut self,
13207 action: &DeleteToPreviousWordStart,
13208 window: &mut Window,
13209 cx: &mut Context<Self>,
13210 ) {
13211 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13212 self.transact(window, cx, |this, window, cx| {
13213 this.select_autoclose_pair(window, cx);
13214 this.change_selections(Default::default(), window, cx, |s| {
13215 s.move_with(|map, selection| {
13216 if selection.is_empty() {
13217 let mut cursor = if action.ignore_newlines {
13218 movement::previous_word_start(map, selection.head())
13219 } else {
13220 movement::previous_word_start_or_newline(map, selection.head())
13221 };
13222 cursor = movement::adjust_greedy_deletion(
13223 map,
13224 selection.head(),
13225 cursor,
13226 action.ignore_brackets,
13227 );
13228 selection.set_head(cursor, SelectionGoal::None);
13229 }
13230 });
13231 });
13232 this.insert("", window, cx);
13233 });
13234 }
13235
13236 pub fn delete_to_previous_subword_start(
13237 &mut self,
13238 _: &DeleteToPreviousSubwordStart,
13239 window: &mut Window,
13240 cx: &mut Context<Self>,
13241 ) {
13242 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13243 self.transact(window, cx, |this, window, cx| {
13244 this.select_autoclose_pair(window, cx);
13245 this.change_selections(Default::default(), window, cx, |s| {
13246 s.move_with(|map, selection| {
13247 if selection.is_empty() {
13248 let mut cursor = movement::previous_subword_start(map, selection.head());
13249 cursor =
13250 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13251 selection.set_head(cursor, SelectionGoal::None);
13252 }
13253 });
13254 });
13255 this.insert("", window, cx);
13256 });
13257 }
13258
13259 pub fn move_to_next_word_end(
13260 &mut self,
13261 _: &MoveToNextWordEnd,
13262 window: &mut Window,
13263 cx: &mut Context<Self>,
13264 ) {
13265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13266 self.change_selections(Default::default(), window, cx, |s| {
13267 s.move_cursors_with(|map, head, _| {
13268 (movement::next_word_end(map, head), SelectionGoal::None)
13269 });
13270 })
13271 }
13272
13273 pub fn move_to_next_subword_end(
13274 &mut self,
13275 _: &MoveToNextSubwordEnd,
13276 window: &mut Window,
13277 cx: &mut Context<Self>,
13278 ) {
13279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13280 self.change_selections(Default::default(), window, cx, |s| {
13281 s.move_cursors_with(|map, head, _| {
13282 (movement::next_subword_end(map, head), SelectionGoal::None)
13283 });
13284 })
13285 }
13286
13287 pub fn select_to_next_word_end(
13288 &mut self,
13289 _: &SelectToNextWordEnd,
13290 window: &mut Window,
13291 cx: &mut Context<Self>,
13292 ) {
13293 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13294 self.change_selections(Default::default(), window, cx, |s| {
13295 s.move_heads_with(|map, head, _| {
13296 (movement::next_word_end(map, head), SelectionGoal::None)
13297 });
13298 })
13299 }
13300
13301 pub fn select_to_next_subword_end(
13302 &mut self,
13303 _: &SelectToNextSubwordEnd,
13304 window: &mut Window,
13305 cx: &mut Context<Self>,
13306 ) {
13307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13308 self.change_selections(Default::default(), window, cx, |s| {
13309 s.move_heads_with(|map, head, _| {
13310 (movement::next_subword_end(map, head), SelectionGoal::None)
13311 });
13312 })
13313 }
13314
13315 pub fn delete_to_next_word_end(
13316 &mut self,
13317 action: &DeleteToNextWordEnd,
13318 window: &mut Window,
13319 cx: &mut Context<Self>,
13320 ) {
13321 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13322 self.transact(window, cx, |this, window, cx| {
13323 this.change_selections(Default::default(), window, cx, |s| {
13324 s.move_with(|map, selection| {
13325 if selection.is_empty() {
13326 let mut cursor = if action.ignore_newlines {
13327 movement::next_word_end(map, selection.head())
13328 } else {
13329 movement::next_word_end_or_newline(map, selection.head())
13330 };
13331 cursor = movement::adjust_greedy_deletion(
13332 map,
13333 selection.head(),
13334 cursor,
13335 action.ignore_brackets,
13336 );
13337 selection.set_head(cursor, SelectionGoal::None);
13338 }
13339 });
13340 });
13341 this.insert("", window, cx);
13342 });
13343 }
13344
13345 pub fn delete_to_next_subword_end(
13346 &mut self,
13347 _: &DeleteToNextSubwordEnd,
13348 window: &mut Window,
13349 cx: &mut Context<Self>,
13350 ) {
13351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13352 self.transact(window, cx, |this, window, cx| {
13353 this.change_selections(Default::default(), window, cx, |s| {
13354 s.move_with(|map, selection| {
13355 if selection.is_empty() {
13356 let mut cursor = movement::next_subword_end(map, selection.head());
13357 cursor =
13358 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13359 selection.set_head(cursor, SelectionGoal::None);
13360 }
13361 });
13362 });
13363 this.insert("", window, cx);
13364 });
13365 }
13366
13367 pub fn move_to_beginning_of_line(
13368 &mut self,
13369 action: &MoveToBeginningOfLine,
13370 window: &mut Window,
13371 cx: &mut Context<Self>,
13372 ) {
13373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13374 self.change_selections(Default::default(), window, cx, |s| {
13375 s.move_cursors_with(|map, head, _| {
13376 (
13377 movement::indented_line_beginning(
13378 map,
13379 head,
13380 action.stop_at_soft_wraps,
13381 action.stop_at_indent,
13382 ),
13383 SelectionGoal::None,
13384 )
13385 });
13386 })
13387 }
13388
13389 pub fn select_to_beginning_of_line(
13390 &mut self,
13391 action: &SelectToBeginningOfLine,
13392 window: &mut Window,
13393 cx: &mut Context<Self>,
13394 ) {
13395 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13396 self.change_selections(Default::default(), window, cx, |s| {
13397 s.move_heads_with(|map, head, _| {
13398 (
13399 movement::indented_line_beginning(
13400 map,
13401 head,
13402 action.stop_at_soft_wraps,
13403 action.stop_at_indent,
13404 ),
13405 SelectionGoal::None,
13406 )
13407 });
13408 });
13409 }
13410
13411 pub fn delete_to_beginning_of_line(
13412 &mut self,
13413 action: &DeleteToBeginningOfLine,
13414 window: &mut Window,
13415 cx: &mut Context<Self>,
13416 ) {
13417 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13418 self.transact(window, cx, |this, window, cx| {
13419 this.change_selections(Default::default(), window, cx, |s| {
13420 s.move_with(|_, selection| {
13421 selection.reversed = true;
13422 });
13423 });
13424
13425 this.select_to_beginning_of_line(
13426 &SelectToBeginningOfLine {
13427 stop_at_soft_wraps: false,
13428 stop_at_indent: action.stop_at_indent,
13429 },
13430 window,
13431 cx,
13432 );
13433 this.backspace(&Backspace, window, cx);
13434 });
13435 }
13436
13437 pub fn move_to_end_of_line(
13438 &mut self,
13439 action: &MoveToEndOfLine,
13440 window: &mut Window,
13441 cx: &mut Context<Self>,
13442 ) {
13443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13444 self.change_selections(Default::default(), window, cx, |s| {
13445 s.move_cursors_with(|map, head, _| {
13446 (
13447 movement::line_end(map, head, action.stop_at_soft_wraps),
13448 SelectionGoal::None,
13449 )
13450 });
13451 })
13452 }
13453
13454 pub fn select_to_end_of_line(
13455 &mut self,
13456 action: &SelectToEndOfLine,
13457 window: &mut Window,
13458 cx: &mut Context<Self>,
13459 ) {
13460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13461 self.change_selections(Default::default(), window, cx, |s| {
13462 s.move_heads_with(|map, head, _| {
13463 (
13464 movement::line_end(map, head, action.stop_at_soft_wraps),
13465 SelectionGoal::None,
13466 )
13467 });
13468 })
13469 }
13470
13471 pub fn delete_to_end_of_line(
13472 &mut self,
13473 _: &DeleteToEndOfLine,
13474 window: &mut Window,
13475 cx: &mut Context<Self>,
13476 ) {
13477 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13478 self.transact(window, cx, |this, window, cx| {
13479 this.select_to_end_of_line(
13480 &SelectToEndOfLine {
13481 stop_at_soft_wraps: false,
13482 },
13483 window,
13484 cx,
13485 );
13486 this.delete(&Delete, window, cx);
13487 });
13488 }
13489
13490 pub fn cut_to_end_of_line(
13491 &mut self,
13492 action: &CutToEndOfLine,
13493 window: &mut Window,
13494 cx: &mut Context<Self>,
13495 ) {
13496 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13497 self.transact(window, cx, |this, window, cx| {
13498 this.select_to_end_of_line(
13499 &SelectToEndOfLine {
13500 stop_at_soft_wraps: false,
13501 },
13502 window,
13503 cx,
13504 );
13505 if !action.stop_at_newlines {
13506 this.change_selections(Default::default(), window, cx, |s| {
13507 s.move_with(|_, sel| {
13508 if sel.is_empty() {
13509 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13510 }
13511 });
13512 });
13513 }
13514 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13515 let item = this.cut_common(false, window, cx);
13516 cx.write_to_clipboard(item);
13517 });
13518 }
13519
13520 pub fn move_to_start_of_paragraph(
13521 &mut self,
13522 _: &MoveToStartOfParagraph,
13523 window: &mut Window,
13524 cx: &mut Context<Self>,
13525 ) {
13526 if matches!(self.mode, EditorMode::SingleLine) {
13527 cx.propagate();
13528 return;
13529 }
13530 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13531 self.change_selections(Default::default(), window, cx, |s| {
13532 s.move_with(|map, selection| {
13533 selection.collapse_to(
13534 movement::start_of_paragraph(map, selection.head(), 1),
13535 SelectionGoal::None,
13536 )
13537 });
13538 })
13539 }
13540
13541 pub fn move_to_end_of_paragraph(
13542 &mut self,
13543 _: &MoveToEndOfParagraph,
13544 window: &mut Window,
13545 cx: &mut Context<Self>,
13546 ) {
13547 if matches!(self.mode, EditorMode::SingleLine) {
13548 cx.propagate();
13549 return;
13550 }
13551 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13552 self.change_selections(Default::default(), window, cx, |s| {
13553 s.move_with(|map, selection| {
13554 selection.collapse_to(
13555 movement::end_of_paragraph(map, selection.head(), 1),
13556 SelectionGoal::None,
13557 )
13558 });
13559 })
13560 }
13561
13562 pub fn select_to_start_of_paragraph(
13563 &mut self,
13564 _: &SelectToStartOfParagraph,
13565 window: &mut Window,
13566 cx: &mut Context<Self>,
13567 ) {
13568 if matches!(self.mode, EditorMode::SingleLine) {
13569 cx.propagate();
13570 return;
13571 }
13572 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13573 self.change_selections(Default::default(), window, cx, |s| {
13574 s.move_heads_with(|map, head, _| {
13575 (
13576 movement::start_of_paragraph(map, head, 1),
13577 SelectionGoal::None,
13578 )
13579 });
13580 })
13581 }
13582
13583 pub fn select_to_end_of_paragraph(
13584 &mut self,
13585 _: &SelectToEndOfParagraph,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 if matches!(self.mode, EditorMode::SingleLine) {
13590 cx.propagate();
13591 return;
13592 }
13593 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13594 self.change_selections(Default::default(), window, cx, |s| {
13595 s.move_heads_with(|map, head, _| {
13596 (
13597 movement::end_of_paragraph(map, head, 1),
13598 SelectionGoal::None,
13599 )
13600 });
13601 })
13602 }
13603
13604 pub fn move_to_start_of_excerpt(
13605 &mut self,
13606 _: &MoveToStartOfExcerpt,
13607 window: &mut Window,
13608 cx: &mut Context<Self>,
13609 ) {
13610 if matches!(self.mode, EditorMode::SingleLine) {
13611 cx.propagate();
13612 return;
13613 }
13614 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13615 self.change_selections(Default::default(), window, cx, |s| {
13616 s.move_with(|map, selection| {
13617 selection.collapse_to(
13618 movement::start_of_excerpt(
13619 map,
13620 selection.head(),
13621 workspace::searchable::Direction::Prev,
13622 ),
13623 SelectionGoal::None,
13624 )
13625 });
13626 })
13627 }
13628
13629 pub fn move_to_start_of_next_excerpt(
13630 &mut self,
13631 _: &MoveToStartOfNextExcerpt,
13632 window: &mut Window,
13633 cx: &mut Context<Self>,
13634 ) {
13635 if matches!(self.mode, EditorMode::SingleLine) {
13636 cx.propagate();
13637 return;
13638 }
13639
13640 self.change_selections(Default::default(), window, cx, |s| {
13641 s.move_with(|map, selection| {
13642 selection.collapse_to(
13643 movement::start_of_excerpt(
13644 map,
13645 selection.head(),
13646 workspace::searchable::Direction::Next,
13647 ),
13648 SelectionGoal::None,
13649 )
13650 });
13651 })
13652 }
13653
13654 pub fn move_to_end_of_excerpt(
13655 &mut self,
13656 _: &MoveToEndOfExcerpt,
13657 window: &mut Window,
13658 cx: &mut Context<Self>,
13659 ) {
13660 if matches!(self.mode, EditorMode::SingleLine) {
13661 cx.propagate();
13662 return;
13663 }
13664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13665 self.change_selections(Default::default(), window, cx, |s| {
13666 s.move_with(|map, selection| {
13667 selection.collapse_to(
13668 movement::end_of_excerpt(
13669 map,
13670 selection.head(),
13671 workspace::searchable::Direction::Next,
13672 ),
13673 SelectionGoal::None,
13674 )
13675 });
13676 })
13677 }
13678
13679 pub fn move_to_end_of_previous_excerpt(
13680 &mut self,
13681 _: &MoveToEndOfPreviousExcerpt,
13682 window: &mut Window,
13683 cx: &mut Context<Self>,
13684 ) {
13685 if matches!(self.mode, EditorMode::SingleLine) {
13686 cx.propagate();
13687 return;
13688 }
13689 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13690 self.change_selections(Default::default(), window, cx, |s| {
13691 s.move_with(|map, selection| {
13692 selection.collapse_to(
13693 movement::end_of_excerpt(
13694 map,
13695 selection.head(),
13696 workspace::searchable::Direction::Prev,
13697 ),
13698 SelectionGoal::None,
13699 )
13700 });
13701 })
13702 }
13703
13704 pub fn select_to_start_of_excerpt(
13705 &mut self,
13706 _: &SelectToStartOfExcerpt,
13707 window: &mut Window,
13708 cx: &mut Context<Self>,
13709 ) {
13710 if matches!(self.mode, EditorMode::SingleLine) {
13711 cx.propagate();
13712 return;
13713 }
13714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13715 self.change_selections(Default::default(), window, cx, |s| {
13716 s.move_heads_with(|map, head, _| {
13717 (
13718 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13719 SelectionGoal::None,
13720 )
13721 });
13722 })
13723 }
13724
13725 pub fn select_to_start_of_next_excerpt(
13726 &mut self,
13727 _: &SelectToStartOfNextExcerpt,
13728 window: &mut Window,
13729 cx: &mut Context<Self>,
13730 ) {
13731 if matches!(self.mode, EditorMode::SingleLine) {
13732 cx.propagate();
13733 return;
13734 }
13735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13736 self.change_selections(Default::default(), window, cx, |s| {
13737 s.move_heads_with(|map, head, _| {
13738 (
13739 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13740 SelectionGoal::None,
13741 )
13742 });
13743 })
13744 }
13745
13746 pub fn select_to_end_of_excerpt(
13747 &mut self,
13748 _: &SelectToEndOfExcerpt,
13749 window: &mut Window,
13750 cx: &mut Context<Self>,
13751 ) {
13752 if matches!(self.mode, EditorMode::SingleLine) {
13753 cx.propagate();
13754 return;
13755 }
13756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13757 self.change_selections(Default::default(), window, cx, |s| {
13758 s.move_heads_with(|map, head, _| {
13759 (
13760 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13761 SelectionGoal::None,
13762 )
13763 });
13764 })
13765 }
13766
13767 pub fn select_to_end_of_previous_excerpt(
13768 &mut self,
13769 _: &SelectToEndOfPreviousExcerpt,
13770 window: &mut Window,
13771 cx: &mut Context<Self>,
13772 ) {
13773 if matches!(self.mode, EditorMode::SingleLine) {
13774 cx.propagate();
13775 return;
13776 }
13777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13778 self.change_selections(Default::default(), window, cx, |s| {
13779 s.move_heads_with(|map, head, _| {
13780 (
13781 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13782 SelectionGoal::None,
13783 )
13784 });
13785 })
13786 }
13787
13788 pub fn move_to_beginning(
13789 &mut self,
13790 _: &MoveToBeginning,
13791 window: &mut Window,
13792 cx: &mut Context<Self>,
13793 ) {
13794 if matches!(self.mode, EditorMode::SingleLine) {
13795 cx.propagate();
13796 return;
13797 }
13798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13799 self.change_selections(Default::default(), window, cx, |s| {
13800 s.select_ranges(vec![0..0]);
13801 });
13802 }
13803
13804 pub fn select_to_beginning(
13805 &mut self,
13806 _: &SelectToBeginning,
13807 window: &mut Window,
13808 cx: &mut Context<Self>,
13809 ) {
13810 let mut selection = self.selections.last::<Point>(cx);
13811 selection.set_head(Point::zero(), SelectionGoal::None);
13812 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13813 self.change_selections(Default::default(), window, cx, |s| {
13814 s.select(vec![selection]);
13815 });
13816 }
13817
13818 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13819 if matches!(self.mode, EditorMode::SingleLine) {
13820 cx.propagate();
13821 return;
13822 }
13823 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13824 let cursor = self.buffer.read(cx).read(cx).len();
13825 self.change_selections(Default::default(), window, cx, |s| {
13826 s.select_ranges(vec![cursor..cursor])
13827 });
13828 }
13829
13830 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13831 self.nav_history = nav_history;
13832 }
13833
13834 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13835 self.nav_history.as_ref()
13836 }
13837
13838 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13839 self.push_to_nav_history(
13840 self.selections.newest_anchor().head(),
13841 None,
13842 false,
13843 true,
13844 cx,
13845 );
13846 }
13847
13848 fn push_to_nav_history(
13849 &mut self,
13850 cursor_anchor: Anchor,
13851 new_position: Option<Point>,
13852 is_deactivate: bool,
13853 always: bool,
13854 cx: &mut Context<Self>,
13855 ) {
13856 if let Some(nav_history) = self.nav_history.as_mut() {
13857 let buffer = self.buffer.read(cx).read(cx);
13858 let cursor_position = cursor_anchor.to_point(&buffer);
13859 let scroll_state = self.scroll_manager.anchor();
13860 let scroll_top_row = scroll_state.top_row(&buffer);
13861 drop(buffer);
13862
13863 if let Some(new_position) = new_position {
13864 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13865 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13866 return;
13867 }
13868 }
13869
13870 nav_history.push(
13871 Some(NavigationData {
13872 cursor_anchor,
13873 cursor_position,
13874 scroll_anchor: scroll_state,
13875 scroll_top_row,
13876 }),
13877 cx,
13878 );
13879 cx.emit(EditorEvent::PushedToNavHistory {
13880 anchor: cursor_anchor,
13881 is_deactivate,
13882 })
13883 }
13884 }
13885
13886 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13888 let buffer = self.buffer.read(cx).snapshot(cx);
13889 let mut selection = self.selections.first::<usize>(cx);
13890 selection.set_head(buffer.len(), SelectionGoal::None);
13891 self.change_selections(Default::default(), window, cx, |s| {
13892 s.select(vec![selection]);
13893 });
13894 }
13895
13896 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13898 let end = self.buffer.read(cx).read(cx).len();
13899 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13900 s.select_ranges(vec![0..end]);
13901 });
13902 }
13903
13904 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13905 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13906 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13907 let mut selections = self.selections.all::<Point>(cx);
13908 let max_point = display_map.buffer_snapshot.max_point();
13909 for selection in &mut selections {
13910 let rows = selection.spanned_rows(true, &display_map);
13911 selection.start = Point::new(rows.start.0, 0);
13912 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13913 selection.reversed = false;
13914 }
13915 self.change_selections(Default::default(), window, cx, |s| {
13916 s.select(selections);
13917 });
13918 }
13919
13920 pub fn split_selection_into_lines(
13921 &mut self,
13922 action: &SplitSelectionIntoLines,
13923 window: &mut Window,
13924 cx: &mut Context<Self>,
13925 ) {
13926 let selections = self
13927 .selections
13928 .all::<Point>(cx)
13929 .into_iter()
13930 .map(|selection| selection.start..selection.end)
13931 .collect::<Vec<_>>();
13932 self.unfold_ranges(&selections, true, true, cx);
13933
13934 let mut new_selection_ranges = Vec::new();
13935 {
13936 let buffer = self.buffer.read(cx).read(cx);
13937 for selection in selections {
13938 for row in selection.start.row..selection.end.row {
13939 let line_start = Point::new(row, 0);
13940 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13941
13942 if action.keep_selections {
13943 // Keep the selection range for each line
13944 let selection_start = if row == selection.start.row {
13945 selection.start
13946 } else {
13947 line_start
13948 };
13949 new_selection_ranges.push(selection_start..line_end);
13950 } else {
13951 // Collapse to cursor at end of line
13952 new_selection_ranges.push(line_end..line_end);
13953 }
13954 }
13955
13956 let is_multiline_selection = selection.start.row != selection.end.row;
13957 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13958 // so this action feels more ergonomic when paired with other selection operations
13959 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13960 if !should_skip_last {
13961 if action.keep_selections {
13962 if is_multiline_selection {
13963 let line_start = Point::new(selection.end.row, 0);
13964 new_selection_ranges.push(line_start..selection.end);
13965 } else {
13966 new_selection_ranges.push(selection.start..selection.end);
13967 }
13968 } else {
13969 new_selection_ranges.push(selection.end..selection.end);
13970 }
13971 }
13972 }
13973 }
13974 self.change_selections(Default::default(), window, cx, |s| {
13975 s.select_ranges(new_selection_ranges);
13976 });
13977 }
13978
13979 pub fn add_selection_above(
13980 &mut self,
13981 _: &AddSelectionAbove,
13982 window: &mut Window,
13983 cx: &mut Context<Self>,
13984 ) {
13985 self.add_selection(true, window, cx);
13986 }
13987
13988 pub fn add_selection_below(
13989 &mut self,
13990 _: &AddSelectionBelow,
13991 window: &mut Window,
13992 cx: &mut Context<Self>,
13993 ) {
13994 self.add_selection(false, window, cx);
13995 }
13996
13997 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13998 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13999
14000 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14001 let all_selections = self.selections.all::<Point>(cx);
14002 let text_layout_details = self.text_layout_details(window);
14003
14004 let (mut columnar_selections, new_selections_to_columnarize) = {
14005 if let Some(state) = self.add_selections_state.as_ref() {
14006 let columnar_selection_ids: HashSet<_> = state
14007 .groups
14008 .iter()
14009 .flat_map(|group| group.stack.iter())
14010 .copied()
14011 .collect();
14012
14013 all_selections
14014 .into_iter()
14015 .partition(|s| columnar_selection_ids.contains(&s.id))
14016 } else {
14017 (Vec::new(), all_selections)
14018 }
14019 };
14020
14021 let mut state = self
14022 .add_selections_state
14023 .take()
14024 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14025
14026 for selection in new_selections_to_columnarize {
14027 let range = selection.display_range(&display_map).sorted();
14028 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14029 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14030 let positions = start_x.min(end_x)..start_x.max(end_x);
14031 let mut stack = Vec::new();
14032 for row in range.start.row().0..=range.end.row().0 {
14033 if let Some(selection) = self.selections.build_columnar_selection(
14034 &display_map,
14035 DisplayRow(row),
14036 &positions,
14037 selection.reversed,
14038 &text_layout_details,
14039 ) {
14040 stack.push(selection.id);
14041 columnar_selections.push(selection);
14042 }
14043 }
14044 if !stack.is_empty() {
14045 if above {
14046 stack.reverse();
14047 }
14048 state.groups.push(AddSelectionsGroup { above, stack });
14049 }
14050 }
14051
14052 let mut final_selections = Vec::new();
14053 let end_row = if above {
14054 DisplayRow(0)
14055 } else {
14056 display_map.max_point().row()
14057 };
14058
14059 let mut last_added_item_per_group = HashMap::default();
14060 for group in state.groups.iter_mut() {
14061 if let Some(last_id) = group.stack.last() {
14062 last_added_item_per_group.insert(*last_id, group);
14063 }
14064 }
14065
14066 for selection in columnar_selections {
14067 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14068 if above == group.above {
14069 let range = selection.display_range(&display_map).sorted();
14070 debug_assert_eq!(range.start.row(), range.end.row());
14071 let mut row = range.start.row();
14072 let positions =
14073 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14074 px(start)..px(end)
14075 } else {
14076 let start_x =
14077 display_map.x_for_display_point(range.start, &text_layout_details);
14078 let end_x =
14079 display_map.x_for_display_point(range.end, &text_layout_details);
14080 start_x.min(end_x)..start_x.max(end_x)
14081 };
14082
14083 let mut maybe_new_selection = None;
14084 while row != end_row {
14085 if above {
14086 row.0 -= 1;
14087 } else {
14088 row.0 += 1;
14089 }
14090 if let Some(new_selection) = self.selections.build_columnar_selection(
14091 &display_map,
14092 row,
14093 &positions,
14094 selection.reversed,
14095 &text_layout_details,
14096 ) {
14097 maybe_new_selection = Some(new_selection);
14098 break;
14099 }
14100 }
14101
14102 if let Some(new_selection) = maybe_new_selection {
14103 group.stack.push(new_selection.id);
14104 if above {
14105 final_selections.push(new_selection);
14106 final_selections.push(selection);
14107 } else {
14108 final_selections.push(selection);
14109 final_selections.push(new_selection);
14110 }
14111 } else {
14112 final_selections.push(selection);
14113 }
14114 } else {
14115 group.stack.pop();
14116 }
14117 } else {
14118 final_selections.push(selection);
14119 }
14120 }
14121
14122 self.change_selections(Default::default(), window, cx, |s| {
14123 s.select(final_selections);
14124 });
14125
14126 let final_selection_ids: HashSet<_> = self
14127 .selections
14128 .all::<Point>(cx)
14129 .iter()
14130 .map(|s| s.id)
14131 .collect();
14132 state.groups.retain_mut(|group| {
14133 // selections might get merged above so we remove invalid items from stacks
14134 group.stack.retain(|id| final_selection_ids.contains(id));
14135
14136 // single selection in stack can be treated as initial state
14137 group.stack.len() > 1
14138 });
14139
14140 if !state.groups.is_empty() {
14141 self.add_selections_state = Some(state);
14142 }
14143 }
14144
14145 fn select_match_ranges(
14146 &mut self,
14147 range: Range<usize>,
14148 reversed: bool,
14149 replace_newest: bool,
14150 auto_scroll: Option<Autoscroll>,
14151 window: &mut Window,
14152 cx: &mut Context<Editor>,
14153 ) {
14154 self.unfold_ranges(
14155 std::slice::from_ref(&range),
14156 false,
14157 auto_scroll.is_some(),
14158 cx,
14159 );
14160 let effects = if let Some(scroll) = auto_scroll {
14161 SelectionEffects::scroll(scroll)
14162 } else {
14163 SelectionEffects::no_scroll()
14164 };
14165 self.change_selections(effects, window, cx, |s| {
14166 if replace_newest {
14167 s.delete(s.newest_anchor().id);
14168 }
14169 if reversed {
14170 s.insert_range(range.end..range.start);
14171 } else {
14172 s.insert_range(range);
14173 }
14174 });
14175 }
14176
14177 pub fn select_next_match_internal(
14178 &mut self,
14179 display_map: &DisplaySnapshot,
14180 replace_newest: bool,
14181 autoscroll: Option<Autoscroll>,
14182 window: &mut Window,
14183 cx: &mut Context<Self>,
14184 ) -> Result<()> {
14185 let buffer = &display_map.buffer_snapshot;
14186 let mut selections = self.selections.all::<usize>(cx);
14187 if let Some(mut select_next_state) = self.select_next_state.take() {
14188 let query = &select_next_state.query;
14189 if !select_next_state.done {
14190 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14191 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14192 let mut next_selected_range = None;
14193
14194 let bytes_after_last_selection =
14195 buffer.bytes_in_range(last_selection.end..buffer.len());
14196 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14197 let query_matches = query
14198 .stream_find_iter(bytes_after_last_selection)
14199 .map(|result| (last_selection.end, result))
14200 .chain(
14201 query
14202 .stream_find_iter(bytes_before_first_selection)
14203 .map(|result| (0, result)),
14204 );
14205
14206 for (start_offset, query_match) in query_matches {
14207 let query_match = query_match.unwrap(); // can only fail due to I/O
14208 let offset_range =
14209 start_offset + query_match.start()..start_offset + query_match.end();
14210
14211 if !select_next_state.wordwise
14212 || (!buffer.is_inside_word(offset_range.start, false)
14213 && !buffer.is_inside_word(offset_range.end, false))
14214 {
14215 // TODO: This is n^2, because we might check all the selections
14216 if !selections
14217 .iter()
14218 .any(|selection| selection.range().overlaps(&offset_range))
14219 {
14220 next_selected_range = Some(offset_range);
14221 break;
14222 }
14223 }
14224 }
14225
14226 if let Some(next_selected_range) = next_selected_range {
14227 self.select_match_ranges(
14228 next_selected_range,
14229 last_selection.reversed,
14230 replace_newest,
14231 autoscroll,
14232 window,
14233 cx,
14234 );
14235 } else {
14236 select_next_state.done = true;
14237 }
14238 }
14239
14240 self.select_next_state = Some(select_next_state);
14241 } else {
14242 let mut only_carets = true;
14243 let mut same_text_selected = true;
14244 let mut selected_text = None;
14245
14246 let mut selections_iter = selections.iter().peekable();
14247 while let Some(selection) = selections_iter.next() {
14248 if selection.start != selection.end {
14249 only_carets = false;
14250 }
14251
14252 if same_text_selected {
14253 if selected_text.is_none() {
14254 selected_text =
14255 Some(buffer.text_for_range(selection.range()).collect::<String>());
14256 }
14257
14258 if let Some(next_selection) = selections_iter.peek() {
14259 if next_selection.range().len() == selection.range().len() {
14260 let next_selected_text = buffer
14261 .text_for_range(next_selection.range())
14262 .collect::<String>();
14263 if Some(next_selected_text) != selected_text {
14264 same_text_selected = false;
14265 selected_text = None;
14266 }
14267 } else {
14268 same_text_selected = false;
14269 selected_text = None;
14270 }
14271 }
14272 }
14273 }
14274
14275 if only_carets {
14276 for selection in &mut selections {
14277 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14278 selection.start = word_range.start;
14279 selection.end = word_range.end;
14280 selection.goal = SelectionGoal::None;
14281 selection.reversed = false;
14282 self.select_match_ranges(
14283 selection.start..selection.end,
14284 selection.reversed,
14285 replace_newest,
14286 autoscroll,
14287 window,
14288 cx,
14289 );
14290 }
14291
14292 if selections.len() == 1 {
14293 let selection = selections
14294 .last()
14295 .expect("ensured that there's only one selection");
14296 let query = buffer
14297 .text_for_range(selection.start..selection.end)
14298 .collect::<String>();
14299 let is_empty = query.is_empty();
14300 let select_state = SelectNextState {
14301 query: AhoCorasick::new(&[query])?,
14302 wordwise: true,
14303 done: is_empty,
14304 };
14305 self.select_next_state = Some(select_state);
14306 } else {
14307 self.select_next_state = None;
14308 }
14309 } else if let Some(selected_text) = selected_text {
14310 self.select_next_state = Some(SelectNextState {
14311 query: AhoCorasick::new(&[selected_text])?,
14312 wordwise: false,
14313 done: false,
14314 });
14315 self.select_next_match_internal(
14316 display_map,
14317 replace_newest,
14318 autoscroll,
14319 window,
14320 cx,
14321 )?;
14322 }
14323 }
14324 Ok(())
14325 }
14326
14327 pub fn select_all_matches(
14328 &mut self,
14329 _action: &SelectAllMatches,
14330 window: &mut Window,
14331 cx: &mut Context<Self>,
14332 ) -> Result<()> {
14333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14334
14335 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14336
14337 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14338 let Some(select_next_state) = self.select_next_state.as_mut() else {
14339 return Ok(());
14340 };
14341 if select_next_state.done {
14342 return Ok(());
14343 }
14344
14345 let mut new_selections = Vec::new();
14346
14347 let reversed = self.selections.oldest::<usize>(cx).reversed;
14348 let buffer = &display_map.buffer_snapshot;
14349 let query_matches = select_next_state
14350 .query
14351 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14352
14353 for query_match in query_matches.into_iter() {
14354 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14355 let offset_range = if reversed {
14356 query_match.end()..query_match.start()
14357 } else {
14358 query_match.start()..query_match.end()
14359 };
14360
14361 if !select_next_state.wordwise
14362 || (!buffer.is_inside_word(offset_range.start, false)
14363 && !buffer.is_inside_word(offset_range.end, false))
14364 {
14365 new_selections.push(offset_range.start..offset_range.end);
14366 }
14367 }
14368
14369 select_next_state.done = true;
14370
14371 if new_selections.is_empty() {
14372 log::error!("bug: new_selections is empty in select_all_matches");
14373 return Ok(());
14374 }
14375
14376 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14377 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14378 selections.select_ranges(new_selections)
14379 });
14380
14381 Ok(())
14382 }
14383
14384 pub fn select_next(
14385 &mut self,
14386 action: &SelectNext,
14387 window: &mut Window,
14388 cx: &mut Context<Self>,
14389 ) -> Result<()> {
14390 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14391 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14392 self.select_next_match_internal(
14393 &display_map,
14394 action.replace_newest,
14395 Some(Autoscroll::newest()),
14396 window,
14397 cx,
14398 )?;
14399 Ok(())
14400 }
14401
14402 pub fn select_previous(
14403 &mut self,
14404 action: &SelectPrevious,
14405 window: &mut Window,
14406 cx: &mut Context<Self>,
14407 ) -> Result<()> {
14408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14409 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14410 let buffer = &display_map.buffer_snapshot;
14411 let mut selections = self.selections.all::<usize>(cx);
14412 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14413 let query = &select_prev_state.query;
14414 if !select_prev_state.done {
14415 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14416 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14417 let mut next_selected_range = None;
14418 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14419 let bytes_before_last_selection =
14420 buffer.reversed_bytes_in_range(0..last_selection.start);
14421 let bytes_after_first_selection =
14422 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14423 let query_matches = query
14424 .stream_find_iter(bytes_before_last_selection)
14425 .map(|result| (last_selection.start, result))
14426 .chain(
14427 query
14428 .stream_find_iter(bytes_after_first_selection)
14429 .map(|result| (buffer.len(), result)),
14430 );
14431 for (end_offset, query_match) in query_matches {
14432 let query_match = query_match.unwrap(); // can only fail due to I/O
14433 let offset_range =
14434 end_offset - query_match.end()..end_offset - query_match.start();
14435
14436 if !select_prev_state.wordwise
14437 || (!buffer.is_inside_word(offset_range.start, false)
14438 && !buffer.is_inside_word(offset_range.end, false))
14439 {
14440 next_selected_range = Some(offset_range);
14441 break;
14442 }
14443 }
14444
14445 if let Some(next_selected_range) = next_selected_range {
14446 self.select_match_ranges(
14447 next_selected_range,
14448 last_selection.reversed,
14449 action.replace_newest,
14450 Some(Autoscroll::newest()),
14451 window,
14452 cx,
14453 );
14454 } else {
14455 select_prev_state.done = true;
14456 }
14457 }
14458
14459 self.select_prev_state = Some(select_prev_state);
14460 } else {
14461 let mut only_carets = true;
14462 let mut same_text_selected = true;
14463 let mut selected_text = None;
14464
14465 let mut selections_iter = selections.iter().peekable();
14466 while let Some(selection) = selections_iter.next() {
14467 if selection.start != selection.end {
14468 only_carets = false;
14469 }
14470
14471 if same_text_selected {
14472 if selected_text.is_none() {
14473 selected_text =
14474 Some(buffer.text_for_range(selection.range()).collect::<String>());
14475 }
14476
14477 if let Some(next_selection) = selections_iter.peek() {
14478 if next_selection.range().len() == selection.range().len() {
14479 let next_selected_text = buffer
14480 .text_for_range(next_selection.range())
14481 .collect::<String>();
14482 if Some(next_selected_text) != selected_text {
14483 same_text_selected = false;
14484 selected_text = None;
14485 }
14486 } else {
14487 same_text_selected = false;
14488 selected_text = None;
14489 }
14490 }
14491 }
14492 }
14493
14494 if only_carets {
14495 for selection in &mut selections {
14496 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14497 selection.start = word_range.start;
14498 selection.end = word_range.end;
14499 selection.goal = SelectionGoal::None;
14500 selection.reversed = false;
14501 self.select_match_ranges(
14502 selection.start..selection.end,
14503 selection.reversed,
14504 action.replace_newest,
14505 Some(Autoscroll::newest()),
14506 window,
14507 cx,
14508 );
14509 }
14510 if selections.len() == 1 {
14511 let selection = selections
14512 .last()
14513 .expect("ensured that there's only one selection");
14514 let query = buffer
14515 .text_for_range(selection.start..selection.end)
14516 .collect::<String>();
14517 let is_empty = query.is_empty();
14518 let select_state = SelectNextState {
14519 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14520 wordwise: true,
14521 done: is_empty,
14522 };
14523 self.select_prev_state = Some(select_state);
14524 } else {
14525 self.select_prev_state = None;
14526 }
14527 } else if let Some(selected_text) = selected_text {
14528 self.select_prev_state = Some(SelectNextState {
14529 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14530 wordwise: false,
14531 done: false,
14532 });
14533 self.select_previous(action, window, cx)?;
14534 }
14535 }
14536 Ok(())
14537 }
14538
14539 pub fn find_next_match(
14540 &mut self,
14541 _: &FindNextMatch,
14542 window: &mut Window,
14543 cx: &mut Context<Self>,
14544 ) -> Result<()> {
14545 let selections = self.selections.disjoint_anchors();
14546 match selections.first() {
14547 Some(first) if selections.len() >= 2 => {
14548 self.change_selections(Default::default(), window, cx, |s| {
14549 s.select_ranges([first.range()]);
14550 });
14551 }
14552 _ => self.select_next(
14553 &SelectNext {
14554 replace_newest: true,
14555 },
14556 window,
14557 cx,
14558 )?,
14559 }
14560 Ok(())
14561 }
14562
14563 pub fn find_previous_match(
14564 &mut self,
14565 _: &FindPreviousMatch,
14566 window: &mut Window,
14567 cx: &mut Context<Self>,
14568 ) -> Result<()> {
14569 let selections = self.selections.disjoint_anchors();
14570 match selections.last() {
14571 Some(last) if selections.len() >= 2 => {
14572 self.change_selections(Default::default(), window, cx, |s| {
14573 s.select_ranges([last.range()]);
14574 });
14575 }
14576 _ => self.select_previous(
14577 &SelectPrevious {
14578 replace_newest: true,
14579 },
14580 window,
14581 cx,
14582 )?,
14583 }
14584 Ok(())
14585 }
14586
14587 pub fn toggle_comments(
14588 &mut self,
14589 action: &ToggleComments,
14590 window: &mut Window,
14591 cx: &mut Context<Self>,
14592 ) {
14593 if self.read_only(cx) {
14594 return;
14595 }
14596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14597 let text_layout_details = &self.text_layout_details(window);
14598 self.transact(window, cx, |this, window, cx| {
14599 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14600 let mut edits = Vec::new();
14601 let mut selection_edit_ranges = Vec::new();
14602 let mut last_toggled_row = None;
14603 let snapshot = this.buffer.read(cx).read(cx);
14604 let empty_str: Arc<str> = Arc::default();
14605 let mut suffixes_inserted = Vec::new();
14606 let ignore_indent = action.ignore_indent;
14607
14608 fn comment_prefix_range(
14609 snapshot: &MultiBufferSnapshot,
14610 row: MultiBufferRow,
14611 comment_prefix: &str,
14612 comment_prefix_whitespace: &str,
14613 ignore_indent: bool,
14614 ) -> Range<Point> {
14615 let indent_size = if ignore_indent {
14616 0
14617 } else {
14618 snapshot.indent_size_for_line(row).len
14619 };
14620
14621 let start = Point::new(row.0, indent_size);
14622
14623 let mut line_bytes = snapshot
14624 .bytes_in_range(start..snapshot.max_point())
14625 .flatten()
14626 .copied();
14627
14628 // If this line currently begins with the line comment prefix, then record
14629 // the range containing the prefix.
14630 if line_bytes
14631 .by_ref()
14632 .take(comment_prefix.len())
14633 .eq(comment_prefix.bytes())
14634 {
14635 // Include any whitespace that matches the comment prefix.
14636 let matching_whitespace_len = line_bytes
14637 .zip(comment_prefix_whitespace.bytes())
14638 .take_while(|(a, b)| a == b)
14639 .count() as u32;
14640 let end = Point::new(
14641 start.row,
14642 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14643 );
14644 start..end
14645 } else {
14646 start..start
14647 }
14648 }
14649
14650 fn comment_suffix_range(
14651 snapshot: &MultiBufferSnapshot,
14652 row: MultiBufferRow,
14653 comment_suffix: &str,
14654 comment_suffix_has_leading_space: bool,
14655 ) -> Range<Point> {
14656 let end = Point::new(row.0, snapshot.line_len(row));
14657 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14658
14659 let mut line_end_bytes = snapshot
14660 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14661 .flatten()
14662 .copied();
14663
14664 let leading_space_len = if suffix_start_column > 0
14665 && line_end_bytes.next() == Some(b' ')
14666 && comment_suffix_has_leading_space
14667 {
14668 1
14669 } else {
14670 0
14671 };
14672
14673 // If this line currently begins with the line comment prefix, then record
14674 // the range containing the prefix.
14675 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14676 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14677 start..end
14678 } else {
14679 end..end
14680 }
14681 }
14682
14683 // TODO: Handle selections that cross excerpts
14684 for selection in &mut selections {
14685 let start_column = snapshot
14686 .indent_size_for_line(MultiBufferRow(selection.start.row))
14687 .len;
14688 let language = if let Some(language) =
14689 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14690 {
14691 language
14692 } else {
14693 continue;
14694 };
14695
14696 selection_edit_ranges.clear();
14697
14698 // If multiple selections contain a given row, avoid processing that
14699 // row more than once.
14700 let mut start_row = MultiBufferRow(selection.start.row);
14701 if last_toggled_row == Some(start_row) {
14702 start_row = start_row.next_row();
14703 }
14704 let end_row =
14705 if selection.end.row > selection.start.row && selection.end.column == 0 {
14706 MultiBufferRow(selection.end.row - 1)
14707 } else {
14708 MultiBufferRow(selection.end.row)
14709 };
14710 last_toggled_row = Some(end_row);
14711
14712 if start_row > end_row {
14713 continue;
14714 }
14715
14716 // If the language has line comments, toggle those.
14717 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14718
14719 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14720 if ignore_indent {
14721 full_comment_prefixes = full_comment_prefixes
14722 .into_iter()
14723 .map(|s| Arc::from(s.trim_end()))
14724 .collect();
14725 }
14726
14727 if !full_comment_prefixes.is_empty() {
14728 let first_prefix = full_comment_prefixes
14729 .first()
14730 .expect("prefixes is non-empty");
14731 let prefix_trimmed_lengths = full_comment_prefixes
14732 .iter()
14733 .map(|p| p.trim_end_matches(' ').len())
14734 .collect::<SmallVec<[usize; 4]>>();
14735
14736 let mut all_selection_lines_are_comments = true;
14737
14738 for row in start_row.0..=end_row.0 {
14739 let row = MultiBufferRow(row);
14740 if start_row < end_row && snapshot.is_line_blank(row) {
14741 continue;
14742 }
14743
14744 let prefix_range = full_comment_prefixes
14745 .iter()
14746 .zip(prefix_trimmed_lengths.iter().copied())
14747 .map(|(prefix, trimmed_prefix_len)| {
14748 comment_prefix_range(
14749 snapshot.deref(),
14750 row,
14751 &prefix[..trimmed_prefix_len],
14752 &prefix[trimmed_prefix_len..],
14753 ignore_indent,
14754 )
14755 })
14756 .max_by_key(|range| range.end.column - range.start.column)
14757 .expect("prefixes is non-empty");
14758
14759 if prefix_range.is_empty() {
14760 all_selection_lines_are_comments = false;
14761 }
14762
14763 selection_edit_ranges.push(prefix_range);
14764 }
14765
14766 if all_selection_lines_are_comments {
14767 edits.extend(
14768 selection_edit_ranges
14769 .iter()
14770 .cloned()
14771 .map(|range| (range, empty_str.clone())),
14772 );
14773 } else {
14774 let min_column = selection_edit_ranges
14775 .iter()
14776 .map(|range| range.start.column)
14777 .min()
14778 .unwrap_or(0);
14779 edits.extend(selection_edit_ranges.iter().map(|range| {
14780 let position = Point::new(range.start.row, min_column);
14781 (position..position, first_prefix.clone())
14782 }));
14783 }
14784 } else if let Some(BlockCommentConfig {
14785 start: full_comment_prefix,
14786 end: comment_suffix,
14787 ..
14788 }) = language.block_comment()
14789 {
14790 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14791 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14792 let prefix_range = comment_prefix_range(
14793 snapshot.deref(),
14794 start_row,
14795 comment_prefix,
14796 comment_prefix_whitespace,
14797 ignore_indent,
14798 );
14799 let suffix_range = comment_suffix_range(
14800 snapshot.deref(),
14801 end_row,
14802 comment_suffix.trim_start_matches(' '),
14803 comment_suffix.starts_with(' '),
14804 );
14805
14806 if prefix_range.is_empty() || suffix_range.is_empty() {
14807 edits.push((
14808 prefix_range.start..prefix_range.start,
14809 full_comment_prefix.clone(),
14810 ));
14811 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14812 suffixes_inserted.push((end_row, comment_suffix.len()));
14813 } else {
14814 edits.push((prefix_range, empty_str.clone()));
14815 edits.push((suffix_range, empty_str.clone()));
14816 }
14817 } else {
14818 continue;
14819 }
14820 }
14821
14822 drop(snapshot);
14823 this.buffer.update(cx, |buffer, cx| {
14824 buffer.edit(edits, None, cx);
14825 });
14826
14827 // Adjust selections so that they end before any comment suffixes that
14828 // were inserted.
14829 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14830 let mut selections = this.selections.all::<Point>(cx);
14831 let snapshot = this.buffer.read(cx).read(cx);
14832 for selection in &mut selections {
14833 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14834 match row.cmp(&MultiBufferRow(selection.end.row)) {
14835 Ordering::Less => {
14836 suffixes_inserted.next();
14837 continue;
14838 }
14839 Ordering::Greater => break,
14840 Ordering::Equal => {
14841 if selection.end.column == snapshot.line_len(row) {
14842 if selection.is_empty() {
14843 selection.start.column -= suffix_len as u32;
14844 }
14845 selection.end.column -= suffix_len as u32;
14846 }
14847 break;
14848 }
14849 }
14850 }
14851 }
14852
14853 drop(snapshot);
14854 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14855
14856 let selections = this.selections.all::<Point>(cx);
14857 let selections_on_single_row = selections.windows(2).all(|selections| {
14858 selections[0].start.row == selections[1].start.row
14859 && selections[0].end.row == selections[1].end.row
14860 && selections[0].start.row == selections[0].end.row
14861 });
14862 let selections_selecting = selections
14863 .iter()
14864 .any(|selection| selection.start != selection.end);
14865 let advance_downwards = action.advance_downwards
14866 && selections_on_single_row
14867 && !selections_selecting
14868 && !matches!(this.mode, EditorMode::SingleLine);
14869
14870 if advance_downwards {
14871 let snapshot = this.buffer.read(cx).snapshot(cx);
14872
14873 this.change_selections(Default::default(), window, cx, |s| {
14874 s.move_cursors_with(|display_snapshot, display_point, _| {
14875 let mut point = display_point.to_point(display_snapshot);
14876 point.row += 1;
14877 point = snapshot.clip_point(point, Bias::Left);
14878 let display_point = point.to_display_point(display_snapshot);
14879 let goal = SelectionGoal::HorizontalPosition(
14880 display_snapshot
14881 .x_for_display_point(display_point, text_layout_details)
14882 .into(),
14883 );
14884 (display_point, goal)
14885 })
14886 });
14887 }
14888 });
14889 }
14890
14891 pub fn select_enclosing_symbol(
14892 &mut self,
14893 _: &SelectEnclosingSymbol,
14894 window: &mut Window,
14895 cx: &mut Context<Self>,
14896 ) {
14897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14898
14899 let buffer = self.buffer.read(cx).snapshot(cx);
14900 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14901
14902 fn update_selection(
14903 selection: &Selection<usize>,
14904 buffer_snap: &MultiBufferSnapshot,
14905 ) -> Option<Selection<usize>> {
14906 let cursor = selection.head();
14907 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14908 for symbol in symbols.iter().rev() {
14909 let start = symbol.range.start.to_offset(buffer_snap);
14910 let end = symbol.range.end.to_offset(buffer_snap);
14911 let new_range = start..end;
14912 if start < selection.start || end > selection.end {
14913 return Some(Selection {
14914 id: selection.id,
14915 start: new_range.start,
14916 end: new_range.end,
14917 goal: SelectionGoal::None,
14918 reversed: selection.reversed,
14919 });
14920 }
14921 }
14922 None
14923 }
14924
14925 let mut selected_larger_symbol = false;
14926 let new_selections = old_selections
14927 .iter()
14928 .map(|selection| match update_selection(selection, &buffer) {
14929 Some(new_selection) => {
14930 if new_selection.range() != selection.range() {
14931 selected_larger_symbol = true;
14932 }
14933 new_selection
14934 }
14935 None => selection.clone(),
14936 })
14937 .collect::<Vec<_>>();
14938
14939 if selected_larger_symbol {
14940 self.change_selections(Default::default(), window, cx, |s| {
14941 s.select(new_selections);
14942 });
14943 }
14944 }
14945
14946 pub fn select_larger_syntax_node(
14947 &mut self,
14948 _: &SelectLargerSyntaxNode,
14949 window: &mut Window,
14950 cx: &mut Context<Self>,
14951 ) {
14952 let Some(visible_row_count) = self.visible_row_count() else {
14953 return;
14954 };
14955 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14956 if old_selections.is_empty() {
14957 return;
14958 }
14959
14960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14961
14962 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14963 let buffer = self.buffer.read(cx).snapshot(cx);
14964
14965 let mut selected_larger_node = false;
14966 let mut new_selections = old_selections
14967 .iter()
14968 .map(|selection| {
14969 let old_range = selection.start..selection.end;
14970
14971 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14972 // manually select word at selection
14973 if ["string_content", "inline"].contains(&node.kind()) {
14974 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14975 // ignore if word is already selected
14976 if !word_range.is_empty() && old_range != word_range {
14977 let (last_word_range, _) =
14978 buffer.surrounding_word(old_range.end, false);
14979 // only select word if start and end point belongs to same word
14980 if word_range == last_word_range {
14981 selected_larger_node = true;
14982 return Selection {
14983 id: selection.id,
14984 start: word_range.start,
14985 end: word_range.end,
14986 goal: SelectionGoal::None,
14987 reversed: selection.reversed,
14988 };
14989 }
14990 }
14991 }
14992 }
14993
14994 let mut new_range = old_range.clone();
14995 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
14996 {
14997 new_range = match containing_range {
14998 MultiOrSingleBufferOffsetRange::Single(_) => break,
14999 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15000 };
15001 if !node.is_named() {
15002 continue;
15003 }
15004 if !display_map.intersects_fold(new_range.start)
15005 && !display_map.intersects_fold(new_range.end)
15006 {
15007 break;
15008 }
15009 }
15010
15011 selected_larger_node |= new_range != old_range;
15012 Selection {
15013 id: selection.id,
15014 start: new_range.start,
15015 end: new_range.end,
15016 goal: SelectionGoal::None,
15017 reversed: selection.reversed,
15018 }
15019 })
15020 .collect::<Vec<_>>();
15021
15022 if !selected_larger_node {
15023 return; // don't put this call in the history
15024 }
15025
15026 // scroll based on transformation done to the last selection created by the user
15027 let (last_old, last_new) = old_selections
15028 .last()
15029 .zip(new_selections.last().cloned())
15030 .expect("old_selections isn't empty");
15031
15032 // revert selection
15033 let is_selection_reversed = {
15034 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15035 new_selections.last_mut().expect("checked above").reversed =
15036 should_newest_selection_be_reversed;
15037 should_newest_selection_be_reversed
15038 };
15039
15040 if selected_larger_node {
15041 self.select_syntax_node_history.disable_clearing = true;
15042 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15043 s.select(new_selections.clone());
15044 });
15045 self.select_syntax_node_history.disable_clearing = false;
15046 }
15047
15048 let start_row = last_new.start.to_display_point(&display_map).row().0;
15049 let end_row = last_new.end.to_display_point(&display_map).row().0;
15050 let selection_height = end_row - start_row + 1;
15051 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15052
15053 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15054 let scroll_behavior = if fits_on_the_screen {
15055 self.request_autoscroll(Autoscroll::fit(), cx);
15056 SelectSyntaxNodeScrollBehavior::FitSelection
15057 } else if is_selection_reversed {
15058 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15059 SelectSyntaxNodeScrollBehavior::CursorTop
15060 } else {
15061 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15062 SelectSyntaxNodeScrollBehavior::CursorBottom
15063 };
15064
15065 self.select_syntax_node_history.push((
15066 old_selections,
15067 scroll_behavior,
15068 is_selection_reversed,
15069 ));
15070 }
15071
15072 pub fn select_smaller_syntax_node(
15073 &mut self,
15074 _: &SelectSmallerSyntaxNode,
15075 window: &mut Window,
15076 cx: &mut Context<Self>,
15077 ) {
15078 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15079
15080 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15081 self.select_syntax_node_history.pop()
15082 {
15083 if let Some(selection) = selections.last_mut() {
15084 selection.reversed = is_selection_reversed;
15085 }
15086
15087 self.select_syntax_node_history.disable_clearing = true;
15088 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15089 s.select(selections.to_vec());
15090 });
15091 self.select_syntax_node_history.disable_clearing = false;
15092
15093 match scroll_behavior {
15094 SelectSyntaxNodeScrollBehavior::CursorTop => {
15095 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15096 }
15097 SelectSyntaxNodeScrollBehavior::FitSelection => {
15098 self.request_autoscroll(Autoscroll::fit(), cx);
15099 }
15100 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15101 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15102 }
15103 }
15104 }
15105 }
15106
15107 pub fn unwrap_syntax_node(
15108 &mut self,
15109 _: &UnwrapSyntaxNode,
15110 window: &mut Window,
15111 cx: &mut Context<Self>,
15112 ) {
15113 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15114
15115 let buffer = self.buffer.read(cx).snapshot(cx);
15116 let selections = self
15117 .selections
15118 .all::<usize>(cx)
15119 .into_iter()
15120 // subtracting the offset requires sorting
15121 .sorted_by_key(|i| i.start);
15122
15123 let full_edits = selections
15124 .into_iter()
15125 .filter_map(|selection| {
15126 // Only requires two branches once if-let-chains stabilize (#53667)
15127 let child = if !selection.is_empty() {
15128 selection.range()
15129 } else if let Some((_, ancestor_range)) =
15130 buffer.syntax_ancestor(selection.start..selection.end)
15131 {
15132 match ancestor_range {
15133 MultiOrSingleBufferOffsetRange::Single(range) => range,
15134 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15135 }
15136 } else {
15137 selection.range()
15138 };
15139
15140 let mut parent = child.clone();
15141 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15142 parent = match ancestor_range {
15143 MultiOrSingleBufferOffsetRange::Single(range) => range,
15144 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15145 };
15146 if parent.start < child.start || parent.end > child.end {
15147 break;
15148 }
15149 }
15150
15151 if parent == child {
15152 return None;
15153 }
15154 let text = buffer.text_for_range(child).collect::<String>();
15155 Some((selection.id, parent, text))
15156 })
15157 .collect::<Vec<_>>();
15158
15159 self.transact(window, cx, |this, window, cx| {
15160 this.buffer.update(cx, |buffer, cx| {
15161 buffer.edit(
15162 full_edits
15163 .iter()
15164 .map(|(_, p, t)| (p.clone(), t.clone()))
15165 .collect::<Vec<_>>(),
15166 None,
15167 cx,
15168 );
15169 });
15170 this.change_selections(Default::default(), window, cx, |s| {
15171 let mut offset = 0;
15172 let mut selections = vec![];
15173 for (id, parent, text) in full_edits {
15174 let start = parent.start - offset;
15175 offset += parent.len() - text.len();
15176 selections.push(Selection {
15177 id,
15178 start,
15179 end: start + text.len(),
15180 reversed: false,
15181 goal: Default::default(),
15182 });
15183 }
15184 s.select(selections);
15185 });
15186 });
15187 }
15188
15189 pub fn select_next_syntax_node(
15190 &mut self,
15191 _: &SelectNextSyntaxNode,
15192 window: &mut Window,
15193 cx: &mut Context<Self>,
15194 ) {
15195 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15196 if old_selections.is_empty() {
15197 return;
15198 }
15199
15200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15201
15202 let buffer = self.buffer.read(cx).snapshot(cx);
15203 let mut selected_sibling = false;
15204
15205 let new_selections = old_selections
15206 .iter()
15207 .map(|selection| {
15208 let old_range = selection.start..selection.end;
15209
15210 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15211 let new_range = node.byte_range();
15212 selected_sibling = true;
15213 Selection {
15214 id: selection.id,
15215 start: new_range.start,
15216 end: new_range.end,
15217 goal: SelectionGoal::None,
15218 reversed: selection.reversed,
15219 }
15220 } else {
15221 selection.clone()
15222 }
15223 })
15224 .collect::<Vec<_>>();
15225
15226 if selected_sibling {
15227 self.change_selections(
15228 SelectionEffects::scroll(Autoscroll::fit()),
15229 window,
15230 cx,
15231 |s| {
15232 s.select(new_selections);
15233 },
15234 );
15235 }
15236 }
15237
15238 pub fn select_prev_syntax_node(
15239 &mut self,
15240 _: &SelectPreviousSyntaxNode,
15241 window: &mut Window,
15242 cx: &mut Context<Self>,
15243 ) {
15244 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15245 if old_selections.is_empty() {
15246 return;
15247 }
15248
15249 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15250
15251 let buffer = self.buffer.read(cx).snapshot(cx);
15252 let mut selected_sibling = false;
15253
15254 let new_selections = old_selections
15255 .iter()
15256 .map(|selection| {
15257 let old_range = selection.start..selection.end;
15258
15259 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15260 let new_range = node.byte_range();
15261 selected_sibling = true;
15262 Selection {
15263 id: selection.id,
15264 start: new_range.start,
15265 end: new_range.end,
15266 goal: SelectionGoal::None,
15267 reversed: selection.reversed,
15268 }
15269 } else {
15270 selection.clone()
15271 }
15272 })
15273 .collect::<Vec<_>>();
15274
15275 if selected_sibling {
15276 self.change_selections(
15277 SelectionEffects::scroll(Autoscroll::fit()),
15278 window,
15279 cx,
15280 |s| {
15281 s.select(new_selections);
15282 },
15283 );
15284 }
15285 }
15286
15287 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15288 if !EditorSettings::get_global(cx).gutter.runnables {
15289 self.clear_tasks();
15290 return Task::ready(());
15291 }
15292 let project = self.project().map(Entity::downgrade);
15293 let task_sources = self.lsp_task_sources(cx);
15294 let multi_buffer = self.buffer.downgrade();
15295 cx.spawn_in(window, async move |editor, cx| {
15296 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15297 let Some(project) = project.and_then(|p| p.upgrade()) else {
15298 return;
15299 };
15300 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15301 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15302 }) else {
15303 return;
15304 };
15305
15306 let hide_runnables = project
15307 .update(cx, |project, _| project.is_via_collab())
15308 .unwrap_or(true);
15309 if hide_runnables {
15310 return;
15311 }
15312 let new_rows =
15313 cx.background_spawn({
15314 let snapshot = display_snapshot.clone();
15315 async move {
15316 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15317 }
15318 })
15319 .await;
15320 let Ok(lsp_tasks) =
15321 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15322 else {
15323 return;
15324 };
15325 let lsp_tasks = lsp_tasks.await;
15326
15327 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15328 lsp_tasks
15329 .into_iter()
15330 .flat_map(|(kind, tasks)| {
15331 tasks.into_iter().filter_map(move |(location, task)| {
15332 Some((kind.clone(), location?, task))
15333 })
15334 })
15335 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15336 let buffer = location.target.buffer;
15337 let buffer_snapshot = buffer.read(cx).snapshot();
15338 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15339 |(excerpt_id, snapshot, _)| {
15340 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15341 display_snapshot
15342 .buffer_snapshot
15343 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15344 } else {
15345 None
15346 }
15347 },
15348 );
15349 if let Some(offset) = offset {
15350 let task_buffer_range =
15351 location.target.range.to_point(&buffer_snapshot);
15352 let context_buffer_range =
15353 task_buffer_range.to_offset(&buffer_snapshot);
15354 let context_range = BufferOffset(context_buffer_range.start)
15355 ..BufferOffset(context_buffer_range.end);
15356
15357 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15358 .or_insert_with(|| RunnableTasks {
15359 templates: Vec::new(),
15360 offset,
15361 column: task_buffer_range.start.column,
15362 extra_variables: HashMap::default(),
15363 context_range,
15364 })
15365 .templates
15366 .push((kind, task.original_task().clone()));
15367 }
15368
15369 acc
15370 })
15371 }) else {
15372 return;
15373 };
15374
15375 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15376 buffer.language_settings(cx).tasks.prefer_lsp
15377 }) else {
15378 return;
15379 };
15380
15381 let rows = Self::runnable_rows(
15382 project,
15383 display_snapshot,
15384 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15385 new_rows,
15386 cx.clone(),
15387 )
15388 .await;
15389 editor
15390 .update(cx, |editor, _| {
15391 editor.clear_tasks();
15392 for (key, mut value) in rows {
15393 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15394 value.templates.extend(lsp_tasks.templates);
15395 }
15396
15397 editor.insert_tasks(key, value);
15398 }
15399 for (key, value) in lsp_tasks_by_rows {
15400 editor.insert_tasks(key, value);
15401 }
15402 })
15403 .ok();
15404 })
15405 }
15406 fn fetch_runnable_ranges(
15407 snapshot: &DisplaySnapshot,
15408 range: Range<Anchor>,
15409 ) -> Vec<language::RunnableRange> {
15410 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15411 }
15412
15413 fn runnable_rows(
15414 project: Entity<Project>,
15415 snapshot: DisplaySnapshot,
15416 prefer_lsp: bool,
15417 runnable_ranges: Vec<RunnableRange>,
15418 cx: AsyncWindowContext,
15419 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15420 cx.spawn(async move |cx| {
15421 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15422 for mut runnable in runnable_ranges {
15423 let Some(tasks) = cx
15424 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15425 .ok()
15426 else {
15427 continue;
15428 };
15429 let mut tasks = tasks.await;
15430
15431 if prefer_lsp {
15432 tasks.retain(|(task_kind, _)| {
15433 !matches!(task_kind, TaskSourceKind::Language { .. })
15434 });
15435 }
15436 if tasks.is_empty() {
15437 continue;
15438 }
15439
15440 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15441 let Some(row) = snapshot
15442 .buffer_snapshot
15443 .buffer_line_for_row(MultiBufferRow(point.row))
15444 .map(|(_, range)| range.start.row)
15445 else {
15446 continue;
15447 };
15448
15449 let context_range =
15450 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15451 runnable_rows.push((
15452 (runnable.buffer_id, row),
15453 RunnableTasks {
15454 templates: tasks,
15455 offset: snapshot
15456 .buffer_snapshot
15457 .anchor_before(runnable.run_range.start),
15458 context_range,
15459 column: point.column,
15460 extra_variables: runnable.extra_captures,
15461 },
15462 ));
15463 }
15464 runnable_rows
15465 })
15466 }
15467
15468 fn templates_with_tags(
15469 project: &Entity<Project>,
15470 runnable: &mut Runnable,
15471 cx: &mut App,
15472 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15473 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15474 let (worktree_id, file) = project
15475 .buffer_for_id(runnable.buffer, cx)
15476 .and_then(|buffer| buffer.read(cx).file())
15477 .map(|file| (file.worktree_id(cx), file.clone()))
15478 .unzip();
15479
15480 (
15481 project.task_store().read(cx).task_inventory().cloned(),
15482 worktree_id,
15483 file,
15484 )
15485 });
15486
15487 let tags = mem::take(&mut runnable.tags);
15488 let language = runnable.language.clone();
15489 cx.spawn(async move |cx| {
15490 let mut templates_with_tags = Vec::new();
15491 if let Some(inventory) = inventory {
15492 for RunnableTag(tag) in tags {
15493 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15494 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15495 }) else {
15496 return templates_with_tags;
15497 };
15498 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15499 move |(_, template)| {
15500 template.tags.iter().any(|source_tag| source_tag == &tag)
15501 },
15502 ));
15503 }
15504 }
15505 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15506
15507 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15508 // Strongest source wins; if we have worktree tag binding, prefer that to
15509 // global and language bindings;
15510 // if we have a global binding, prefer that to language binding.
15511 let first_mismatch = templates_with_tags
15512 .iter()
15513 .position(|(tag_source, _)| tag_source != leading_tag_source);
15514 if let Some(index) = first_mismatch {
15515 templates_with_tags.truncate(index);
15516 }
15517 }
15518
15519 templates_with_tags
15520 })
15521 }
15522
15523 pub fn move_to_enclosing_bracket(
15524 &mut self,
15525 _: &MoveToEnclosingBracket,
15526 window: &mut Window,
15527 cx: &mut Context<Self>,
15528 ) {
15529 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15530 self.change_selections(Default::default(), window, cx, |s| {
15531 s.move_offsets_with(|snapshot, selection| {
15532 let Some(enclosing_bracket_ranges) =
15533 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15534 else {
15535 return;
15536 };
15537
15538 let mut best_length = usize::MAX;
15539 let mut best_inside = false;
15540 let mut best_in_bracket_range = false;
15541 let mut best_destination = None;
15542 for (open, close) in enclosing_bracket_ranges {
15543 let close = close.to_inclusive();
15544 let length = close.end() - open.start;
15545 let inside = selection.start >= open.end && selection.end <= *close.start();
15546 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15547 || close.contains(&selection.head());
15548
15549 // If best is next to a bracket and current isn't, skip
15550 if !in_bracket_range && best_in_bracket_range {
15551 continue;
15552 }
15553
15554 // Prefer smaller lengths unless best is inside and current isn't
15555 if length > best_length && (best_inside || !inside) {
15556 continue;
15557 }
15558
15559 best_length = length;
15560 best_inside = inside;
15561 best_in_bracket_range = in_bracket_range;
15562 best_destination = Some(
15563 if close.contains(&selection.start) && close.contains(&selection.end) {
15564 if inside { open.end } else { open.start }
15565 } else if inside {
15566 *close.start()
15567 } else {
15568 *close.end()
15569 },
15570 );
15571 }
15572
15573 if let Some(destination) = best_destination {
15574 selection.collapse_to(destination, SelectionGoal::None);
15575 }
15576 })
15577 });
15578 }
15579
15580 pub fn undo_selection(
15581 &mut self,
15582 _: &UndoSelection,
15583 window: &mut Window,
15584 cx: &mut Context<Self>,
15585 ) {
15586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15587 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15588 self.selection_history.mode = SelectionHistoryMode::Undoing;
15589 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15590 this.end_selection(window, cx);
15591 this.change_selections(
15592 SelectionEffects::scroll(Autoscroll::newest()),
15593 window,
15594 cx,
15595 |s| s.select_anchors(entry.selections.to_vec()),
15596 );
15597 });
15598 self.selection_history.mode = SelectionHistoryMode::Normal;
15599
15600 self.select_next_state = entry.select_next_state;
15601 self.select_prev_state = entry.select_prev_state;
15602 self.add_selections_state = entry.add_selections_state;
15603 }
15604 }
15605
15606 pub fn redo_selection(
15607 &mut self,
15608 _: &RedoSelection,
15609 window: &mut Window,
15610 cx: &mut Context<Self>,
15611 ) {
15612 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15613 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15614 self.selection_history.mode = SelectionHistoryMode::Redoing;
15615 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15616 this.end_selection(window, cx);
15617 this.change_selections(
15618 SelectionEffects::scroll(Autoscroll::newest()),
15619 window,
15620 cx,
15621 |s| s.select_anchors(entry.selections.to_vec()),
15622 );
15623 });
15624 self.selection_history.mode = SelectionHistoryMode::Normal;
15625
15626 self.select_next_state = entry.select_next_state;
15627 self.select_prev_state = entry.select_prev_state;
15628 self.add_selections_state = entry.add_selections_state;
15629 }
15630 }
15631
15632 pub fn expand_excerpts(
15633 &mut self,
15634 action: &ExpandExcerpts,
15635 _: &mut Window,
15636 cx: &mut Context<Self>,
15637 ) {
15638 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15639 }
15640
15641 pub fn expand_excerpts_down(
15642 &mut self,
15643 action: &ExpandExcerptsDown,
15644 _: &mut Window,
15645 cx: &mut Context<Self>,
15646 ) {
15647 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15648 }
15649
15650 pub fn expand_excerpts_up(
15651 &mut self,
15652 action: &ExpandExcerptsUp,
15653 _: &mut Window,
15654 cx: &mut Context<Self>,
15655 ) {
15656 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15657 }
15658
15659 pub fn expand_excerpts_for_direction(
15660 &mut self,
15661 lines: u32,
15662 direction: ExpandExcerptDirection,
15663
15664 cx: &mut Context<Self>,
15665 ) {
15666 let selections = self.selections.disjoint_anchors();
15667
15668 let lines = if lines == 0 {
15669 EditorSettings::get_global(cx).expand_excerpt_lines
15670 } else {
15671 lines
15672 };
15673
15674 self.buffer.update(cx, |buffer, cx| {
15675 let snapshot = buffer.snapshot(cx);
15676 let mut excerpt_ids = selections
15677 .iter()
15678 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15679 .collect::<Vec<_>>();
15680 excerpt_ids.sort();
15681 excerpt_ids.dedup();
15682 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15683 })
15684 }
15685
15686 pub fn expand_excerpt(
15687 &mut self,
15688 excerpt: ExcerptId,
15689 direction: ExpandExcerptDirection,
15690 window: &mut Window,
15691 cx: &mut Context<Self>,
15692 ) {
15693 let current_scroll_position = self.scroll_position(cx);
15694 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15695 let mut should_scroll_up = false;
15696
15697 if direction == ExpandExcerptDirection::Down {
15698 let multi_buffer = self.buffer.read(cx);
15699 let snapshot = multi_buffer.snapshot(cx);
15700 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15701 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15702 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15703 {
15704 let buffer_snapshot = buffer.read(cx).snapshot();
15705 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15706 let last_row = buffer_snapshot.max_point().row;
15707 let lines_below = last_row.saturating_sub(excerpt_end_row);
15708 should_scroll_up = lines_below >= lines_to_expand;
15709 }
15710 }
15711
15712 self.buffer.update(cx, |buffer, cx| {
15713 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15714 });
15715
15716 if should_scroll_up {
15717 let new_scroll_position =
15718 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15719 self.set_scroll_position(new_scroll_position, window, cx);
15720 }
15721 }
15722
15723 pub fn go_to_singleton_buffer_point(
15724 &mut self,
15725 point: Point,
15726 window: &mut Window,
15727 cx: &mut Context<Self>,
15728 ) {
15729 self.go_to_singleton_buffer_range(point..point, window, cx);
15730 }
15731
15732 pub fn go_to_singleton_buffer_range(
15733 &mut self,
15734 range: Range<Point>,
15735 window: &mut Window,
15736 cx: &mut Context<Self>,
15737 ) {
15738 let multibuffer = self.buffer().read(cx);
15739 let Some(buffer) = multibuffer.as_singleton() else {
15740 return;
15741 };
15742 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15743 return;
15744 };
15745 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15746 return;
15747 };
15748 self.change_selections(
15749 SelectionEffects::default().nav_history(true),
15750 window,
15751 cx,
15752 |s| s.select_anchor_ranges([start..end]),
15753 );
15754 }
15755
15756 pub fn go_to_diagnostic(
15757 &mut self,
15758 action: &GoToDiagnostic,
15759 window: &mut Window,
15760 cx: &mut Context<Self>,
15761 ) {
15762 if !self.diagnostics_enabled() {
15763 return;
15764 }
15765 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15766 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15767 }
15768
15769 pub fn go_to_prev_diagnostic(
15770 &mut self,
15771 action: &GoToPreviousDiagnostic,
15772 window: &mut Window,
15773 cx: &mut Context<Self>,
15774 ) {
15775 if !self.diagnostics_enabled() {
15776 return;
15777 }
15778 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15779 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15780 }
15781
15782 pub fn go_to_diagnostic_impl(
15783 &mut self,
15784 direction: Direction,
15785 severity: GoToDiagnosticSeverityFilter,
15786 window: &mut Window,
15787 cx: &mut Context<Self>,
15788 ) {
15789 let buffer = self.buffer.read(cx).snapshot(cx);
15790 let selection = self.selections.newest::<usize>(cx);
15791
15792 let mut active_group_id = None;
15793 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15794 && active_group.active_range.start.to_offset(&buffer) == selection.start
15795 {
15796 active_group_id = Some(active_group.group_id);
15797 }
15798
15799 fn filtered(
15800 snapshot: EditorSnapshot,
15801 severity: GoToDiagnosticSeverityFilter,
15802 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15803 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15804 diagnostics
15805 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15806 .filter(|entry| entry.range.start != entry.range.end)
15807 .filter(|entry| !entry.diagnostic.is_unnecessary)
15808 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15809 }
15810
15811 let snapshot = self.snapshot(window, cx);
15812 let before = filtered(
15813 snapshot.clone(),
15814 severity,
15815 buffer
15816 .diagnostics_in_range(0..selection.start)
15817 .filter(|entry| entry.range.start <= selection.start),
15818 );
15819 let after = filtered(
15820 snapshot,
15821 severity,
15822 buffer
15823 .diagnostics_in_range(selection.start..buffer.len())
15824 .filter(|entry| entry.range.start >= selection.start),
15825 );
15826
15827 let mut found: Option<DiagnosticEntry<usize>> = None;
15828 if direction == Direction::Prev {
15829 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15830 {
15831 for diagnostic in prev_diagnostics.into_iter().rev() {
15832 if diagnostic.range.start != selection.start
15833 || active_group_id
15834 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15835 {
15836 found = Some(diagnostic);
15837 break 'outer;
15838 }
15839 }
15840 }
15841 } else {
15842 for diagnostic in after.chain(before) {
15843 if diagnostic.range.start != selection.start
15844 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15845 {
15846 found = Some(diagnostic);
15847 break;
15848 }
15849 }
15850 }
15851 let Some(next_diagnostic) = found else {
15852 return;
15853 };
15854
15855 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15856 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15857 return;
15858 };
15859 self.change_selections(Default::default(), window, cx, |s| {
15860 s.select_ranges(vec![
15861 next_diagnostic.range.start..next_diagnostic.range.start,
15862 ])
15863 });
15864 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15865 self.refresh_edit_prediction(false, true, window, cx);
15866 }
15867
15868 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15869 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15870 let snapshot = self.snapshot(window, cx);
15871 let selection = self.selections.newest::<Point>(cx);
15872 self.go_to_hunk_before_or_after_position(
15873 &snapshot,
15874 selection.head(),
15875 Direction::Next,
15876 window,
15877 cx,
15878 );
15879 }
15880
15881 pub fn go_to_hunk_before_or_after_position(
15882 &mut self,
15883 snapshot: &EditorSnapshot,
15884 position: Point,
15885 direction: Direction,
15886 window: &mut Window,
15887 cx: &mut Context<Editor>,
15888 ) {
15889 let row = if direction == Direction::Next {
15890 self.hunk_after_position(snapshot, position)
15891 .map(|hunk| hunk.row_range.start)
15892 } else {
15893 self.hunk_before_position(snapshot, position)
15894 };
15895
15896 if let Some(row) = row {
15897 let destination = Point::new(row.0, 0);
15898 let autoscroll = Autoscroll::center();
15899
15900 self.unfold_ranges(&[destination..destination], false, false, cx);
15901 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15902 s.select_ranges([destination..destination]);
15903 });
15904 }
15905 }
15906
15907 fn hunk_after_position(
15908 &mut self,
15909 snapshot: &EditorSnapshot,
15910 position: Point,
15911 ) -> Option<MultiBufferDiffHunk> {
15912 snapshot
15913 .buffer_snapshot
15914 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15915 .find(|hunk| hunk.row_range.start.0 > position.row)
15916 .or_else(|| {
15917 snapshot
15918 .buffer_snapshot
15919 .diff_hunks_in_range(Point::zero()..position)
15920 .find(|hunk| hunk.row_range.end.0 < position.row)
15921 })
15922 }
15923
15924 fn go_to_prev_hunk(
15925 &mut self,
15926 _: &GoToPreviousHunk,
15927 window: &mut Window,
15928 cx: &mut Context<Self>,
15929 ) {
15930 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15931 let snapshot = self.snapshot(window, cx);
15932 let selection = self.selections.newest::<Point>(cx);
15933 self.go_to_hunk_before_or_after_position(
15934 &snapshot,
15935 selection.head(),
15936 Direction::Prev,
15937 window,
15938 cx,
15939 );
15940 }
15941
15942 fn hunk_before_position(
15943 &mut self,
15944 snapshot: &EditorSnapshot,
15945 position: Point,
15946 ) -> Option<MultiBufferRow> {
15947 snapshot
15948 .buffer_snapshot
15949 .diff_hunk_before(position)
15950 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15951 }
15952
15953 fn go_to_next_change(
15954 &mut self,
15955 _: &GoToNextChange,
15956 window: &mut Window,
15957 cx: &mut Context<Self>,
15958 ) {
15959 if let Some(selections) = self
15960 .change_list
15961 .next_change(1, Direction::Next)
15962 .map(|s| s.to_vec())
15963 {
15964 self.change_selections(Default::default(), window, cx, |s| {
15965 let map = s.display_map();
15966 s.select_display_ranges(selections.iter().map(|a| {
15967 let point = a.to_display_point(&map);
15968 point..point
15969 }))
15970 })
15971 }
15972 }
15973
15974 fn go_to_previous_change(
15975 &mut self,
15976 _: &GoToPreviousChange,
15977 window: &mut Window,
15978 cx: &mut Context<Self>,
15979 ) {
15980 if let Some(selections) = self
15981 .change_list
15982 .next_change(1, Direction::Prev)
15983 .map(|s| s.to_vec())
15984 {
15985 self.change_selections(Default::default(), window, cx, |s| {
15986 let map = s.display_map();
15987 s.select_display_ranges(selections.iter().map(|a| {
15988 let point = a.to_display_point(&map);
15989 point..point
15990 }))
15991 })
15992 }
15993 }
15994
15995 pub fn go_to_next_document_highlight(
15996 &mut self,
15997 _: &GoToNextDocumentHighlight,
15998 window: &mut Window,
15999 cx: &mut Context<Self>,
16000 ) {
16001 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16002 }
16003
16004 pub fn go_to_prev_document_highlight(
16005 &mut self,
16006 _: &GoToPreviousDocumentHighlight,
16007 window: &mut Window,
16008 cx: &mut Context<Self>,
16009 ) {
16010 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16011 }
16012
16013 pub fn go_to_document_highlight_before_or_after_position(
16014 &mut self,
16015 direction: Direction,
16016 window: &mut Window,
16017 cx: &mut Context<Editor>,
16018 ) {
16019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16020 let snapshot = self.snapshot(window, cx);
16021 let buffer = &snapshot.buffer_snapshot;
16022 let position = self.selections.newest::<Point>(cx).head();
16023 let anchor_position = buffer.anchor_after(position);
16024
16025 // Get all document highlights (both read and write)
16026 let mut all_highlights = Vec::new();
16027
16028 if let Some((_, read_highlights)) = self
16029 .background_highlights
16030 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16031 {
16032 all_highlights.extend(read_highlights.iter());
16033 }
16034
16035 if let Some((_, write_highlights)) = self
16036 .background_highlights
16037 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16038 {
16039 all_highlights.extend(write_highlights.iter());
16040 }
16041
16042 if all_highlights.is_empty() {
16043 return;
16044 }
16045
16046 // Sort highlights by position
16047 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16048
16049 let target_highlight = match direction {
16050 Direction::Next => {
16051 // Find the first highlight after the current position
16052 all_highlights
16053 .iter()
16054 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16055 }
16056 Direction::Prev => {
16057 // Find the last highlight before the current position
16058 all_highlights
16059 .iter()
16060 .rev()
16061 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16062 }
16063 };
16064
16065 if let Some(highlight) = target_highlight {
16066 let destination = highlight.start.to_point(buffer);
16067 let autoscroll = Autoscroll::center();
16068
16069 self.unfold_ranges(&[destination..destination], false, false, cx);
16070 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16071 s.select_ranges([destination..destination]);
16072 });
16073 }
16074 }
16075
16076 fn go_to_line<T: 'static>(
16077 &mut self,
16078 position: Anchor,
16079 highlight_color: Option<Hsla>,
16080 window: &mut Window,
16081 cx: &mut Context<Self>,
16082 ) {
16083 let snapshot = self.snapshot(window, cx).display_snapshot;
16084 let position = position.to_point(&snapshot.buffer_snapshot);
16085 let start = snapshot
16086 .buffer_snapshot
16087 .clip_point(Point::new(position.row, 0), Bias::Left);
16088 let end = start + Point::new(1, 0);
16089 let start = snapshot.buffer_snapshot.anchor_before(start);
16090 let end = snapshot.buffer_snapshot.anchor_before(end);
16091
16092 self.highlight_rows::<T>(
16093 start..end,
16094 highlight_color
16095 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16096 Default::default(),
16097 cx,
16098 );
16099
16100 if self.buffer.read(cx).is_singleton() {
16101 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16102 }
16103 }
16104
16105 pub fn go_to_definition(
16106 &mut self,
16107 _: &GoToDefinition,
16108 window: &mut Window,
16109 cx: &mut Context<Self>,
16110 ) -> Task<Result<Navigated>> {
16111 let definition =
16112 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16113 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16114 cx.spawn_in(window, async move |editor, cx| {
16115 if definition.await? == Navigated::Yes {
16116 return Ok(Navigated::Yes);
16117 }
16118 match fallback_strategy {
16119 GoToDefinitionFallback::None => Ok(Navigated::No),
16120 GoToDefinitionFallback::FindAllReferences => {
16121 match editor.update_in(cx, |editor, window, cx| {
16122 editor.find_all_references(&FindAllReferences, window, cx)
16123 })? {
16124 Some(references) => references.await,
16125 None => Ok(Navigated::No),
16126 }
16127 }
16128 }
16129 })
16130 }
16131
16132 pub fn go_to_declaration(
16133 &mut self,
16134 _: &GoToDeclaration,
16135 window: &mut Window,
16136 cx: &mut Context<Self>,
16137 ) -> Task<Result<Navigated>> {
16138 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16139 }
16140
16141 pub fn go_to_declaration_split(
16142 &mut self,
16143 _: &GoToDeclaration,
16144 window: &mut Window,
16145 cx: &mut Context<Self>,
16146 ) -> Task<Result<Navigated>> {
16147 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16148 }
16149
16150 pub fn go_to_implementation(
16151 &mut self,
16152 _: &GoToImplementation,
16153 window: &mut Window,
16154 cx: &mut Context<Self>,
16155 ) -> Task<Result<Navigated>> {
16156 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16157 }
16158
16159 pub fn go_to_implementation_split(
16160 &mut self,
16161 _: &GoToImplementationSplit,
16162 window: &mut Window,
16163 cx: &mut Context<Self>,
16164 ) -> Task<Result<Navigated>> {
16165 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16166 }
16167
16168 pub fn go_to_type_definition(
16169 &mut self,
16170 _: &GoToTypeDefinition,
16171 window: &mut Window,
16172 cx: &mut Context<Self>,
16173 ) -> Task<Result<Navigated>> {
16174 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16175 }
16176
16177 pub fn go_to_definition_split(
16178 &mut self,
16179 _: &GoToDefinitionSplit,
16180 window: &mut Window,
16181 cx: &mut Context<Self>,
16182 ) -> Task<Result<Navigated>> {
16183 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16184 }
16185
16186 pub fn go_to_type_definition_split(
16187 &mut self,
16188 _: &GoToTypeDefinitionSplit,
16189 window: &mut Window,
16190 cx: &mut Context<Self>,
16191 ) -> Task<Result<Navigated>> {
16192 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16193 }
16194
16195 fn go_to_definition_of_kind(
16196 &mut self,
16197 kind: GotoDefinitionKind,
16198 split: bool,
16199 window: &mut Window,
16200 cx: &mut Context<Self>,
16201 ) -> Task<Result<Navigated>> {
16202 let Some(provider) = self.semantics_provider.clone() else {
16203 return Task::ready(Ok(Navigated::No));
16204 };
16205 let head = self.selections.newest::<usize>(cx).head();
16206 let buffer = self.buffer.read(cx);
16207 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16208 return Task::ready(Ok(Navigated::No));
16209 };
16210 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16211 return Task::ready(Ok(Navigated::No));
16212 };
16213
16214 cx.spawn_in(window, async move |editor, cx| {
16215 let Some(definitions) = definitions.await? else {
16216 return Ok(Navigated::No);
16217 };
16218 let navigated = editor
16219 .update_in(cx, |editor, window, cx| {
16220 editor.navigate_to_hover_links(
16221 Some(kind),
16222 definitions
16223 .into_iter()
16224 .filter(|location| {
16225 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16226 })
16227 .map(HoverLink::Text)
16228 .collect::<Vec<_>>(),
16229 split,
16230 window,
16231 cx,
16232 )
16233 })?
16234 .await?;
16235 anyhow::Ok(navigated)
16236 })
16237 }
16238
16239 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16240 let selection = self.selections.newest_anchor();
16241 let head = selection.head();
16242 let tail = selection.tail();
16243
16244 let Some((buffer, start_position)) =
16245 self.buffer.read(cx).text_anchor_for_position(head, cx)
16246 else {
16247 return;
16248 };
16249
16250 let end_position = if head != tail {
16251 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16252 return;
16253 };
16254 Some(pos)
16255 } else {
16256 None
16257 };
16258
16259 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16260 let url = if let Some(end_pos) = end_position {
16261 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16262 } else {
16263 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16264 };
16265
16266 if let Some(url) = url {
16267 editor.update(cx, |_, cx| {
16268 cx.open_url(&url);
16269 })
16270 } else {
16271 Ok(())
16272 }
16273 });
16274
16275 url_finder.detach();
16276 }
16277
16278 pub fn open_selected_filename(
16279 &mut self,
16280 _: &OpenSelectedFilename,
16281 window: &mut Window,
16282 cx: &mut Context<Self>,
16283 ) {
16284 let Some(workspace) = self.workspace() else {
16285 return;
16286 };
16287
16288 let position = self.selections.newest_anchor().head();
16289
16290 let Some((buffer, buffer_position)) =
16291 self.buffer.read(cx).text_anchor_for_position(position, cx)
16292 else {
16293 return;
16294 };
16295
16296 let project = self.project.clone();
16297
16298 cx.spawn_in(window, async move |_, cx| {
16299 let result = find_file(&buffer, project, buffer_position, cx).await;
16300
16301 if let Some((_, path)) = result {
16302 workspace
16303 .update_in(cx, |workspace, window, cx| {
16304 workspace.open_resolved_path(path, window, cx)
16305 })?
16306 .await?;
16307 }
16308 anyhow::Ok(())
16309 })
16310 .detach();
16311 }
16312
16313 pub(crate) fn navigate_to_hover_links(
16314 &mut self,
16315 kind: Option<GotoDefinitionKind>,
16316 definitions: Vec<HoverLink>,
16317 split: bool,
16318 window: &mut Window,
16319 cx: &mut Context<Editor>,
16320 ) -> Task<Result<Navigated>> {
16321 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16322 let mut first_url_or_file = None;
16323 let definitions: Vec<_> = definitions
16324 .into_iter()
16325 .filter_map(|def| match def {
16326 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16327 HoverLink::InlayHint(lsp_location, server_id) => {
16328 let computation =
16329 self.compute_target_location(lsp_location, server_id, window, cx);
16330 Some(cx.background_spawn(computation))
16331 }
16332 HoverLink::Url(url) => {
16333 first_url_or_file = Some(Either::Left(url));
16334 None
16335 }
16336 HoverLink::File(path) => {
16337 first_url_or_file = Some(Either::Right(path));
16338 None
16339 }
16340 })
16341 .collect();
16342
16343 let workspace = self.workspace();
16344
16345 cx.spawn_in(window, async move |editor, acx| {
16346 let mut locations: Vec<Location> = future::join_all(definitions)
16347 .await
16348 .into_iter()
16349 .filter_map(|location| location.transpose())
16350 .collect::<Result<_>>()
16351 .context("location tasks")?;
16352
16353 if locations.len() > 1 {
16354 let Some(workspace) = workspace else {
16355 return Ok(Navigated::No);
16356 };
16357
16358 let tab_kind = match kind {
16359 Some(GotoDefinitionKind::Implementation) => "Implementations",
16360 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16361 Some(GotoDefinitionKind::Declaration) => "Declarations",
16362 Some(GotoDefinitionKind::Type) => "Types",
16363 };
16364 let title = editor
16365 .update_in(acx, |_, _, cx| {
16366 let target = locations
16367 .iter()
16368 .map(|location| {
16369 location
16370 .buffer
16371 .read(cx)
16372 .text_for_range(location.range.clone())
16373 .collect::<String>()
16374 })
16375 .filter(|text| !text.contains('\n'))
16376 .unique()
16377 .take(3)
16378 .join(", ");
16379 if target.is_empty() {
16380 tab_kind.to_owned()
16381 } else {
16382 format!("{tab_kind} for {target}")
16383 }
16384 })
16385 .context("buffer title")?;
16386
16387 let opened = workspace
16388 .update_in(acx, |workspace, window, cx| {
16389 Self::open_locations_in_multibuffer(
16390 workspace,
16391 locations,
16392 title,
16393 split,
16394 MultibufferSelectionMode::First,
16395 window,
16396 cx,
16397 )
16398 })
16399 .is_ok();
16400
16401 anyhow::Ok(Navigated::from_bool(opened))
16402 } else if locations.is_empty() {
16403 // If there is one url or file, open it directly
16404 match first_url_or_file {
16405 Some(Either::Left(url)) => {
16406 acx.update(|_, cx| cx.open_url(&url))?;
16407 Ok(Navigated::Yes)
16408 }
16409 Some(Either::Right(path)) => {
16410 let Some(workspace) = workspace else {
16411 return Ok(Navigated::No);
16412 };
16413
16414 workspace
16415 .update_in(acx, |workspace, window, cx| {
16416 workspace.open_resolved_path(path, window, cx)
16417 })?
16418 .await?;
16419 Ok(Navigated::Yes)
16420 }
16421 None => Ok(Navigated::No),
16422 }
16423 } else {
16424 let Some(workspace) = workspace else {
16425 return Ok(Navigated::No);
16426 };
16427
16428 let target = locations.pop().unwrap();
16429 editor.update_in(acx, |editor, window, cx| {
16430 let range = target.range.to_point(target.buffer.read(cx));
16431 let range = editor.range_for_match(&range);
16432 let range = collapse_multiline_range(range);
16433
16434 if !split
16435 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16436 {
16437 editor.go_to_singleton_buffer_range(range, window, cx);
16438 } else {
16439 let pane = workspace.read(cx).active_pane().clone();
16440 window.defer(cx, move |window, cx| {
16441 let target_editor: Entity<Self> =
16442 workspace.update(cx, |workspace, cx| {
16443 let pane = if split {
16444 workspace.adjacent_pane(window, cx)
16445 } else {
16446 workspace.active_pane().clone()
16447 };
16448
16449 workspace.open_project_item(
16450 pane,
16451 target.buffer.clone(),
16452 true,
16453 true,
16454 window,
16455 cx,
16456 )
16457 });
16458 target_editor.update(cx, |target_editor, cx| {
16459 // When selecting a definition in a different buffer, disable the nav history
16460 // to avoid creating a history entry at the previous cursor location.
16461 pane.update(cx, |pane, _| pane.disable_history());
16462 target_editor.go_to_singleton_buffer_range(range, window, cx);
16463 pane.update(cx, |pane, _| pane.enable_history());
16464 });
16465 });
16466 }
16467 Navigated::Yes
16468 })
16469 }
16470 })
16471 }
16472
16473 fn compute_target_location(
16474 &self,
16475 lsp_location: lsp::Location,
16476 server_id: LanguageServerId,
16477 window: &mut Window,
16478 cx: &mut Context<Self>,
16479 ) -> Task<anyhow::Result<Option<Location>>> {
16480 let Some(project) = self.project.clone() else {
16481 return Task::ready(Ok(None));
16482 };
16483
16484 cx.spawn_in(window, async move |editor, cx| {
16485 let location_task = editor.update(cx, |_, cx| {
16486 project.update(cx, |project, cx| {
16487 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16488 })
16489 })?;
16490 let location = Some({
16491 let target_buffer_handle = location_task.await.context("open local buffer")?;
16492 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16493 let target_start = target_buffer
16494 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16495 let target_end = target_buffer
16496 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16497 target_buffer.anchor_after(target_start)
16498 ..target_buffer.anchor_before(target_end)
16499 })?;
16500 Location {
16501 buffer: target_buffer_handle,
16502 range,
16503 }
16504 });
16505 Ok(location)
16506 })
16507 }
16508
16509 pub fn find_all_references(
16510 &mut self,
16511 _: &FindAllReferences,
16512 window: &mut Window,
16513 cx: &mut Context<Self>,
16514 ) -> Option<Task<Result<Navigated>>> {
16515 let selection = self.selections.newest::<usize>(cx);
16516 let multi_buffer = self.buffer.read(cx);
16517 let head = selection.head();
16518
16519 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16520 let head_anchor = multi_buffer_snapshot.anchor_at(
16521 head,
16522 if head < selection.tail() {
16523 Bias::Right
16524 } else {
16525 Bias::Left
16526 },
16527 );
16528
16529 match self
16530 .find_all_references_task_sources
16531 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16532 {
16533 Ok(_) => {
16534 log::info!(
16535 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16536 );
16537 return None;
16538 }
16539 Err(i) => {
16540 self.find_all_references_task_sources.insert(i, head_anchor);
16541 }
16542 }
16543
16544 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16545 let workspace = self.workspace()?;
16546 let project = workspace.read(cx).project().clone();
16547 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16548 Some(cx.spawn_in(window, async move |editor, cx| {
16549 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16550 if let Ok(i) = editor
16551 .find_all_references_task_sources
16552 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16553 {
16554 editor.find_all_references_task_sources.remove(i);
16555 }
16556 });
16557
16558 let Some(locations) = references.await? else {
16559 return anyhow::Ok(Navigated::No);
16560 };
16561 if locations.is_empty() {
16562 return anyhow::Ok(Navigated::No);
16563 }
16564
16565 workspace.update_in(cx, |workspace, window, cx| {
16566 let target = locations
16567 .iter()
16568 .map(|location| {
16569 location
16570 .buffer
16571 .read(cx)
16572 .text_for_range(location.range.clone())
16573 .collect::<String>()
16574 })
16575 .filter(|text| !text.contains('\n'))
16576 .unique()
16577 .take(3)
16578 .join(", ");
16579 let title = if target.is_empty() {
16580 "References".to_owned()
16581 } else {
16582 format!("References to {target}")
16583 };
16584 Self::open_locations_in_multibuffer(
16585 workspace,
16586 locations,
16587 title,
16588 false,
16589 MultibufferSelectionMode::First,
16590 window,
16591 cx,
16592 );
16593 Navigated::Yes
16594 })
16595 }))
16596 }
16597
16598 /// Opens a multibuffer with the given project locations in it
16599 pub fn open_locations_in_multibuffer(
16600 workspace: &mut Workspace,
16601 mut locations: Vec<Location>,
16602 title: String,
16603 split: bool,
16604 multibuffer_selection_mode: MultibufferSelectionMode,
16605 window: &mut Window,
16606 cx: &mut Context<Workspace>,
16607 ) {
16608 if locations.is_empty() {
16609 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16610 return;
16611 }
16612
16613 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16614
16615 let mut locations = locations.into_iter().peekable();
16616 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16617 let capability = workspace.project().read(cx).capability();
16618
16619 // a key to find existing multibuffer editors with the same set of locations
16620 // to prevent us from opening more and more multibuffer tabs for searches and the like
16621 let mut key = (title.clone(), vec![]);
16622 let excerpt_buffer = cx.new(|cx| {
16623 let key = &mut key.1;
16624 let mut multibuffer = MultiBuffer::new(capability);
16625 while let Some(location) = locations.next() {
16626 let buffer = location.buffer.read(cx);
16627 let mut ranges_for_buffer = Vec::new();
16628 let range = location.range.to_point(buffer);
16629 ranges_for_buffer.push(range.clone());
16630
16631 while let Some(next_location) =
16632 locations.next_if(|next_location| next_location.buffer == location.buffer)
16633 {
16634 ranges_for_buffer.push(next_location.range.to_point(buffer));
16635 }
16636
16637 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16638 key.push((
16639 location.buffer.read(cx).remote_id(),
16640 ranges_for_buffer.clone(),
16641 ));
16642 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16643 PathKey::for_buffer(&location.buffer, cx),
16644 location.buffer.clone(),
16645 ranges_for_buffer,
16646 multibuffer_context_lines(cx),
16647 cx,
16648 );
16649 ranges.extend(new_ranges)
16650 }
16651
16652 multibuffer.with_title(title)
16653 });
16654 let existing = workspace.active_pane().update(cx, |pane, cx| {
16655 pane.items()
16656 .filter_map(|item| item.downcast::<Editor>())
16657 .find(|editor| {
16658 editor
16659 .read(cx)
16660 .lookup_key
16661 .as_ref()
16662 .and_then(|it| {
16663 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16664 })
16665 .is_some_and(|it| *it == key)
16666 })
16667 });
16668 let editor = existing.unwrap_or_else(|| {
16669 cx.new(|cx| {
16670 let mut editor = Editor::for_multibuffer(
16671 excerpt_buffer,
16672 Some(workspace.project().clone()),
16673 window,
16674 cx,
16675 );
16676 editor.lookup_key = Some(Box::new(key));
16677 editor
16678 })
16679 });
16680 editor.update(cx, |editor, cx| {
16681 match multibuffer_selection_mode {
16682 MultibufferSelectionMode::First => {
16683 if let Some(first_range) = ranges.first() {
16684 editor.change_selections(
16685 SelectionEffects::no_scroll(),
16686 window,
16687 cx,
16688 |selections| {
16689 selections.clear_disjoint();
16690 selections
16691 .select_anchor_ranges(std::iter::once(first_range.clone()));
16692 },
16693 );
16694 }
16695 editor.highlight_background::<Self>(
16696 &ranges,
16697 |theme| theme.colors().editor_highlighted_line_background,
16698 cx,
16699 );
16700 }
16701 MultibufferSelectionMode::All => {
16702 editor.change_selections(
16703 SelectionEffects::no_scroll(),
16704 window,
16705 cx,
16706 |selections| {
16707 selections.clear_disjoint();
16708 selections.select_anchor_ranges(ranges);
16709 },
16710 );
16711 }
16712 }
16713 editor.register_buffers_with_language_servers(cx);
16714 });
16715
16716 let item = Box::new(editor);
16717 let item_id = item.item_id();
16718
16719 if split {
16720 workspace.split_item(SplitDirection::Right, item, window, cx);
16721 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16722 let (preview_item_id, preview_item_idx) =
16723 workspace.active_pane().read_with(cx, |pane, _| {
16724 (pane.preview_item_id(), pane.preview_item_idx())
16725 });
16726
16727 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16728
16729 if let Some(preview_item_id) = preview_item_id {
16730 workspace.active_pane().update(cx, |pane, cx| {
16731 pane.remove_item(preview_item_id, false, false, window, cx);
16732 });
16733 }
16734 } else {
16735 workspace.add_item_to_active_pane(item, None, true, window, cx);
16736 }
16737 workspace.active_pane().update(cx, |pane, cx| {
16738 pane.set_preview_item_id(Some(item_id), cx);
16739 });
16740 }
16741
16742 pub fn rename(
16743 &mut self,
16744 _: &Rename,
16745 window: &mut Window,
16746 cx: &mut Context<Self>,
16747 ) -> Option<Task<Result<()>>> {
16748 use language::ToOffset as _;
16749
16750 let provider = self.semantics_provider.clone()?;
16751 let selection = self.selections.newest_anchor().clone();
16752 let (cursor_buffer, cursor_buffer_position) = self
16753 .buffer
16754 .read(cx)
16755 .text_anchor_for_position(selection.head(), cx)?;
16756 let (tail_buffer, cursor_buffer_position_end) = self
16757 .buffer
16758 .read(cx)
16759 .text_anchor_for_position(selection.tail(), cx)?;
16760 if tail_buffer != cursor_buffer {
16761 return None;
16762 }
16763
16764 let snapshot = cursor_buffer.read(cx).snapshot();
16765 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16766 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16767 let prepare_rename = provider
16768 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16769 .unwrap_or_else(|| Task::ready(Ok(None)));
16770 drop(snapshot);
16771
16772 Some(cx.spawn_in(window, async move |this, cx| {
16773 let rename_range = if let Some(range) = prepare_rename.await? {
16774 Some(range)
16775 } else {
16776 this.update(cx, |this, cx| {
16777 let buffer = this.buffer.read(cx).snapshot(cx);
16778 let mut buffer_highlights = this
16779 .document_highlights_for_position(selection.head(), &buffer)
16780 .filter(|highlight| {
16781 highlight.start.excerpt_id == selection.head().excerpt_id
16782 && highlight.end.excerpt_id == selection.head().excerpt_id
16783 });
16784 buffer_highlights
16785 .next()
16786 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16787 })?
16788 };
16789 if let Some(rename_range) = rename_range {
16790 this.update_in(cx, |this, window, cx| {
16791 let snapshot = cursor_buffer.read(cx).snapshot();
16792 let rename_buffer_range = rename_range.to_offset(&snapshot);
16793 let cursor_offset_in_rename_range =
16794 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16795 let cursor_offset_in_rename_range_end =
16796 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16797
16798 this.take_rename(false, window, cx);
16799 let buffer = this.buffer.read(cx).read(cx);
16800 let cursor_offset = selection.head().to_offset(&buffer);
16801 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16802 let rename_end = rename_start + rename_buffer_range.len();
16803 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16804 let mut old_highlight_id = None;
16805 let old_name: Arc<str> = buffer
16806 .chunks(rename_start..rename_end, true)
16807 .map(|chunk| {
16808 if old_highlight_id.is_none() {
16809 old_highlight_id = chunk.syntax_highlight_id;
16810 }
16811 chunk.text
16812 })
16813 .collect::<String>()
16814 .into();
16815
16816 drop(buffer);
16817
16818 // Position the selection in the rename editor so that it matches the current selection.
16819 this.show_local_selections = false;
16820 let rename_editor = cx.new(|cx| {
16821 let mut editor = Editor::single_line(window, cx);
16822 editor.buffer.update(cx, |buffer, cx| {
16823 buffer.edit([(0..0, old_name.clone())], None, cx)
16824 });
16825 let rename_selection_range = match cursor_offset_in_rename_range
16826 .cmp(&cursor_offset_in_rename_range_end)
16827 {
16828 Ordering::Equal => {
16829 editor.select_all(&SelectAll, window, cx);
16830 return editor;
16831 }
16832 Ordering::Less => {
16833 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16834 }
16835 Ordering::Greater => {
16836 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16837 }
16838 };
16839 if rename_selection_range.end > old_name.len() {
16840 editor.select_all(&SelectAll, window, cx);
16841 } else {
16842 editor.change_selections(Default::default(), window, cx, |s| {
16843 s.select_ranges([rename_selection_range]);
16844 });
16845 }
16846 editor
16847 });
16848 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16849 if e == &EditorEvent::Focused {
16850 cx.emit(EditorEvent::FocusedIn)
16851 }
16852 })
16853 .detach();
16854
16855 let write_highlights =
16856 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16857 let read_highlights =
16858 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16859 let ranges = write_highlights
16860 .iter()
16861 .flat_map(|(_, ranges)| ranges.iter())
16862 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16863 .cloned()
16864 .collect();
16865
16866 this.highlight_text::<Rename>(
16867 ranges,
16868 HighlightStyle {
16869 fade_out: Some(0.6),
16870 ..Default::default()
16871 },
16872 cx,
16873 );
16874 let rename_focus_handle = rename_editor.focus_handle(cx);
16875 window.focus(&rename_focus_handle);
16876 let block_id = this.insert_blocks(
16877 [BlockProperties {
16878 style: BlockStyle::Flex,
16879 placement: BlockPlacement::Below(range.start),
16880 height: Some(1),
16881 render: Arc::new({
16882 let rename_editor = rename_editor.clone();
16883 move |cx: &mut BlockContext| {
16884 let mut text_style = cx.editor_style.text.clone();
16885 if let Some(highlight_style) = old_highlight_id
16886 .and_then(|h| h.style(&cx.editor_style.syntax))
16887 {
16888 text_style = text_style.highlight(highlight_style);
16889 }
16890 div()
16891 .block_mouse_except_scroll()
16892 .pl(cx.anchor_x)
16893 .child(EditorElement::new(
16894 &rename_editor,
16895 EditorStyle {
16896 background: cx.theme().system().transparent,
16897 local_player: cx.editor_style.local_player,
16898 text: text_style,
16899 scrollbar_width: cx.editor_style.scrollbar_width,
16900 syntax: cx.editor_style.syntax.clone(),
16901 status: cx.editor_style.status.clone(),
16902 inlay_hints_style: HighlightStyle {
16903 font_weight: Some(FontWeight::BOLD),
16904 ..make_inlay_hints_style(cx.app)
16905 },
16906 edit_prediction_styles: make_suggestion_styles(
16907 cx.app,
16908 ),
16909 ..EditorStyle::default()
16910 },
16911 ))
16912 .into_any_element()
16913 }
16914 }),
16915 priority: 0,
16916 }],
16917 Some(Autoscroll::fit()),
16918 cx,
16919 )[0];
16920 this.pending_rename = Some(RenameState {
16921 range,
16922 old_name,
16923 editor: rename_editor,
16924 block_id,
16925 });
16926 })?;
16927 }
16928
16929 Ok(())
16930 }))
16931 }
16932
16933 pub fn confirm_rename(
16934 &mut self,
16935 _: &ConfirmRename,
16936 window: &mut Window,
16937 cx: &mut Context<Self>,
16938 ) -> Option<Task<Result<()>>> {
16939 let rename = self.take_rename(false, window, cx)?;
16940 let workspace = self.workspace()?.downgrade();
16941 let (buffer, start) = self
16942 .buffer
16943 .read(cx)
16944 .text_anchor_for_position(rename.range.start, cx)?;
16945 let (end_buffer, _) = self
16946 .buffer
16947 .read(cx)
16948 .text_anchor_for_position(rename.range.end, cx)?;
16949 if buffer != end_buffer {
16950 return None;
16951 }
16952
16953 let old_name = rename.old_name;
16954 let new_name = rename.editor.read(cx).text(cx);
16955
16956 let rename = self.semantics_provider.as_ref()?.perform_rename(
16957 &buffer,
16958 start,
16959 new_name.clone(),
16960 cx,
16961 )?;
16962
16963 Some(cx.spawn_in(window, async move |editor, cx| {
16964 let project_transaction = rename.await?;
16965 Self::open_project_transaction(
16966 &editor,
16967 workspace,
16968 project_transaction,
16969 format!("Rename: {} → {}", old_name, new_name),
16970 cx,
16971 )
16972 .await?;
16973
16974 editor.update(cx, |editor, cx| {
16975 editor.refresh_document_highlights(cx);
16976 })?;
16977 Ok(())
16978 }))
16979 }
16980
16981 fn take_rename(
16982 &mut self,
16983 moving_cursor: bool,
16984 window: &mut Window,
16985 cx: &mut Context<Self>,
16986 ) -> Option<RenameState> {
16987 let rename = self.pending_rename.take()?;
16988 if rename.editor.focus_handle(cx).is_focused(window) {
16989 window.focus(&self.focus_handle);
16990 }
16991
16992 self.remove_blocks(
16993 [rename.block_id].into_iter().collect(),
16994 Some(Autoscroll::fit()),
16995 cx,
16996 );
16997 self.clear_highlights::<Rename>(cx);
16998 self.show_local_selections = true;
16999
17000 if moving_cursor {
17001 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17002 editor.selections.newest::<usize>(cx).head()
17003 });
17004
17005 // Update the selection to match the position of the selection inside
17006 // the rename editor.
17007 let snapshot = self.buffer.read(cx).read(cx);
17008 let rename_range = rename.range.to_offset(&snapshot);
17009 let cursor_in_editor = snapshot
17010 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17011 .min(rename_range.end);
17012 drop(snapshot);
17013
17014 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17015 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17016 });
17017 } else {
17018 self.refresh_document_highlights(cx);
17019 }
17020
17021 Some(rename)
17022 }
17023
17024 pub fn pending_rename(&self) -> Option<&RenameState> {
17025 self.pending_rename.as_ref()
17026 }
17027
17028 fn format(
17029 &mut self,
17030 _: &Format,
17031 window: &mut Window,
17032 cx: &mut Context<Self>,
17033 ) -> Option<Task<Result<()>>> {
17034 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17035
17036 let project = match &self.project {
17037 Some(project) => project.clone(),
17038 None => return None,
17039 };
17040
17041 Some(self.perform_format(
17042 project,
17043 FormatTrigger::Manual,
17044 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17045 window,
17046 cx,
17047 ))
17048 }
17049
17050 fn format_selections(
17051 &mut self,
17052 _: &FormatSelections,
17053 window: &mut Window,
17054 cx: &mut Context<Self>,
17055 ) -> Option<Task<Result<()>>> {
17056 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17057
17058 let project = match &self.project {
17059 Some(project) => project.clone(),
17060 None => return None,
17061 };
17062
17063 let ranges = self
17064 .selections
17065 .all_adjusted(cx)
17066 .into_iter()
17067 .map(|selection| selection.range())
17068 .collect_vec();
17069
17070 Some(self.perform_format(
17071 project,
17072 FormatTrigger::Manual,
17073 FormatTarget::Ranges(ranges),
17074 window,
17075 cx,
17076 ))
17077 }
17078
17079 fn perform_format(
17080 &mut self,
17081 project: Entity<Project>,
17082 trigger: FormatTrigger,
17083 target: FormatTarget,
17084 window: &mut Window,
17085 cx: &mut Context<Self>,
17086 ) -> Task<Result<()>> {
17087 let buffer = self.buffer.clone();
17088 let (buffers, target) = match target {
17089 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17090 FormatTarget::Ranges(selection_ranges) => {
17091 let multi_buffer = buffer.read(cx);
17092 let snapshot = multi_buffer.read(cx);
17093 let mut buffers = HashSet::default();
17094 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17095 BTreeMap::new();
17096 for selection_range in selection_ranges {
17097 for (buffer, buffer_range, _) in
17098 snapshot.range_to_buffer_ranges(selection_range)
17099 {
17100 let buffer_id = buffer.remote_id();
17101 let start = buffer.anchor_before(buffer_range.start);
17102 let end = buffer.anchor_after(buffer_range.end);
17103 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17104 buffer_id_to_ranges
17105 .entry(buffer_id)
17106 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17107 .or_insert_with(|| vec![start..end]);
17108 }
17109 }
17110 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17111 }
17112 };
17113
17114 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17115 let selections_prev = transaction_id_prev
17116 .and_then(|transaction_id_prev| {
17117 // default to selections as they were after the last edit, if we have them,
17118 // instead of how they are now.
17119 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17120 // will take you back to where you made the last edit, instead of staying where you scrolled
17121 self.selection_history
17122 .transaction(transaction_id_prev)
17123 .map(|t| t.0.clone())
17124 })
17125 .unwrap_or_else(|| self.selections.disjoint_anchors());
17126
17127 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17128 let format = project.update(cx, |project, cx| {
17129 project.format(buffers, target, true, trigger, cx)
17130 });
17131
17132 cx.spawn_in(window, async move |editor, cx| {
17133 let transaction = futures::select_biased! {
17134 transaction = format.log_err().fuse() => transaction,
17135 () = timeout => {
17136 log::warn!("timed out waiting for formatting");
17137 None
17138 }
17139 };
17140
17141 buffer
17142 .update(cx, |buffer, cx| {
17143 if let Some(transaction) = transaction
17144 && !buffer.is_singleton()
17145 {
17146 buffer.push_transaction(&transaction.0, cx);
17147 }
17148 cx.notify();
17149 })
17150 .ok();
17151
17152 if let Some(transaction_id_now) =
17153 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17154 {
17155 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17156 if has_new_transaction {
17157 _ = editor.update(cx, |editor, _| {
17158 editor
17159 .selection_history
17160 .insert_transaction(transaction_id_now, selections_prev);
17161 });
17162 }
17163 }
17164
17165 Ok(())
17166 })
17167 }
17168
17169 fn organize_imports(
17170 &mut self,
17171 _: &OrganizeImports,
17172 window: &mut Window,
17173 cx: &mut Context<Self>,
17174 ) -> Option<Task<Result<()>>> {
17175 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17176 let project = match &self.project {
17177 Some(project) => project.clone(),
17178 None => return None,
17179 };
17180 Some(self.perform_code_action_kind(
17181 project,
17182 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17183 window,
17184 cx,
17185 ))
17186 }
17187
17188 fn perform_code_action_kind(
17189 &mut self,
17190 project: Entity<Project>,
17191 kind: CodeActionKind,
17192 window: &mut Window,
17193 cx: &mut Context<Self>,
17194 ) -> Task<Result<()>> {
17195 let buffer = self.buffer.clone();
17196 let buffers = buffer.read(cx).all_buffers();
17197 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17198 let apply_action = project.update(cx, |project, cx| {
17199 project.apply_code_action_kind(buffers, kind, true, cx)
17200 });
17201 cx.spawn_in(window, async move |_, cx| {
17202 let transaction = futures::select_biased! {
17203 () = timeout => {
17204 log::warn!("timed out waiting for executing code action");
17205 None
17206 }
17207 transaction = apply_action.log_err().fuse() => transaction,
17208 };
17209 buffer
17210 .update(cx, |buffer, cx| {
17211 // check if we need this
17212 if let Some(transaction) = transaction
17213 && !buffer.is_singleton()
17214 {
17215 buffer.push_transaction(&transaction.0, cx);
17216 }
17217 cx.notify();
17218 })
17219 .ok();
17220 Ok(())
17221 })
17222 }
17223
17224 pub fn restart_language_server(
17225 &mut self,
17226 _: &RestartLanguageServer,
17227 _: &mut Window,
17228 cx: &mut Context<Self>,
17229 ) {
17230 if let Some(project) = self.project.clone() {
17231 self.buffer.update(cx, |multi_buffer, cx| {
17232 project.update(cx, |project, cx| {
17233 project.restart_language_servers_for_buffers(
17234 multi_buffer.all_buffers().into_iter().collect(),
17235 HashSet::default(),
17236 cx,
17237 );
17238 });
17239 })
17240 }
17241 }
17242
17243 pub fn stop_language_server(
17244 &mut self,
17245 _: &StopLanguageServer,
17246 _: &mut Window,
17247 cx: &mut Context<Self>,
17248 ) {
17249 if let Some(project) = self.project.clone() {
17250 self.buffer.update(cx, |multi_buffer, cx| {
17251 project.update(cx, |project, cx| {
17252 project.stop_language_servers_for_buffers(
17253 multi_buffer.all_buffers().into_iter().collect(),
17254 HashSet::default(),
17255 cx,
17256 );
17257 cx.emit(project::Event::RefreshInlayHints);
17258 });
17259 });
17260 }
17261 }
17262
17263 fn cancel_language_server_work(
17264 workspace: &mut Workspace,
17265 _: &actions::CancelLanguageServerWork,
17266 _: &mut Window,
17267 cx: &mut Context<Workspace>,
17268 ) {
17269 let project = workspace.project();
17270 let buffers = workspace
17271 .active_item(cx)
17272 .and_then(|item| item.act_as::<Editor>(cx))
17273 .map_or(HashSet::default(), |editor| {
17274 editor.read(cx).buffer.read(cx).all_buffers()
17275 });
17276 project.update(cx, |project, cx| {
17277 project.cancel_language_server_work_for_buffers(buffers, cx);
17278 });
17279 }
17280
17281 fn show_character_palette(
17282 &mut self,
17283 _: &ShowCharacterPalette,
17284 window: &mut Window,
17285 _: &mut Context<Self>,
17286 ) {
17287 window.show_character_palette();
17288 }
17289
17290 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17291 if !self.diagnostics_enabled() {
17292 return;
17293 }
17294
17295 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17296 let buffer = self.buffer.read(cx).snapshot(cx);
17297 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17298 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17299 let is_valid = buffer
17300 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17301 .any(|entry| {
17302 entry.diagnostic.is_primary
17303 && !entry.range.is_empty()
17304 && entry.range.start == primary_range_start
17305 && entry.diagnostic.message == active_diagnostics.active_message
17306 });
17307
17308 if !is_valid {
17309 self.dismiss_diagnostics(cx);
17310 }
17311 }
17312 }
17313
17314 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17315 match &self.active_diagnostics {
17316 ActiveDiagnostic::Group(group) => Some(group),
17317 _ => None,
17318 }
17319 }
17320
17321 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17322 if !self.diagnostics_enabled() {
17323 return;
17324 }
17325 self.dismiss_diagnostics(cx);
17326 self.active_diagnostics = ActiveDiagnostic::All;
17327 }
17328
17329 fn activate_diagnostics(
17330 &mut self,
17331 buffer_id: BufferId,
17332 diagnostic: DiagnosticEntry<usize>,
17333 window: &mut Window,
17334 cx: &mut Context<Self>,
17335 ) {
17336 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17337 return;
17338 }
17339 self.dismiss_diagnostics(cx);
17340 let snapshot = self.snapshot(window, cx);
17341 let buffer = self.buffer.read(cx).snapshot(cx);
17342 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17343 return;
17344 };
17345
17346 let diagnostic_group = buffer
17347 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17348 .collect::<Vec<_>>();
17349
17350 let blocks =
17351 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17352
17353 let blocks = self.display_map.update(cx, |display_map, cx| {
17354 display_map.insert_blocks(blocks, cx).into_iter().collect()
17355 });
17356 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17357 active_range: buffer.anchor_before(diagnostic.range.start)
17358 ..buffer.anchor_after(diagnostic.range.end),
17359 active_message: diagnostic.diagnostic.message.clone(),
17360 group_id: diagnostic.diagnostic.group_id,
17361 blocks,
17362 });
17363 cx.notify();
17364 }
17365
17366 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17367 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17368 return;
17369 };
17370
17371 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17372 if let ActiveDiagnostic::Group(group) = prev {
17373 self.display_map.update(cx, |display_map, cx| {
17374 display_map.remove_blocks(group.blocks, cx);
17375 });
17376 cx.notify();
17377 }
17378 }
17379
17380 /// Disable inline diagnostics rendering for this editor.
17381 pub fn disable_inline_diagnostics(&mut self) {
17382 self.inline_diagnostics_enabled = false;
17383 self.inline_diagnostics_update = Task::ready(());
17384 self.inline_diagnostics.clear();
17385 }
17386
17387 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17388 self.diagnostics_enabled = false;
17389 self.dismiss_diagnostics(cx);
17390 self.inline_diagnostics_update = Task::ready(());
17391 self.inline_diagnostics.clear();
17392 }
17393
17394 pub fn disable_word_completions(&mut self) {
17395 self.word_completions_enabled = false;
17396 }
17397
17398 pub fn diagnostics_enabled(&self) -> bool {
17399 self.diagnostics_enabled && self.mode.is_full()
17400 }
17401
17402 pub fn inline_diagnostics_enabled(&self) -> bool {
17403 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17404 }
17405
17406 pub fn show_inline_diagnostics(&self) -> bool {
17407 self.show_inline_diagnostics
17408 }
17409
17410 pub fn toggle_inline_diagnostics(
17411 &mut self,
17412 _: &ToggleInlineDiagnostics,
17413 window: &mut Window,
17414 cx: &mut Context<Editor>,
17415 ) {
17416 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17417 self.refresh_inline_diagnostics(false, window, cx);
17418 }
17419
17420 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17421 self.diagnostics_max_severity = severity;
17422 self.display_map.update(cx, |display_map, _| {
17423 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17424 });
17425 }
17426
17427 pub fn toggle_diagnostics(
17428 &mut self,
17429 _: &ToggleDiagnostics,
17430 window: &mut Window,
17431 cx: &mut Context<Editor>,
17432 ) {
17433 if !self.diagnostics_enabled() {
17434 return;
17435 }
17436
17437 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17438 EditorSettings::get_global(cx)
17439 .diagnostics_max_severity
17440 .filter(|severity| severity != &DiagnosticSeverity::Off)
17441 .unwrap_or(DiagnosticSeverity::Hint)
17442 } else {
17443 DiagnosticSeverity::Off
17444 };
17445 self.set_max_diagnostics_severity(new_severity, cx);
17446 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17447 self.active_diagnostics = ActiveDiagnostic::None;
17448 self.inline_diagnostics_update = Task::ready(());
17449 self.inline_diagnostics.clear();
17450 } else {
17451 self.refresh_inline_diagnostics(false, window, cx);
17452 }
17453
17454 cx.notify();
17455 }
17456
17457 pub fn toggle_minimap(
17458 &mut self,
17459 _: &ToggleMinimap,
17460 window: &mut Window,
17461 cx: &mut Context<Editor>,
17462 ) {
17463 if self.supports_minimap(cx) {
17464 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17465 }
17466 }
17467
17468 fn refresh_inline_diagnostics(
17469 &mut self,
17470 debounce: bool,
17471 window: &mut Window,
17472 cx: &mut Context<Self>,
17473 ) {
17474 let max_severity = ProjectSettings::get_global(cx)
17475 .diagnostics
17476 .inline
17477 .max_severity
17478 .unwrap_or(self.diagnostics_max_severity);
17479
17480 if !self.inline_diagnostics_enabled()
17481 || !self.show_inline_diagnostics
17482 || max_severity == DiagnosticSeverity::Off
17483 {
17484 self.inline_diagnostics_update = Task::ready(());
17485 self.inline_diagnostics.clear();
17486 return;
17487 }
17488
17489 let debounce_ms = ProjectSettings::get_global(cx)
17490 .diagnostics
17491 .inline
17492 .update_debounce_ms;
17493 let debounce = if debounce && debounce_ms > 0 {
17494 Some(Duration::from_millis(debounce_ms))
17495 } else {
17496 None
17497 };
17498 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17499 if let Some(debounce) = debounce {
17500 cx.background_executor().timer(debounce).await;
17501 }
17502 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17503 editor
17504 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17505 .ok()
17506 }) else {
17507 return;
17508 };
17509
17510 let new_inline_diagnostics = cx
17511 .background_spawn(async move {
17512 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17513 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17514 let message = diagnostic_entry
17515 .diagnostic
17516 .message
17517 .split_once('\n')
17518 .map(|(line, _)| line)
17519 .map(SharedString::new)
17520 .unwrap_or_else(|| {
17521 SharedString::from(diagnostic_entry.diagnostic.message)
17522 });
17523 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17524 let (Ok(i) | Err(i)) = inline_diagnostics
17525 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17526 inline_diagnostics.insert(
17527 i,
17528 (
17529 start_anchor,
17530 InlineDiagnostic {
17531 message,
17532 group_id: diagnostic_entry.diagnostic.group_id,
17533 start: diagnostic_entry.range.start.to_point(&snapshot),
17534 is_primary: diagnostic_entry.diagnostic.is_primary,
17535 severity: diagnostic_entry.diagnostic.severity,
17536 },
17537 ),
17538 );
17539 }
17540 inline_diagnostics
17541 })
17542 .await;
17543
17544 editor
17545 .update(cx, |editor, cx| {
17546 editor.inline_diagnostics = new_inline_diagnostics;
17547 cx.notify();
17548 })
17549 .ok();
17550 });
17551 }
17552
17553 fn pull_diagnostics(
17554 &mut self,
17555 buffer_id: Option<BufferId>,
17556 window: &Window,
17557 cx: &mut Context<Self>,
17558 ) -> Option<()> {
17559 if !self.mode().is_full() {
17560 return None;
17561 }
17562 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17563 .diagnostics
17564 .lsp_pull_diagnostics;
17565 if !pull_diagnostics_settings.enabled {
17566 return None;
17567 }
17568 let project = self.project()?.downgrade();
17569 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17570 let mut buffers = self.buffer.read(cx).all_buffers();
17571 if let Some(buffer_id) = buffer_id {
17572 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17573 }
17574
17575 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17576 cx.background_executor().timer(debounce).await;
17577
17578 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17579 buffers
17580 .into_iter()
17581 .filter_map(|buffer| {
17582 project
17583 .update(cx, |project, cx| {
17584 project.lsp_store().update(cx, |lsp_store, cx| {
17585 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17586 })
17587 })
17588 .ok()
17589 })
17590 .collect::<FuturesUnordered<_>>()
17591 }) else {
17592 return;
17593 };
17594
17595 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17596 match pull_task {
17597 Ok(()) => {
17598 if editor
17599 .update_in(cx, |editor, window, cx| {
17600 editor.update_diagnostics_state(window, cx);
17601 })
17602 .is_err()
17603 {
17604 return;
17605 }
17606 }
17607 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17608 }
17609 }
17610 });
17611
17612 Some(())
17613 }
17614
17615 pub fn set_selections_from_remote(
17616 &mut self,
17617 selections: Vec<Selection<Anchor>>,
17618 pending_selection: Option<Selection<Anchor>>,
17619 window: &mut Window,
17620 cx: &mut Context<Self>,
17621 ) {
17622 let old_cursor_position = self.selections.newest_anchor().head();
17623 self.selections.change_with(cx, |s| {
17624 s.select_anchors(selections);
17625 if let Some(pending_selection) = pending_selection {
17626 s.set_pending(pending_selection, SelectMode::Character);
17627 } else {
17628 s.clear_pending();
17629 }
17630 });
17631 self.selections_did_change(
17632 false,
17633 &old_cursor_position,
17634 SelectionEffects::default(),
17635 window,
17636 cx,
17637 );
17638 }
17639
17640 pub fn transact(
17641 &mut self,
17642 window: &mut Window,
17643 cx: &mut Context<Self>,
17644 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17645 ) -> Option<TransactionId> {
17646 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17647 this.start_transaction_at(Instant::now(), window, cx);
17648 update(this, window, cx);
17649 this.end_transaction_at(Instant::now(), cx)
17650 })
17651 }
17652
17653 pub fn start_transaction_at(
17654 &mut self,
17655 now: Instant,
17656 window: &mut Window,
17657 cx: &mut Context<Self>,
17658 ) -> Option<TransactionId> {
17659 self.end_selection(window, cx);
17660 if let Some(tx_id) = self
17661 .buffer
17662 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17663 {
17664 self.selection_history
17665 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17666 cx.emit(EditorEvent::TransactionBegun {
17667 transaction_id: tx_id,
17668 });
17669 Some(tx_id)
17670 } else {
17671 None
17672 }
17673 }
17674
17675 pub fn end_transaction_at(
17676 &mut self,
17677 now: Instant,
17678 cx: &mut Context<Self>,
17679 ) -> Option<TransactionId> {
17680 if let Some(transaction_id) = self
17681 .buffer
17682 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17683 {
17684 if let Some((_, end_selections)) =
17685 self.selection_history.transaction_mut(transaction_id)
17686 {
17687 *end_selections = Some(self.selections.disjoint_anchors());
17688 } else {
17689 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17690 }
17691
17692 cx.emit(EditorEvent::Edited { transaction_id });
17693 Some(transaction_id)
17694 } else {
17695 None
17696 }
17697 }
17698
17699 pub fn modify_transaction_selection_history(
17700 &mut self,
17701 transaction_id: TransactionId,
17702 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17703 ) -> bool {
17704 self.selection_history
17705 .transaction_mut(transaction_id)
17706 .map(modify)
17707 .is_some()
17708 }
17709
17710 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17711 if self.selection_mark_mode {
17712 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17713 s.move_with(|_, sel| {
17714 sel.collapse_to(sel.head(), SelectionGoal::None);
17715 });
17716 })
17717 }
17718 self.selection_mark_mode = true;
17719 cx.notify();
17720 }
17721
17722 pub fn swap_selection_ends(
17723 &mut self,
17724 _: &actions::SwapSelectionEnds,
17725 window: &mut Window,
17726 cx: &mut Context<Self>,
17727 ) {
17728 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17729 s.move_with(|_, sel| {
17730 if sel.start != sel.end {
17731 sel.reversed = !sel.reversed
17732 }
17733 });
17734 });
17735 self.request_autoscroll(Autoscroll::newest(), cx);
17736 cx.notify();
17737 }
17738
17739 pub fn toggle_focus(
17740 workspace: &mut Workspace,
17741 _: &actions::ToggleFocus,
17742 window: &mut Window,
17743 cx: &mut Context<Workspace>,
17744 ) {
17745 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17746 return;
17747 };
17748 workspace.activate_item(&item, true, true, window, cx);
17749 }
17750
17751 pub fn toggle_fold(
17752 &mut self,
17753 _: &actions::ToggleFold,
17754 window: &mut Window,
17755 cx: &mut Context<Self>,
17756 ) {
17757 if self.is_singleton(cx) {
17758 let selection = self.selections.newest::<Point>(cx);
17759
17760 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17761 let range = if selection.is_empty() {
17762 let point = selection.head().to_display_point(&display_map);
17763 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17764 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17765 .to_point(&display_map);
17766 start..end
17767 } else {
17768 selection.range()
17769 };
17770 if display_map.folds_in_range(range).next().is_some() {
17771 self.unfold_lines(&Default::default(), window, cx)
17772 } else {
17773 self.fold(&Default::default(), window, cx)
17774 }
17775 } else {
17776 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17777 let buffer_ids: HashSet<_> = self
17778 .selections
17779 .disjoint_anchor_ranges()
17780 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17781 .collect();
17782
17783 let should_unfold = buffer_ids
17784 .iter()
17785 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17786
17787 for buffer_id in buffer_ids {
17788 if should_unfold {
17789 self.unfold_buffer(buffer_id, cx);
17790 } else {
17791 self.fold_buffer(buffer_id, cx);
17792 }
17793 }
17794 }
17795 }
17796
17797 pub fn toggle_fold_recursive(
17798 &mut self,
17799 _: &actions::ToggleFoldRecursive,
17800 window: &mut Window,
17801 cx: &mut Context<Self>,
17802 ) {
17803 let selection = self.selections.newest::<Point>(cx);
17804
17805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17806 let range = if selection.is_empty() {
17807 let point = selection.head().to_display_point(&display_map);
17808 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17809 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17810 .to_point(&display_map);
17811 start..end
17812 } else {
17813 selection.range()
17814 };
17815 if display_map.folds_in_range(range).next().is_some() {
17816 self.unfold_recursive(&Default::default(), window, cx)
17817 } else {
17818 self.fold_recursive(&Default::default(), window, cx)
17819 }
17820 }
17821
17822 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17823 if self.is_singleton(cx) {
17824 let mut to_fold = Vec::new();
17825 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17826 let selections = self.selections.all_adjusted(cx);
17827
17828 for selection in selections {
17829 let range = selection.range().sorted();
17830 let buffer_start_row = range.start.row;
17831
17832 if range.start.row != range.end.row {
17833 let mut found = false;
17834 let mut row = range.start.row;
17835 while row <= range.end.row {
17836 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17837 {
17838 found = true;
17839 row = crease.range().end.row + 1;
17840 to_fold.push(crease);
17841 } else {
17842 row += 1
17843 }
17844 }
17845 if found {
17846 continue;
17847 }
17848 }
17849
17850 for row in (0..=range.start.row).rev() {
17851 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17852 && crease.range().end.row >= buffer_start_row
17853 {
17854 to_fold.push(crease);
17855 if row <= range.start.row {
17856 break;
17857 }
17858 }
17859 }
17860 }
17861
17862 self.fold_creases(to_fold, true, window, cx);
17863 } else {
17864 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17865 let buffer_ids = self
17866 .selections
17867 .disjoint_anchor_ranges()
17868 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17869 .collect::<HashSet<_>>();
17870 for buffer_id in buffer_ids {
17871 self.fold_buffer(buffer_id, cx);
17872 }
17873 }
17874 }
17875
17876 pub fn toggle_fold_all(
17877 &mut self,
17878 _: &actions::ToggleFoldAll,
17879 window: &mut Window,
17880 cx: &mut Context<Self>,
17881 ) {
17882 if self.buffer.read(cx).is_singleton() {
17883 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17884 let has_folds = display_map
17885 .folds_in_range(0..display_map.buffer_snapshot.len())
17886 .next()
17887 .is_some();
17888
17889 if has_folds {
17890 self.unfold_all(&actions::UnfoldAll, window, cx);
17891 } else {
17892 self.fold_all(&actions::FoldAll, window, cx);
17893 }
17894 } else {
17895 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17896 let should_unfold = buffer_ids
17897 .iter()
17898 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17899
17900 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17901 editor
17902 .update_in(cx, |editor, _, cx| {
17903 for buffer_id in buffer_ids {
17904 if should_unfold {
17905 editor.unfold_buffer(buffer_id, cx);
17906 } else {
17907 editor.fold_buffer(buffer_id, cx);
17908 }
17909 }
17910 })
17911 .ok();
17912 });
17913 }
17914 }
17915
17916 fn fold_at_level(
17917 &mut self,
17918 fold_at: &FoldAtLevel,
17919 window: &mut Window,
17920 cx: &mut Context<Self>,
17921 ) {
17922 if !self.buffer.read(cx).is_singleton() {
17923 return;
17924 }
17925
17926 let fold_at_level = fold_at.0;
17927 let snapshot = self.buffer.read(cx).snapshot(cx);
17928 let mut to_fold = Vec::new();
17929 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17930
17931 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17932 while start_row < end_row {
17933 match self
17934 .snapshot(window, cx)
17935 .crease_for_buffer_row(MultiBufferRow(start_row))
17936 {
17937 Some(crease) => {
17938 let nested_start_row = crease.range().start.row + 1;
17939 let nested_end_row = crease.range().end.row;
17940
17941 if current_level < fold_at_level {
17942 stack.push((nested_start_row, nested_end_row, current_level + 1));
17943 } else if current_level == fold_at_level {
17944 to_fold.push(crease);
17945 }
17946
17947 start_row = nested_end_row + 1;
17948 }
17949 None => start_row += 1,
17950 }
17951 }
17952 }
17953
17954 self.fold_creases(to_fold, true, window, cx);
17955 }
17956
17957 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17958 if self.buffer.read(cx).is_singleton() {
17959 let mut fold_ranges = Vec::new();
17960 let snapshot = self.buffer.read(cx).snapshot(cx);
17961
17962 for row in 0..snapshot.max_row().0 {
17963 if let Some(foldable_range) = self
17964 .snapshot(window, cx)
17965 .crease_for_buffer_row(MultiBufferRow(row))
17966 {
17967 fold_ranges.push(foldable_range);
17968 }
17969 }
17970
17971 self.fold_creases(fold_ranges, true, window, cx);
17972 } else {
17973 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17974 editor
17975 .update_in(cx, |editor, _, cx| {
17976 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17977 editor.fold_buffer(buffer_id, cx);
17978 }
17979 })
17980 .ok();
17981 });
17982 }
17983 }
17984
17985 pub fn fold_function_bodies(
17986 &mut self,
17987 _: &actions::FoldFunctionBodies,
17988 window: &mut Window,
17989 cx: &mut Context<Self>,
17990 ) {
17991 let snapshot = self.buffer.read(cx).snapshot(cx);
17992
17993 let ranges = snapshot
17994 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17995 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17996 .collect::<Vec<_>>();
17997
17998 let creases = ranges
17999 .into_iter()
18000 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18001 .collect();
18002
18003 self.fold_creases(creases, true, window, cx);
18004 }
18005
18006 pub fn fold_recursive(
18007 &mut self,
18008 _: &actions::FoldRecursive,
18009 window: &mut Window,
18010 cx: &mut Context<Self>,
18011 ) {
18012 let mut to_fold = Vec::new();
18013 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18014 let selections = self.selections.all_adjusted(cx);
18015
18016 for selection in selections {
18017 let range = selection.range().sorted();
18018 let buffer_start_row = range.start.row;
18019
18020 if range.start.row != range.end.row {
18021 let mut found = false;
18022 for row in range.start.row..=range.end.row {
18023 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18024 found = true;
18025 to_fold.push(crease);
18026 }
18027 }
18028 if found {
18029 continue;
18030 }
18031 }
18032
18033 for row in (0..=range.start.row).rev() {
18034 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18035 if crease.range().end.row >= buffer_start_row {
18036 to_fold.push(crease);
18037 } else {
18038 break;
18039 }
18040 }
18041 }
18042 }
18043
18044 self.fold_creases(to_fold, true, window, cx);
18045 }
18046
18047 pub fn fold_at(
18048 &mut self,
18049 buffer_row: MultiBufferRow,
18050 window: &mut Window,
18051 cx: &mut Context<Self>,
18052 ) {
18053 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18054
18055 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18056 let autoscroll = self
18057 .selections
18058 .all::<Point>(cx)
18059 .iter()
18060 .any(|selection| crease.range().overlaps(&selection.range()));
18061
18062 self.fold_creases(vec![crease], autoscroll, window, cx);
18063 }
18064 }
18065
18066 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18067 if self.is_singleton(cx) {
18068 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18069 let buffer = &display_map.buffer_snapshot;
18070 let selections = self.selections.all::<Point>(cx);
18071 let ranges = selections
18072 .iter()
18073 .map(|s| {
18074 let range = s.display_range(&display_map).sorted();
18075 let mut start = range.start.to_point(&display_map);
18076 let mut end = range.end.to_point(&display_map);
18077 start.column = 0;
18078 end.column = buffer.line_len(MultiBufferRow(end.row));
18079 start..end
18080 })
18081 .collect::<Vec<_>>();
18082
18083 self.unfold_ranges(&ranges, true, true, cx);
18084 } else {
18085 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18086 let buffer_ids = self
18087 .selections
18088 .disjoint_anchor_ranges()
18089 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18090 .collect::<HashSet<_>>();
18091 for buffer_id in buffer_ids {
18092 self.unfold_buffer(buffer_id, cx);
18093 }
18094 }
18095 }
18096
18097 pub fn unfold_recursive(
18098 &mut self,
18099 _: &UnfoldRecursive,
18100 _window: &mut Window,
18101 cx: &mut Context<Self>,
18102 ) {
18103 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18104 let selections = self.selections.all::<Point>(cx);
18105 let ranges = selections
18106 .iter()
18107 .map(|s| {
18108 let mut range = s.display_range(&display_map).sorted();
18109 *range.start.column_mut() = 0;
18110 *range.end.column_mut() = display_map.line_len(range.end.row());
18111 let start = range.start.to_point(&display_map);
18112 let end = range.end.to_point(&display_map);
18113 start..end
18114 })
18115 .collect::<Vec<_>>();
18116
18117 self.unfold_ranges(&ranges, true, true, cx);
18118 }
18119
18120 pub fn unfold_at(
18121 &mut self,
18122 buffer_row: MultiBufferRow,
18123 _window: &mut Window,
18124 cx: &mut Context<Self>,
18125 ) {
18126 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18127
18128 let intersection_range = Point::new(buffer_row.0, 0)
18129 ..Point::new(
18130 buffer_row.0,
18131 display_map.buffer_snapshot.line_len(buffer_row),
18132 );
18133
18134 let autoscroll = self
18135 .selections
18136 .all::<Point>(cx)
18137 .iter()
18138 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18139
18140 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18141 }
18142
18143 pub fn unfold_all(
18144 &mut self,
18145 _: &actions::UnfoldAll,
18146 _window: &mut Window,
18147 cx: &mut Context<Self>,
18148 ) {
18149 if self.buffer.read(cx).is_singleton() {
18150 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18151 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18152 } else {
18153 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18154 editor
18155 .update(cx, |editor, cx| {
18156 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18157 editor.unfold_buffer(buffer_id, cx);
18158 }
18159 })
18160 .ok();
18161 });
18162 }
18163 }
18164
18165 pub fn fold_selected_ranges(
18166 &mut self,
18167 _: &FoldSelectedRanges,
18168 window: &mut Window,
18169 cx: &mut Context<Self>,
18170 ) {
18171 let selections = self.selections.all_adjusted(cx);
18172 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18173 let ranges = selections
18174 .into_iter()
18175 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18176 .collect::<Vec<_>>();
18177 self.fold_creases(ranges, true, window, cx);
18178 }
18179
18180 pub fn fold_ranges<T: ToOffset + Clone>(
18181 &mut self,
18182 ranges: Vec<Range<T>>,
18183 auto_scroll: bool,
18184 window: &mut Window,
18185 cx: &mut Context<Self>,
18186 ) {
18187 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18188 let ranges = ranges
18189 .into_iter()
18190 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18191 .collect::<Vec<_>>();
18192 self.fold_creases(ranges, auto_scroll, window, cx);
18193 }
18194
18195 pub fn fold_creases<T: ToOffset + Clone>(
18196 &mut self,
18197 creases: Vec<Crease<T>>,
18198 auto_scroll: bool,
18199 _window: &mut Window,
18200 cx: &mut Context<Self>,
18201 ) {
18202 if creases.is_empty() {
18203 return;
18204 }
18205
18206 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18207
18208 if auto_scroll {
18209 self.request_autoscroll(Autoscroll::fit(), cx);
18210 }
18211
18212 cx.notify();
18213
18214 self.scrollbar_marker_state.dirty = true;
18215 self.folds_did_change(cx);
18216 }
18217
18218 /// Removes any folds whose ranges intersect any of the given ranges.
18219 pub fn unfold_ranges<T: ToOffset + Clone>(
18220 &mut self,
18221 ranges: &[Range<T>],
18222 inclusive: bool,
18223 auto_scroll: bool,
18224 cx: &mut Context<Self>,
18225 ) {
18226 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18227 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18228 });
18229 self.folds_did_change(cx);
18230 }
18231
18232 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18233 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18234 return;
18235 }
18236 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18237 self.display_map.update(cx, |display_map, cx| {
18238 display_map.fold_buffers([buffer_id], cx)
18239 });
18240 cx.emit(EditorEvent::BufferFoldToggled {
18241 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18242 folded: true,
18243 });
18244 cx.notify();
18245 }
18246
18247 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18248 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18249 return;
18250 }
18251 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18252 self.display_map.update(cx, |display_map, cx| {
18253 display_map.unfold_buffers([buffer_id], cx);
18254 });
18255 cx.emit(EditorEvent::BufferFoldToggled {
18256 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18257 folded: false,
18258 });
18259 cx.notify();
18260 }
18261
18262 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18263 self.display_map.read(cx).is_buffer_folded(buffer)
18264 }
18265
18266 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18267 self.display_map.read(cx).folded_buffers()
18268 }
18269
18270 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18271 self.display_map.update(cx, |display_map, cx| {
18272 display_map.disable_header_for_buffer(buffer_id, cx);
18273 });
18274 cx.notify();
18275 }
18276
18277 /// Removes any folds with the given ranges.
18278 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18279 &mut self,
18280 ranges: &[Range<T>],
18281 type_id: TypeId,
18282 auto_scroll: bool,
18283 cx: &mut Context<Self>,
18284 ) {
18285 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18286 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18287 });
18288 self.folds_did_change(cx);
18289 }
18290
18291 fn remove_folds_with<T: ToOffset + Clone>(
18292 &mut self,
18293 ranges: &[Range<T>],
18294 auto_scroll: bool,
18295 cx: &mut Context<Self>,
18296 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18297 ) {
18298 if ranges.is_empty() {
18299 return;
18300 }
18301
18302 let mut buffers_affected = HashSet::default();
18303 let multi_buffer = self.buffer().read(cx);
18304 for range in ranges {
18305 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18306 buffers_affected.insert(buffer.read(cx).remote_id());
18307 };
18308 }
18309
18310 self.display_map.update(cx, update);
18311
18312 if auto_scroll {
18313 self.request_autoscroll(Autoscroll::fit(), cx);
18314 }
18315
18316 cx.notify();
18317 self.scrollbar_marker_state.dirty = true;
18318 self.active_indent_guides_state.dirty = true;
18319 }
18320
18321 pub fn update_renderer_widths(
18322 &mut self,
18323 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18324 cx: &mut Context<Self>,
18325 ) -> bool {
18326 self.display_map
18327 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18328 }
18329
18330 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18331 self.display_map.read(cx).fold_placeholder.clone()
18332 }
18333
18334 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18335 self.buffer.update(cx, |buffer, cx| {
18336 buffer.set_all_diff_hunks_expanded(cx);
18337 });
18338 }
18339
18340 pub fn expand_all_diff_hunks(
18341 &mut self,
18342 _: &ExpandAllDiffHunks,
18343 _window: &mut Window,
18344 cx: &mut Context<Self>,
18345 ) {
18346 self.buffer.update(cx, |buffer, cx| {
18347 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18348 });
18349 }
18350
18351 pub fn toggle_selected_diff_hunks(
18352 &mut self,
18353 _: &ToggleSelectedDiffHunks,
18354 _window: &mut Window,
18355 cx: &mut Context<Self>,
18356 ) {
18357 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18358 self.toggle_diff_hunks_in_ranges(ranges, cx);
18359 }
18360
18361 pub fn diff_hunks_in_ranges<'a>(
18362 &'a self,
18363 ranges: &'a [Range<Anchor>],
18364 buffer: &'a MultiBufferSnapshot,
18365 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18366 ranges.iter().flat_map(move |range| {
18367 let end_excerpt_id = range.end.excerpt_id;
18368 let range = range.to_point(buffer);
18369 let mut peek_end = range.end;
18370 if range.end.row < buffer.max_row().0 {
18371 peek_end = Point::new(range.end.row + 1, 0);
18372 }
18373 buffer
18374 .diff_hunks_in_range(range.start..peek_end)
18375 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18376 })
18377 }
18378
18379 pub fn has_stageable_diff_hunks_in_ranges(
18380 &self,
18381 ranges: &[Range<Anchor>],
18382 snapshot: &MultiBufferSnapshot,
18383 ) -> bool {
18384 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18385 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18386 }
18387
18388 pub fn toggle_staged_selected_diff_hunks(
18389 &mut self,
18390 _: &::git::ToggleStaged,
18391 _: &mut Window,
18392 cx: &mut Context<Self>,
18393 ) {
18394 let snapshot = self.buffer.read(cx).snapshot(cx);
18395 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18396 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18397 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18398 }
18399
18400 pub fn set_render_diff_hunk_controls(
18401 &mut self,
18402 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18403 cx: &mut Context<Self>,
18404 ) {
18405 self.render_diff_hunk_controls = render_diff_hunk_controls;
18406 cx.notify();
18407 }
18408
18409 pub fn stage_and_next(
18410 &mut self,
18411 _: &::git::StageAndNext,
18412 window: &mut Window,
18413 cx: &mut Context<Self>,
18414 ) {
18415 self.do_stage_or_unstage_and_next(true, window, cx);
18416 }
18417
18418 pub fn unstage_and_next(
18419 &mut self,
18420 _: &::git::UnstageAndNext,
18421 window: &mut Window,
18422 cx: &mut Context<Self>,
18423 ) {
18424 self.do_stage_or_unstage_and_next(false, window, cx);
18425 }
18426
18427 pub fn stage_or_unstage_diff_hunks(
18428 &mut self,
18429 stage: bool,
18430 ranges: Vec<Range<Anchor>>,
18431 cx: &mut Context<Self>,
18432 ) {
18433 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18434 cx.spawn(async move |this, cx| {
18435 task.await?;
18436 this.update(cx, |this, cx| {
18437 let snapshot = this.buffer.read(cx).snapshot(cx);
18438 let chunk_by = this
18439 .diff_hunks_in_ranges(&ranges, &snapshot)
18440 .chunk_by(|hunk| hunk.buffer_id);
18441 for (buffer_id, hunks) in &chunk_by {
18442 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18443 }
18444 })
18445 })
18446 .detach_and_log_err(cx);
18447 }
18448
18449 fn save_buffers_for_ranges_if_needed(
18450 &mut self,
18451 ranges: &[Range<Anchor>],
18452 cx: &mut Context<Editor>,
18453 ) -> Task<Result<()>> {
18454 let multibuffer = self.buffer.read(cx);
18455 let snapshot = multibuffer.read(cx);
18456 let buffer_ids: HashSet<_> = ranges
18457 .iter()
18458 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18459 .collect();
18460 drop(snapshot);
18461
18462 let mut buffers = HashSet::default();
18463 for buffer_id in buffer_ids {
18464 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18465 let buffer = buffer_entity.read(cx);
18466 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18467 {
18468 buffers.insert(buffer_entity);
18469 }
18470 }
18471 }
18472
18473 if let Some(project) = &self.project {
18474 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18475 } else {
18476 Task::ready(Ok(()))
18477 }
18478 }
18479
18480 fn do_stage_or_unstage_and_next(
18481 &mut self,
18482 stage: bool,
18483 window: &mut Window,
18484 cx: &mut Context<Self>,
18485 ) {
18486 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18487
18488 if ranges.iter().any(|range| range.start != range.end) {
18489 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18490 return;
18491 }
18492
18493 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18494 let snapshot = self.snapshot(window, cx);
18495 let position = self.selections.newest::<Point>(cx).head();
18496 let mut row = snapshot
18497 .buffer_snapshot
18498 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18499 .find(|hunk| hunk.row_range.start.0 > position.row)
18500 .map(|hunk| hunk.row_range.start);
18501
18502 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18503 // Outside of the project diff editor, wrap around to the beginning.
18504 if !all_diff_hunks_expanded {
18505 row = row.or_else(|| {
18506 snapshot
18507 .buffer_snapshot
18508 .diff_hunks_in_range(Point::zero()..position)
18509 .find(|hunk| hunk.row_range.end.0 < position.row)
18510 .map(|hunk| hunk.row_range.start)
18511 });
18512 }
18513
18514 if let Some(row) = row {
18515 let destination = Point::new(row.0, 0);
18516 let autoscroll = Autoscroll::center();
18517
18518 self.unfold_ranges(&[destination..destination], false, false, cx);
18519 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18520 s.select_ranges([destination..destination]);
18521 });
18522 }
18523 }
18524
18525 fn do_stage_or_unstage(
18526 &self,
18527 stage: bool,
18528 buffer_id: BufferId,
18529 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18530 cx: &mut App,
18531 ) -> Option<()> {
18532 let project = self.project()?;
18533 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18534 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18535 let buffer_snapshot = buffer.read(cx).snapshot();
18536 let file_exists = buffer_snapshot
18537 .file()
18538 .is_some_and(|file| file.disk_state().exists());
18539 diff.update(cx, |diff, cx| {
18540 diff.stage_or_unstage_hunks(
18541 stage,
18542 &hunks
18543 .map(|hunk| buffer_diff::DiffHunk {
18544 buffer_range: hunk.buffer_range,
18545 diff_base_byte_range: hunk.diff_base_byte_range,
18546 secondary_status: hunk.secondary_status,
18547 range: Point::zero()..Point::zero(), // unused
18548 })
18549 .collect::<Vec<_>>(),
18550 &buffer_snapshot,
18551 file_exists,
18552 cx,
18553 )
18554 });
18555 None
18556 }
18557
18558 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18559 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18560 self.buffer
18561 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18562 }
18563
18564 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18565 self.buffer.update(cx, |buffer, cx| {
18566 let ranges = vec![Anchor::min()..Anchor::max()];
18567 if !buffer.all_diff_hunks_expanded()
18568 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18569 {
18570 buffer.collapse_diff_hunks(ranges, cx);
18571 true
18572 } else {
18573 false
18574 }
18575 })
18576 }
18577
18578 fn toggle_diff_hunks_in_ranges(
18579 &mut self,
18580 ranges: Vec<Range<Anchor>>,
18581 cx: &mut Context<Editor>,
18582 ) {
18583 self.buffer.update(cx, |buffer, cx| {
18584 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18585 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18586 })
18587 }
18588
18589 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18590 self.buffer.update(cx, |buffer, cx| {
18591 let snapshot = buffer.snapshot(cx);
18592 let excerpt_id = range.end.excerpt_id;
18593 let point_range = range.to_point(&snapshot);
18594 let expand = !buffer.single_hunk_is_expanded(range, cx);
18595 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18596 })
18597 }
18598
18599 pub(crate) fn apply_all_diff_hunks(
18600 &mut self,
18601 _: &ApplyAllDiffHunks,
18602 window: &mut Window,
18603 cx: &mut Context<Self>,
18604 ) {
18605 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18606
18607 let buffers = self.buffer.read(cx).all_buffers();
18608 for branch_buffer in buffers {
18609 branch_buffer.update(cx, |branch_buffer, cx| {
18610 branch_buffer.merge_into_base(Vec::new(), cx);
18611 });
18612 }
18613
18614 if let Some(project) = self.project.clone() {
18615 self.save(
18616 SaveOptions {
18617 format: true,
18618 autosave: false,
18619 },
18620 project,
18621 window,
18622 cx,
18623 )
18624 .detach_and_log_err(cx);
18625 }
18626 }
18627
18628 pub(crate) fn apply_selected_diff_hunks(
18629 &mut self,
18630 _: &ApplyDiffHunk,
18631 window: &mut Window,
18632 cx: &mut Context<Self>,
18633 ) {
18634 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18635 let snapshot = self.snapshot(window, cx);
18636 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18637 let mut ranges_by_buffer = HashMap::default();
18638 self.transact(window, cx, |editor, _window, cx| {
18639 for hunk in hunks {
18640 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18641 ranges_by_buffer
18642 .entry(buffer.clone())
18643 .or_insert_with(Vec::new)
18644 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18645 }
18646 }
18647
18648 for (buffer, ranges) in ranges_by_buffer {
18649 buffer.update(cx, |buffer, cx| {
18650 buffer.merge_into_base(ranges, cx);
18651 });
18652 }
18653 });
18654
18655 if let Some(project) = self.project.clone() {
18656 self.save(
18657 SaveOptions {
18658 format: true,
18659 autosave: false,
18660 },
18661 project,
18662 window,
18663 cx,
18664 )
18665 .detach_and_log_err(cx);
18666 }
18667 }
18668
18669 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18670 if hovered != self.gutter_hovered {
18671 self.gutter_hovered = hovered;
18672 cx.notify();
18673 }
18674 }
18675
18676 pub fn insert_blocks(
18677 &mut self,
18678 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18679 autoscroll: Option<Autoscroll>,
18680 cx: &mut Context<Self>,
18681 ) -> Vec<CustomBlockId> {
18682 let blocks = self
18683 .display_map
18684 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18685 if let Some(autoscroll) = autoscroll {
18686 self.request_autoscroll(autoscroll, cx);
18687 }
18688 cx.notify();
18689 blocks
18690 }
18691
18692 pub fn resize_blocks(
18693 &mut self,
18694 heights: HashMap<CustomBlockId, u32>,
18695 autoscroll: Option<Autoscroll>,
18696 cx: &mut Context<Self>,
18697 ) {
18698 self.display_map
18699 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18700 if let Some(autoscroll) = autoscroll {
18701 self.request_autoscroll(autoscroll, cx);
18702 }
18703 cx.notify();
18704 }
18705
18706 pub fn replace_blocks(
18707 &mut self,
18708 renderers: HashMap<CustomBlockId, RenderBlock>,
18709 autoscroll: Option<Autoscroll>,
18710 cx: &mut Context<Self>,
18711 ) {
18712 self.display_map
18713 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18714 if let Some(autoscroll) = autoscroll {
18715 self.request_autoscroll(autoscroll, cx);
18716 }
18717 cx.notify();
18718 }
18719
18720 pub fn remove_blocks(
18721 &mut self,
18722 block_ids: HashSet<CustomBlockId>,
18723 autoscroll: Option<Autoscroll>,
18724 cx: &mut Context<Self>,
18725 ) {
18726 self.display_map.update(cx, |display_map, cx| {
18727 display_map.remove_blocks(block_ids, cx)
18728 });
18729 if let Some(autoscroll) = autoscroll {
18730 self.request_autoscroll(autoscroll, cx);
18731 }
18732 cx.notify();
18733 }
18734
18735 pub fn row_for_block(
18736 &self,
18737 block_id: CustomBlockId,
18738 cx: &mut Context<Self>,
18739 ) -> Option<DisplayRow> {
18740 self.display_map
18741 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18742 }
18743
18744 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18745 self.focused_block = Some(focused_block);
18746 }
18747
18748 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18749 self.focused_block.take()
18750 }
18751
18752 pub fn insert_creases(
18753 &mut self,
18754 creases: impl IntoIterator<Item = Crease<Anchor>>,
18755 cx: &mut Context<Self>,
18756 ) -> Vec<CreaseId> {
18757 self.display_map
18758 .update(cx, |map, cx| map.insert_creases(creases, cx))
18759 }
18760
18761 pub fn remove_creases(
18762 &mut self,
18763 ids: impl IntoIterator<Item = CreaseId>,
18764 cx: &mut Context<Self>,
18765 ) -> Vec<(CreaseId, Range<Anchor>)> {
18766 self.display_map
18767 .update(cx, |map, cx| map.remove_creases(ids, cx))
18768 }
18769
18770 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18771 self.display_map
18772 .update(cx, |map, cx| map.snapshot(cx))
18773 .longest_row()
18774 }
18775
18776 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18777 self.display_map
18778 .update(cx, |map, cx| map.snapshot(cx))
18779 .max_point()
18780 }
18781
18782 pub fn text(&self, cx: &App) -> String {
18783 self.buffer.read(cx).read(cx).text()
18784 }
18785
18786 pub fn is_empty(&self, cx: &App) -> bool {
18787 self.buffer.read(cx).read(cx).is_empty()
18788 }
18789
18790 pub fn text_option(&self, cx: &App) -> Option<String> {
18791 let text = self.text(cx);
18792 let text = text.trim();
18793
18794 if text.is_empty() {
18795 return None;
18796 }
18797
18798 Some(text.to_string())
18799 }
18800
18801 pub fn set_text(
18802 &mut self,
18803 text: impl Into<Arc<str>>,
18804 window: &mut Window,
18805 cx: &mut Context<Self>,
18806 ) {
18807 self.transact(window, cx, |this, _, cx| {
18808 this.buffer
18809 .read(cx)
18810 .as_singleton()
18811 .expect("you can only call set_text on editors for singleton buffers")
18812 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18813 });
18814 }
18815
18816 pub fn display_text(&self, cx: &mut App) -> String {
18817 self.display_map
18818 .update(cx, |map, cx| map.snapshot(cx))
18819 .text()
18820 }
18821
18822 fn create_minimap(
18823 &self,
18824 minimap_settings: MinimapSettings,
18825 window: &mut Window,
18826 cx: &mut Context<Self>,
18827 ) -> Option<Entity<Self>> {
18828 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18829 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18830 }
18831
18832 fn initialize_new_minimap(
18833 &self,
18834 minimap_settings: MinimapSettings,
18835 window: &mut Window,
18836 cx: &mut Context<Self>,
18837 ) -> Entity<Self> {
18838 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18839
18840 let mut minimap = Editor::new_internal(
18841 EditorMode::Minimap {
18842 parent: cx.weak_entity(),
18843 },
18844 self.buffer.clone(),
18845 None,
18846 Some(self.display_map.clone()),
18847 window,
18848 cx,
18849 );
18850 minimap.scroll_manager.clone_state(&self.scroll_manager);
18851 minimap.set_text_style_refinement(TextStyleRefinement {
18852 font_size: Some(MINIMAP_FONT_SIZE),
18853 font_weight: Some(MINIMAP_FONT_WEIGHT),
18854 ..Default::default()
18855 });
18856 minimap.update_minimap_configuration(minimap_settings, cx);
18857 cx.new(|_| minimap)
18858 }
18859
18860 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18861 let current_line_highlight = minimap_settings
18862 .current_line_highlight
18863 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18864 self.set_current_line_highlight(Some(current_line_highlight));
18865 }
18866
18867 pub fn minimap(&self) -> Option<&Entity<Self>> {
18868 self.minimap
18869 .as_ref()
18870 .filter(|_| self.minimap_visibility.visible())
18871 }
18872
18873 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18874 let mut wrap_guides = smallvec![];
18875
18876 if self.show_wrap_guides == Some(false) {
18877 return wrap_guides;
18878 }
18879
18880 let settings = self.buffer.read(cx).language_settings(cx);
18881 if settings.show_wrap_guides {
18882 match self.soft_wrap_mode(cx) {
18883 SoftWrap::Column(soft_wrap) => {
18884 wrap_guides.push((soft_wrap as usize, true));
18885 }
18886 SoftWrap::Bounded(soft_wrap) => {
18887 wrap_guides.push((soft_wrap as usize, true));
18888 }
18889 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18890 }
18891 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18892 }
18893
18894 wrap_guides
18895 }
18896
18897 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18898 let settings = self.buffer.read(cx).language_settings(cx);
18899 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18900 match mode {
18901 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18902 SoftWrap::None
18903 }
18904 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18905 language_settings::SoftWrap::PreferredLineLength => {
18906 SoftWrap::Column(settings.preferred_line_length)
18907 }
18908 language_settings::SoftWrap::Bounded => {
18909 SoftWrap::Bounded(settings.preferred_line_length)
18910 }
18911 }
18912 }
18913
18914 pub fn set_soft_wrap_mode(
18915 &mut self,
18916 mode: language_settings::SoftWrap,
18917
18918 cx: &mut Context<Self>,
18919 ) {
18920 self.soft_wrap_mode_override = Some(mode);
18921 cx.notify();
18922 }
18923
18924 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18925 self.hard_wrap = hard_wrap;
18926 cx.notify();
18927 }
18928
18929 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18930 self.text_style_refinement = Some(style);
18931 }
18932
18933 /// called by the Element so we know what style we were most recently rendered with.
18934 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
18935 // We intentionally do not inform the display map about the minimap style
18936 // so that wrapping is not recalculated and stays consistent for the editor
18937 // and its linked minimap.
18938 if !self.mode.is_minimap() {
18939 let font = style.text.font();
18940 let font_size = style.text.font_size.to_pixels(window.rem_size());
18941 let display_map = self
18942 .placeholder_display_map
18943 .as_ref()
18944 .filter(|_| self.is_empty(cx))
18945 .unwrap_or(&self.display_map);
18946
18947 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
18948 }
18949 self.style = Some(style);
18950 }
18951
18952 pub fn style(&self) -> Option<&EditorStyle> {
18953 self.style.as_ref()
18954 }
18955
18956 // Called by the element. This method is not designed to be called outside of the editor
18957 // element's layout code because it does not notify when rewrapping is computed synchronously.
18958 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18959 if self.is_empty(cx) {
18960 self.placeholder_display_map
18961 .as_ref()
18962 .map_or(false, |display_map| {
18963 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
18964 })
18965 } else {
18966 self.display_map
18967 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18968 }
18969 }
18970
18971 pub fn set_soft_wrap(&mut self) {
18972 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18973 }
18974
18975 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18976 if self.soft_wrap_mode_override.is_some() {
18977 self.soft_wrap_mode_override.take();
18978 } else {
18979 let soft_wrap = match self.soft_wrap_mode(cx) {
18980 SoftWrap::GitDiff => return,
18981 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18982 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18983 language_settings::SoftWrap::None
18984 }
18985 };
18986 self.soft_wrap_mode_override = Some(soft_wrap);
18987 }
18988 cx.notify();
18989 }
18990
18991 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18992 let Some(workspace) = self.workspace() else {
18993 return;
18994 };
18995 let fs = workspace.read(cx).app_state().fs.clone();
18996 let current_show = TabBarSettings::get_global(cx).show;
18997 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18998 setting.show = Some(!current_show);
18999 });
19000 }
19001
19002 pub fn toggle_indent_guides(
19003 &mut self,
19004 _: &ToggleIndentGuides,
19005 _: &mut Window,
19006 cx: &mut Context<Self>,
19007 ) {
19008 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19009 self.buffer
19010 .read(cx)
19011 .language_settings(cx)
19012 .indent_guides
19013 .enabled
19014 });
19015 self.show_indent_guides = Some(!currently_enabled);
19016 cx.notify();
19017 }
19018
19019 fn should_show_indent_guides(&self) -> Option<bool> {
19020 self.show_indent_guides
19021 }
19022
19023 pub fn toggle_line_numbers(
19024 &mut self,
19025 _: &ToggleLineNumbers,
19026 _: &mut Window,
19027 cx: &mut Context<Self>,
19028 ) {
19029 let mut editor_settings = EditorSettings::get_global(cx).clone();
19030 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19031 EditorSettings::override_global(editor_settings, cx);
19032 }
19033
19034 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19035 if let Some(show_line_numbers) = self.show_line_numbers {
19036 return show_line_numbers;
19037 }
19038 EditorSettings::get_global(cx).gutter.line_numbers
19039 }
19040
19041 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19042 self.use_relative_line_numbers
19043 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19044 }
19045
19046 pub fn toggle_relative_line_numbers(
19047 &mut self,
19048 _: &ToggleRelativeLineNumbers,
19049 _: &mut Window,
19050 cx: &mut Context<Self>,
19051 ) {
19052 let is_relative = self.should_use_relative_line_numbers(cx);
19053 self.set_relative_line_number(Some(!is_relative), cx)
19054 }
19055
19056 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19057 self.use_relative_line_numbers = is_relative;
19058 cx.notify();
19059 }
19060
19061 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19062 self.show_gutter = show_gutter;
19063 cx.notify();
19064 }
19065
19066 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19067 self.show_scrollbars = ScrollbarAxes {
19068 horizontal: show,
19069 vertical: show,
19070 };
19071 cx.notify();
19072 }
19073
19074 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19075 self.show_scrollbars.vertical = show;
19076 cx.notify();
19077 }
19078
19079 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19080 self.show_scrollbars.horizontal = show;
19081 cx.notify();
19082 }
19083
19084 pub fn set_minimap_visibility(
19085 &mut self,
19086 minimap_visibility: MinimapVisibility,
19087 window: &mut Window,
19088 cx: &mut Context<Self>,
19089 ) {
19090 if self.minimap_visibility != minimap_visibility {
19091 if minimap_visibility.visible() && self.minimap.is_none() {
19092 let minimap_settings = EditorSettings::get_global(cx).minimap;
19093 self.minimap =
19094 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19095 }
19096 self.minimap_visibility = minimap_visibility;
19097 cx.notify();
19098 }
19099 }
19100
19101 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19102 self.set_show_scrollbars(false, cx);
19103 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19104 }
19105
19106 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19107 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19108 }
19109
19110 /// Normally the text in full mode and auto height editors is padded on the
19111 /// left side by roughly half a character width for improved hit testing.
19112 ///
19113 /// Use this method to disable this for cases where this is not wanted (e.g.
19114 /// if you want to align the editor text with some other text above or below)
19115 /// or if you want to add this padding to single-line editors.
19116 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19117 self.offset_content = offset_content;
19118 cx.notify();
19119 }
19120
19121 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19122 self.show_line_numbers = Some(show_line_numbers);
19123 cx.notify();
19124 }
19125
19126 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19127 self.disable_expand_excerpt_buttons = true;
19128 cx.notify();
19129 }
19130
19131 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19132 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19133 cx.notify();
19134 }
19135
19136 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19137 self.show_code_actions = Some(show_code_actions);
19138 cx.notify();
19139 }
19140
19141 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19142 self.show_runnables = Some(show_runnables);
19143 cx.notify();
19144 }
19145
19146 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19147 self.show_breakpoints = Some(show_breakpoints);
19148 cx.notify();
19149 }
19150
19151 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19152 if self.display_map.read(cx).masked != masked {
19153 self.display_map.update(cx, |map, _| map.masked = masked);
19154 }
19155 cx.notify()
19156 }
19157
19158 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19159 self.show_wrap_guides = Some(show_wrap_guides);
19160 cx.notify();
19161 }
19162
19163 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19164 self.show_indent_guides = Some(show_indent_guides);
19165 cx.notify();
19166 }
19167
19168 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19169 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19170 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19171 && let Some(dir) = file.abs_path(cx).parent()
19172 {
19173 return Some(dir.to_owned());
19174 }
19175
19176 if let Some(project_path) = buffer.read(cx).project_path(cx) {
19177 return Some(project_path.path.to_path_buf());
19178 }
19179 }
19180
19181 None
19182 }
19183
19184 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19185 self.active_excerpt(cx)?
19186 .1
19187 .read(cx)
19188 .file()
19189 .and_then(|f| f.as_local())
19190 }
19191
19192 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19193 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19194 let buffer = buffer.read(cx);
19195 if let Some(project_path) = buffer.project_path(cx) {
19196 let project = self.project()?.read(cx);
19197 project.absolute_path(&project_path, cx)
19198 } else {
19199 buffer
19200 .file()
19201 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19202 }
19203 })
19204 }
19205
19206 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19207 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19208 let project_path = buffer.read(cx).project_path(cx)?;
19209 let project = self.project()?.read(cx);
19210 let entry = project.entry_for_path(&project_path, cx)?;
19211 let path = entry.path.to_path_buf();
19212 Some(path)
19213 })
19214 }
19215
19216 pub fn reveal_in_finder(
19217 &mut self,
19218 _: &RevealInFileManager,
19219 _window: &mut Window,
19220 cx: &mut Context<Self>,
19221 ) {
19222 if let Some(target) = self.target_file(cx) {
19223 cx.reveal_path(&target.abs_path(cx));
19224 }
19225 }
19226
19227 pub fn copy_path(
19228 &mut self,
19229 _: &zed_actions::workspace::CopyPath,
19230 _window: &mut Window,
19231 cx: &mut Context<Self>,
19232 ) {
19233 if let Some(path) = self.target_file_abs_path(cx)
19234 && let Some(path) = path.to_str()
19235 {
19236 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19237 }
19238 }
19239
19240 pub fn copy_relative_path(
19241 &mut self,
19242 _: &zed_actions::workspace::CopyRelativePath,
19243 _window: &mut Window,
19244 cx: &mut Context<Self>,
19245 ) {
19246 if let Some(path) = self.target_file_path(cx)
19247 && let Some(path) = path.to_str()
19248 {
19249 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19250 }
19251 }
19252
19253 /// Returns the project path for the editor's buffer, if any buffer is
19254 /// opened in the editor.
19255 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19256 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19257 buffer.read(cx).project_path(cx)
19258 } else {
19259 None
19260 }
19261 }
19262
19263 // Returns true if the editor handled a go-to-line request
19264 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19265 maybe!({
19266 let breakpoint_store = self.breakpoint_store.as_ref()?;
19267
19268 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19269 else {
19270 self.clear_row_highlights::<ActiveDebugLine>();
19271 return None;
19272 };
19273
19274 let position = active_stack_frame.position;
19275 let buffer_id = position.buffer_id?;
19276 let snapshot = self
19277 .project
19278 .as_ref()?
19279 .read(cx)
19280 .buffer_for_id(buffer_id, cx)?
19281 .read(cx)
19282 .snapshot();
19283
19284 let mut handled = false;
19285 for (id, ExcerptRange { context, .. }) in
19286 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19287 {
19288 if context.start.cmp(&position, &snapshot).is_ge()
19289 || context.end.cmp(&position, &snapshot).is_lt()
19290 {
19291 continue;
19292 }
19293 let snapshot = self.buffer.read(cx).snapshot(cx);
19294 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19295
19296 handled = true;
19297 self.clear_row_highlights::<ActiveDebugLine>();
19298
19299 self.go_to_line::<ActiveDebugLine>(
19300 multibuffer_anchor,
19301 Some(cx.theme().colors().editor_debugger_active_line_background),
19302 window,
19303 cx,
19304 );
19305
19306 cx.notify();
19307 }
19308
19309 handled.then_some(())
19310 })
19311 .is_some()
19312 }
19313
19314 pub fn copy_file_name_without_extension(
19315 &mut self,
19316 _: &CopyFileNameWithoutExtension,
19317 _: &mut Window,
19318 cx: &mut Context<Self>,
19319 ) {
19320 if let Some(file) = self.target_file(cx)
19321 && let Some(file_stem) = file.path().file_stem()
19322 && let Some(name) = file_stem.to_str()
19323 {
19324 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19325 }
19326 }
19327
19328 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19329 if let Some(file) = self.target_file(cx)
19330 && let Some(file_name) = file.path().file_name()
19331 && let Some(name) = file_name.to_str()
19332 {
19333 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19334 }
19335 }
19336
19337 pub fn toggle_git_blame(
19338 &mut self,
19339 _: &::git::Blame,
19340 window: &mut Window,
19341 cx: &mut Context<Self>,
19342 ) {
19343 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19344
19345 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19346 self.start_git_blame(true, window, cx);
19347 }
19348
19349 cx.notify();
19350 }
19351
19352 pub fn toggle_git_blame_inline(
19353 &mut self,
19354 _: &ToggleGitBlameInline,
19355 window: &mut Window,
19356 cx: &mut Context<Self>,
19357 ) {
19358 self.toggle_git_blame_inline_internal(true, window, cx);
19359 cx.notify();
19360 }
19361
19362 pub fn open_git_blame_commit(
19363 &mut self,
19364 _: &OpenGitBlameCommit,
19365 window: &mut Window,
19366 cx: &mut Context<Self>,
19367 ) {
19368 self.open_git_blame_commit_internal(window, cx);
19369 }
19370
19371 fn open_git_blame_commit_internal(
19372 &mut self,
19373 window: &mut Window,
19374 cx: &mut Context<Self>,
19375 ) -> Option<()> {
19376 let blame = self.blame.as_ref()?;
19377 let snapshot = self.snapshot(window, cx);
19378 let cursor = self.selections.newest::<Point>(cx).head();
19379 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19380 let (_, blame_entry) = blame
19381 .update(cx, |blame, cx| {
19382 blame
19383 .blame_for_rows(
19384 &[RowInfo {
19385 buffer_id: Some(buffer.remote_id()),
19386 buffer_row: Some(point.row),
19387 ..Default::default()
19388 }],
19389 cx,
19390 )
19391 .next()
19392 })
19393 .flatten()?;
19394 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19395 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19396 let workspace = self.workspace()?.downgrade();
19397 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19398 None
19399 }
19400
19401 pub fn git_blame_inline_enabled(&self) -> bool {
19402 self.git_blame_inline_enabled
19403 }
19404
19405 pub fn toggle_selection_menu(
19406 &mut self,
19407 _: &ToggleSelectionMenu,
19408 _: &mut Window,
19409 cx: &mut Context<Self>,
19410 ) {
19411 self.show_selection_menu = self
19412 .show_selection_menu
19413 .map(|show_selections_menu| !show_selections_menu)
19414 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19415
19416 cx.notify();
19417 }
19418
19419 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19420 self.show_selection_menu
19421 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19422 }
19423
19424 fn start_git_blame(
19425 &mut self,
19426 user_triggered: bool,
19427 window: &mut Window,
19428 cx: &mut Context<Self>,
19429 ) {
19430 if let Some(project) = self.project() {
19431 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19432 && buffer.read(cx).file().is_none()
19433 {
19434 return;
19435 }
19436
19437 let focused = self.focus_handle(cx).contains_focused(window, cx);
19438
19439 let project = project.clone();
19440 let blame = cx
19441 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19442 self.blame_subscription =
19443 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19444 self.blame = Some(blame);
19445 }
19446 }
19447
19448 fn toggle_git_blame_inline_internal(
19449 &mut self,
19450 user_triggered: bool,
19451 window: &mut Window,
19452 cx: &mut Context<Self>,
19453 ) {
19454 if self.git_blame_inline_enabled {
19455 self.git_blame_inline_enabled = false;
19456 self.show_git_blame_inline = false;
19457 self.show_git_blame_inline_delay_task.take();
19458 } else {
19459 self.git_blame_inline_enabled = true;
19460 self.start_git_blame_inline(user_triggered, window, cx);
19461 }
19462
19463 cx.notify();
19464 }
19465
19466 fn start_git_blame_inline(
19467 &mut self,
19468 user_triggered: bool,
19469 window: &mut Window,
19470 cx: &mut Context<Self>,
19471 ) {
19472 self.start_git_blame(user_triggered, window, cx);
19473
19474 if ProjectSettings::get_global(cx)
19475 .git
19476 .inline_blame_delay()
19477 .is_some()
19478 {
19479 self.start_inline_blame_timer(window, cx);
19480 } else {
19481 self.show_git_blame_inline = true
19482 }
19483 }
19484
19485 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19486 self.blame.as_ref()
19487 }
19488
19489 pub fn show_git_blame_gutter(&self) -> bool {
19490 self.show_git_blame_gutter
19491 }
19492
19493 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19494 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19495 }
19496
19497 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19498 self.show_git_blame_inline
19499 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19500 && !self.newest_selection_head_on_empty_line(cx)
19501 && self.has_blame_entries(cx)
19502 }
19503
19504 fn has_blame_entries(&self, cx: &App) -> bool {
19505 self.blame()
19506 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19507 }
19508
19509 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19510 let cursor_anchor = self.selections.newest_anchor().head();
19511
19512 let snapshot = self.buffer.read(cx).snapshot(cx);
19513 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19514
19515 snapshot.line_len(buffer_row) == 0
19516 }
19517
19518 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19519 let buffer_and_selection = maybe!({
19520 let selection = self.selections.newest::<Point>(cx);
19521 let selection_range = selection.range();
19522
19523 let multi_buffer = self.buffer().read(cx);
19524 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19525 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19526
19527 let (buffer, range, _) = if selection.reversed {
19528 buffer_ranges.first()
19529 } else {
19530 buffer_ranges.last()
19531 }?;
19532
19533 let selection = text::ToPoint::to_point(&range.start, buffer).row
19534 ..text::ToPoint::to_point(&range.end, buffer).row;
19535 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19536 });
19537
19538 let Some((buffer, selection)) = buffer_and_selection else {
19539 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19540 };
19541
19542 let Some(project) = self.project() else {
19543 return Task::ready(Err(anyhow!("editor does not have project")));
19544 };
19545
19546 project.update(cx, |project, cx| {
19547 project.get_permalink_to_line(&buffer, selection, cx)
19548 })
19549 }
19550
19551 pub fn copy_permalink_to_line(
19552 &mut self,
19553 _: &CopyPermalinkToLine,
19554 window: &mut Window,
19555 cx: &mut Context<Self>,
19556 ) {
19557 let permalink_task = self.get_permalink_to_line(cx);
19558 let workspace = self.workspace();
19559
19560 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19561 Ok(permalink) => {
19562 cx.update(|_, cx| {
19563 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19564 })
19565 .ok();
19566 }
19567 Err(err) => {
19568 let message = format!("Failed to copy permalink: {err}");
19569
19570 anyhow::Result::<()>::Err(err).log_err();
19571
19572 if let Some(workspace) = workspace {
19573 workspace
19574 .update_in(cx, |workspace, _, cx| {
19575 struct CopyPermalinkToLine;
19576
19577 workspace.show_toast(
19578 Toast::new(
19579 NotificationId::unique::<CopyPermalinkToLine>(),
19580 message,
19581 ),
19582 cx,
19583 )
19584 })
19585 .ok();
19586 }
19587 }
19588 })
19589 .detach();
19590 }
19591
19592 pub fn copy_file_location(
19593 &mut self,
19594 _: &CopyFileLocation,
19595 _: &mut Window,
19596 cx: &mut Context<Self>,
19597 ) {
19598 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19599 if let Some(file) = self.target_file(cx)
19600 && let Some(path) = file.path().to_str()
19601 {
19602 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19603 }
19604 }
19605
19606 pub fn open_permalink_to_line(
19607 &mut self,
19608 _: &OpenPermalinkToLine,
19609 window: &mut Window,
19610 cx: &mut Context<Self>,
19611 ) {
19612 let permalink_task = self.get_permalink_to_line(cx);
19613 let workspace = self.workspace();
19614
19615 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19616 Ok(permalink) => {
19617 cx.update(|_, cx| {
19618 cx.open_url(permalink.as_ref());
19619 })
19620 .ok();
19621 }
19622 Err(err) => {
19623 let message = format!("Failed to open permalink: {err}");
19624
19625 anyhow::Result::<()>::Err(err).log_err();
19626
19627 if let Some(workspace) = workspace {
19628 workspace
19629 .update(cx, |workspace, cx| {
19630 struct OpenPermalinkToLine;
19631
19632 workspace.show_toast(
19633 Toast::new(
19634 NotificationId::unique::<OpenPermalinkToLine>(),
19635 message,
19636 ),
19637 cx,
19638 )
19639 })
19640 .ok();
19641 }
19642 }
19643 })
19644 .detach();
19645 }
19646
19647 pub fn insert_uuid_v4(
19648 &mut self,
19649 _: &InsertUuidV4,
19650 window: &mut Window,
19651 cx: &mut Context<Self>,
19652 ) {
19653 self.insert_uuid(UuidVersion::V4, window, cx);
19654 }
19655
19656 pub fn insert_uuid_v7(
19657 &mut self,
19658 _: &InsertUuidV7,
19659 window: &mut Window,
19660 cx: &mut Context<Self>,
19661 ) {
19662 self.insert_uuid(UuidVersion::V7, window, cx);
19663 }
19664
19665 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19666 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19667 self.transact(window, cx, |this, window, cx| {
19668 let edits = this
19669 .selections
19670 .all::<Point>(cx)
19671 .into_iter()
19672 .map(|selection| {
19673 let uuid = match version {
19674 UuidVersion::V4 => uuid::Uuid::new_v4(),
19675 UuidVersion::V7 => uuid::Uuid::now_v7(),
19676 };
19677
19678 (selection.range(), uuid.to_string())
19679 });
19680 this.edit(edits, cx);
19681 this.refresh_edit_prediction(true, false, window, cx);
19682 });
19683 }
19684
19685 pub fn open_selections_in_multibuffer(
19686 &mut self,
19687 _: &OpenSelectionsInMultibuffer,
19688 window: &mut Window,
19689 cx: &mut Context<Self>,
19690 ) {
19691 let multibuffer = self.buffer.read(cx);
19692
19693 let Some(buffer) = multibuffer.as_singleton() else {
19694 return;
19695 };
19696
19697 let Some(workspace) = self.workspace() else {
19698 return;
19699 };
19700
19701 let title = multibuffer.title(cx).to_string();
19702
19703 let locations = self
19704 .selections
19705 .all_anchors(cx)
19706 .iter()
19707 .map(|selection| Location {
19708 buffer: buffer.clone(),
19709 range: selection.start.text_anchor..selection.end.text_anchor,
19710 })
19711 .collect::<Vec<_>>();
19712
19713 cx.spawn_in(window, async move |_, cx| {
19714 workspace.update_in(cx, |workspace, window, cx| {
19715 Self::open_locations_in_multibuffer(
19716 workspace,
19717 locations,
19718 format!("Selections for '{title}'"),
19719 false,
19720 MultibufferSelectionMode::All,
19721 window,
19722 cx,
19723 );
19724 })
19725 })
19726 .detach();
19727 }
19728
19729 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19730 /// last highlight added will be used.
19731 ///
19732 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19733 pub fn highlight_rows<T: 'static>(
19734 &mut self,
19735 range: Range<Anchor>,
19736 color: Hsla,
19737 options: RowHighlightOptions,
19738 cx: &mut Context<Self>,
19739 ) {
19740 let snapshot = self.buffer().read(cx).snapshot(cx);
19741 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19742 let ix = row_highlights.binary_search_by(|highlight| {
19743 Ordering::Equal
19744 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19745 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19746 });
19747
19748 if let Err(mut ix) = ix {
19749 let index = post_inc(&mut self.highlight_order);
19750
19751 // If this range intersects with the preceding highlight, then merge it with
19752 // the preceding highlight. Otherwise insert a new highlight.
19753 let mut merged = false;
19754 if ix > 0 {
19755 let prev_highlight = &mut row_highlights[ix - 1];
19756 if prev_highlight
19757 .range
19758 .end
19759 .cmp(&range.start, &snapshot)
19760 .is_ge()
19761 {
19762 ix -= 1;
19763 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19764 prev_highlight.range.end = range.end;
19765 }
19766 merged = true;
19767 prev_highlight.index = index;
19768 prev_highlight.color = color;
19769 prev_highlight.options = options;
19770 }
19771 }
19772
19773 if !merged {
19774 row_highlights.insert(
19775 ix,
19776 RowHighlight {
19777 range,
19778 index,
19779 color,
19780 options,
19781 type_id: TypeId::of::<T>(),
19782 },
19783 );
19784 }
19785
19786 // If any of the following highlights intersect with this one, merge them.
19787 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19788 let highlight = &row_highlights[ix];
19789 if next_highlight
19790 .range
19791 .start
19792 .cmp(&highlight.range.end, &snapshot)
19793 .is_le()
19794 {
19795 if next_highlight
19796 .range
19797 .end
19798 .cmp(&highlight.range.end, &snapshot)
19799 .is_gt()
19800 {
19801 row_highlights[ix].range.end = next_highlight.range.end;
19802 }
19803 row_highlights.remove(ix + 1);
19804 } else {
19805 break;
19806 }
19807 }
19808 }
19809 }
19810
19811 /// Remove any highlighted row ranges of the given type that intersect the
19812 /// given ranges.
19813 pub fn remove_highlighted_rows<T: 'static>(
19814 &mut self,
19815 ranges_to_remove: Vec<Range<Anchor>>,
19816 cx: &mut Context<Self>,
19817 ) {
19818 let snapshot = self.buffer().read(cx).snapshot(cx);
19819 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19820 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19821 row_highlights.retain(|highlight| {
19822 while let Some(range_to_remove) = ranges_to_remove.peek() {
19823 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19824 Ordering::Less | Ordering::Equal => {
19825 ranges_to_remove.next();
19826 }
19827 Ordering::Greater => {
19828 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19829 Ordering::Less | Ordering::Equal => {
19830 return false;
19831 }
19832 Ordering::Greater => break,
19833 }
19834 }
19835 }
19836 }
19837
19838 true
19839 })
19840 }
19841
19842 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19843 pub fn clear_row_highlights<T: 'static>(&mut self) {
19844 self.highlighted_rows.remove(&TypeId::of::<T>());
19845 }
19846
19847 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19848 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19849 self.highlighted_rows
19850 .get(&TypeId::of::<T>())
19851 .map_or(&[] as &[_], |vec| vec.as_slice())
19852 .iter()
19853 .map(|highlight| (highlight.range.clone(), highlight.color))
19854 }
19855
19856 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19857 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19858 /// Allows to ignore certain kinds of highlights.
19859 pub fn highlighted_display_rows(
19860 &self,
19861 window: &mut Window,
19862 cx: &mut App,
19863 ) -> BTreeMap<DisplayRow, LineHighlight> {
19864 let snapshot = self.snapshot(window, cx);
19865 let mut used_highlight_orders = HashMap::default();
19866 self.highlighted_rows
19867 .iter()
19868 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19869 .fold(
19870 BTreeMap::<DisplayRow, LineHighlight>::new(),
19871 |mut unique_rows, highlight| {
19872 let start = highlight.range.start.to_display_point(&snapshot);
19873 let end = highlight.range.end.to_display_point(&snapshot);
19874 let start_row = start.row().0;
19875 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19876 && end.column() == 0
19877 {
19878 end.row().0.saturating_sub(1)
19879 } else {
19880 end.row().0
19881 };
19882 for row in start_row..=end_row {
19883 let used_index =
19884 used_highlight_orders.entry(row).or_insert(highlight.index);
19885 if highlight.index >= *used_index {
19886 *used_index = highlight.index;
19887 unique_rows.insert(
19888 DisplayRow(row),
19889 LineHighlight {
19890 include_gutter: highlight.options.include_gutter,
19891 border: None,
19892 background: highlight.color.into(),
19893 type_id: Some(highlight.type_id),
19894 },
19895 );
19896 }
19897 }
19898 unique_rows
19899 },
19900 )
19901 }
19902
19903 pub fn highlighted_display_row_for_autoscroll(
19904 &self,
19905 snapshot: &DisplaySnapshot,
19906 ) -> Option<DisplayRow> {
19907 self.highlighted_rows
19908 .values()
19909 .flat_map(|highlighted_rows| highlighted_rows.iter())
19910 .filter_map(|highlight| {
19911 if highlight.options.autoscroll {
19912 Some(highlight.range.start.to_display_point(snapshot).row())
19913 } else {
19914 None
19915 }
19916 })
19917 .min()
19918 }
19919
19920 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19921 self.highlight_background::<SearchWithinRange>(
19922 ranges,
19923 |colors| colors.colors().editor_document_highlight_read_background,
19924 cx,
19925 )
19926 }
19927
19928 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19929 self.breadcrumb_header = Some(new_header);
19930 }
19931
19932 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19933 self.clear_background_highlights::<SearchWithinRange>(cx);
19934 }
19935
19936 pub fn highlight_background<T: 'static>(
19937 &mut self,
19938 ranges: &[Range<Anchor>],
19939 color_fetcher: fn(&Theme) -> Hsla,
19940 cx: &mut Context<Self>,
19941 ) {
19942 self.background_highlights.insert(
19943 HighlightKey::Type(TypeId::of::<T>()),
19944 (color_fetcher, Arc::from(ranges)),
19945 );
19946 self.scrollbar_marker_state.dirty = true;
19947 cx.notify();
19948 }
19949
19950 pub fn highlight_background_key<T: 'static>(
19951 &mut self,
19952 key: usize,
19953 ranges: &[Range<Anchor>],
19954 color_fetcher: fn(&Theme) -> Hsla,
19955 cx: &mut Context<Self>,
19956 ) {
19957 self.background_highlights.insert(
19958 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19959 (color_fetcher, Arc::from(ranges)),
19960 );
19961 self.scrollbar_marker_state.dirty = true;
19962 cx.notify();
19963 }
19964
19965 pub fn clear_background_highlights<T: 'static>(
19966 &mut self,
19967 cx: &mut Context<Self>,
19968 ) -> Option<BackgroundHighlight> {
19969 let text_highlights = self
19970 .background_highlights
19971 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19972 if !text_highlights.1.is_empty() {
19973 self.scrollbar_marker_state.dirty = true;
19974 cx.notify();
19975 }
19976 Some(text_highlights)
19977 }
19978
19979 pub fn highlight_gutter<T: 'static>(
19980 &mut self,
19981 ranges: impl Into<Vec<Range<Anchor>>>,
19982 color_fetcher: fn(&App) -> Hsla,
19983 cx: &mut Context<Self>,
19984 ) {
19985 self.gutter_highlights
19986 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19987 cx.notify();
19988 }
19989
19990 pub fn clear_gutter_highlights<T: 'static>(
19991 &mut self,
19992 cx: &mut Context<Self>,
19993 ) -> Option<GutterHighlight> {
19994 cx.notify();
19995 self.gutter_highlights.remove(&TypeId::of::<T>())
19996 }
19997
19998 pub fn insert_gutter_highlight<T: 'static>(
19999 &mut self,
20000 range: Range<Anchor>,
20001 color_fetcher: fn(&App) -> Hsla,
20002 cx: &mut Context<Self>,
20003 ) {
20004 let snapshot = self.buffer().read(cx).snapshot(cx);
20005 let mut highlights = self
20006 .gutter_highlights
20007 .remove(&TypeId::of::<T>())
20008 .map(|(_, highlights)| highlights)
20009 .unwrap_or_default();
20010 let ix = highlights.binary_search_by(|highlight| {
20011 Ordering::Equal
20012 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20013 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20014 });
20015 if let Err(ix) = ix {
20016 highlights.insert(ix, range);
20017 }
20018 self.gutter_highlights
20019 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20020 }
20021
20022 pub fn remove_gutter_highlights<T: 'static>(
20023 &mut self,
20024 ranges_to_remove: Vec<Range<Anchor>>,
20025 cx: &mut Context<Self>,
20026 ) {
20027 let snapshot = self.buffer().read(cx).snapshot(cx);
20028 let Some((color_fetcher, mut gutter_highlights)) =
20029 self.gutter_highlights.remove(&TypeId::of::<T>())
20030 else {
20031 return;
20032 };
20033 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20034 gutter_highlights.retain(|highlight| {
20035 while let Some(range_to_remove) = ranges_to_remove.peek() {
20036 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20037 Ordering::Less | Ordering::Equal => {
20038 ranges_to_remove.next();
20039 }
20040 Ordering::Greater => {
20041 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20042 Ordering::Less | Ordering::Equal => {
20043 return false;
20044 }
20045 Ordering::Greater => break,
20046 }
20047 }
20048 }
20049 }
20050
20051 true
20052 });
20053 self.gutter_highlights
20054 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20055 }
20056
20057 #[cfg(feature = "test-support")]
20058 pub fn all_text_highlights(
20059 &self,
20060 window: &mut Window,
20061 cx: &mut Context<Self>,
20062 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20063 let snapshot = self.snapshot(window, cx);
20064 self.display_map.update(cx, |display_map, _| {
20065 display_map
20066 .all_text_highlights()
20067 .map(|highlight| {
20068 let (style, ranges) = highlight.as_ref();
20069 (
20070 *style,
20071 ranges
20072 .iter()
20073 .map(|range| range.clone().to_display_points(&snapshot))
20074 .collect(),
20075 )
20076 })
20077 .collect()
20078 })
20079 }
20080
20081 #[cfg(feature = "test-support")]
20082 pub fn all_text_background_highlights(
20083 &self,
20084 window: &mut Window,
20085 cx: &mut Context<Self>,
20086 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20087 let snapshot = self.snapshot(window, cx);
20088 let buffer = &snapshot.buffer_snapshot;
20089 let start = buffer.anchor_before(0);
20090 let end = buffer.anchor_after(buffer.len());
20091 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20092 }
20093
20094 #[cfg(any(test, feature = "test-support"))]
20095 pub fn sorted_background_highlights_in_range(
20096 &self,
20097 search_range: Range<Anchor>,
20098 display_snapshot: &DisplaySnapshot,
20099 theme: &Theme,
20100 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20101 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20102 res.sort_by(|a, b| {
20103 a.0.start
20104 .cmp(&b.0.start)
20105 .then_with(|| a.0.end.cmp(&b.0.end))
20106 .then_with(|| a.1.cmp(&b.1))
20107 });
20108 res
20109 }
20110
20111 #[cfg(feature = "test-support")]
20112 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20113 let snapshot = self.buffer().read(cx).snapshot(cx);
20114
20115 let highlights = self
20116 .background_highlights
20117 .get(&HighlightKey::Type(TypeId::of::<
20118 items::BufferSearchHighlights,
20119 >()));
20120
20121 if let Some((_color, ranges)) = highlights {
20122 ranges
20123 .iter()
20124 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20125 .collect_vec()
20126 } else {
20127 vec![]
20128 }
20129 }
20130
20131 fn document_highlights_for_position<'a>(
20132 &'a self,
20133 position: Anchor,
20134 buffer: &'a MultiBufferSnapshot,
20135 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20136 let read_highlights = self
20137 .background_highlights
20138 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20139 .map(|h| &h.1);
20140 let write_highlights = self
20141 .background_highlights
20142 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20143 .map(|h| &h.1);
20144 let left_position = position.bias_left(buffer);
20145 let right_position = position.bias_right(buffer);
20146 read_highlights
20147 .into_iter()
20148 .chain(write_highlights)
20149 .flat_map(move |ranges| {
20150 let start_ix = match ranges.binary_search_by(|probe| {
20151 let cmp = probe.end.cmp(&left_position, buffer);
20152 if cmp.is_ge() {
20153 Ordering::Greater
20154 } else {
20155 Ordering::Less
20156 }
20157 }) {
20158 Ok(i) | Err(i) => i,
20159 };
20160
20161 ranges[start_ix..]
20162 .iter()
20163 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20164 })
20165 }
20166
20167 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20168 self.background_highlights
20169 .get(&HighlightKey::Type(TypeId::of::<T>()))
20170 .is_some_and(|(_, highlights)| !highlights.is_empty())
20171 }
20172
20173 /// Returns all background highlights for a given range.
20174 ///
20175 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20176 pub fn background_highlights_in_range(
20177 &self,
20178 search_range: Range<Anchor>,
20179 display_snapshot: &DisplaySnapshot,
20180 theme: &Theme,
20181 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20182 let mut results = Vec::new();
20183 for (color_fetcher, ranges) in self.background_highlights.values() {
20184 let color = color_fetcher(theme);
20185 let start_ix = match ranges.binary_search_by(|probe| {
20186 let cmp = probe
20187 .end
20188 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20189 if cmp.is_gt() {
20190 Ordering::Greater
20191 } else {
20192 Ordering::Less
20193 }
20194 }) {
20195 Ok(i) | Err(i) => i,
20196 };
20197 for range in &ranges[start_ix..] {
20198 if range
20199 .start
20200 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20201 .is_ge()
20202 {
20203 break;
20204 }
20205
20206 let start = range.start.to_display_point(display_snapshot);
20207 let end = range.end.to_display_point(display_snapshot);
20208 results.push((start..end, color))
20209 }
20210 }
20211 results
20212 }
20213
20214 pub fn gutter_highlights_in_range(
20215 &self,
20216 search_range: Range<Anchor>,
20217 display_snapshot: &DisplaySnapshot,
20218 cx: &App,
20219 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20220 let mut results = Vec::new();
20221 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20222 let color = color_fetcher(cx);
20223 let start_ix = match ranges.binary_search_by(|probe| {
20224 let cmp = probe
20225 .end
20226 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20227 if cmp.is_gt() {
20228 Ordering::Greater
20229 } else {
20230 Ordering::Less
20231 }
20232 }) {
20233 Ok(i) | Err(i) => i,
20234 };
20235 for range in &ranges[start_ix..] {
20236 if range
20237 .start
20238 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20239 .is_ge()
20240 {
20241 break;
20242 }
20243
20244 let start = range.start.to_display_point(display_snapshot);
20245 let end = range.end.to_display_point(display_snapshot);
20246 results.push((start..end, color))
20247 }
20248 }
20249 results
20250 }
20251
20252 /// Get the text ranges corresponding to the redaction query
20253 pub fn redacted_ranges(
20254 &self,
20255 search_range: Range<Anchor>,
20256 display_snapshot: &DisplaySnapshot,
20257 cx: &App,
20258 ) -> Vec<Range<DisplayPoint>> {
20259 display_snapshot
20260 .buffer_snapshot
20261 .redacted_ranges(search_range, |file| {
20262 if let Some(file) = file {
20263 file.is_private()
20264 && EditorSettings::get(
20265 Some(SettingsLocation {
20266 worktree_id: file.worktree_id(cx),
20267 path: file.path().as_ref(),
20268 }),
20269 cx,
20270 )
20271 .redact_private_values
20272 } else {
20273 false
20274 }
20275 })
20276 .map(|range| {
20277 range.start.to_display_point(display_snapshot)
20278 ..range.end.to_display_point(display_snapshot)
20279 })
20280 .collect()
20281 }
20282
20283 pub fn highlight_text_key<T: 'static>(
20284 &mut self,
20285 key: usize,
20286 ranges: Vec<Range<Anchor>>,
20287 style: HighlightStyle,
20288 cx: &mut Context<Self>,
20289 ) {
20290 self.display_map.update(cx, |map, _| {
20291 map.highlight_text(
20292 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20293 ranges,
20294 style,
20295 );
20296 });
20297 cx.notify();
20298 }
20299
20300 pub fn highlight_text<T: 'static>(
20301 &mut self,
20302 ranges: Vec<Range<Anchor>>,
20303 style: HighlightStyle,
20304 cx: &mut Context<Self>,
20305 ) {
20306 self.display_map.update(cx, |map, _| {
20307 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20308 });
20309 cx.notify();
20310 }
20311
20312 pub(crate) fn highlight_inlays<T: 'static>(
20313 &mut self,
20314 highlights: Vec<InlayHighlight>,
20315 style: HighlightStyle,
20316 cx: &mut Context<Self>,
20317 ) {
20318 self.display_map.update(cx, |map, _| {
20319 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20320 });
20321 cx.notify();
20322 }
20323
20324 pub fn text_highlights<'a, T: 'static>(
20325 &'a self,
20326 cx: &'a App,
20327 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20328 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20329 }
20330
20331 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20332 let cleared = self
20333 .display_map
20334 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20335 if cleared {
20336 cx.notify();
20337 }
20338 }
20339
20340 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20341 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20342 && self.focus_handle.is_focused(window)
20343 }
20344
20345 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20346 self.show_cursor_when_unfocused = is_enabled;
20347 cx.notify();
20348 }
20349
20350 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20351 cx.notify();
20352 }
20353
20354 fn on_debug_session_event(
20355 &mut self,
20356 _session: Entity<Session>,
20357 event: &SessionEvent,
20358 cx: &mut Context<Self>,
20359 ) {
20360 if let SessionEvent::InvalidateInlineValue = event {
20361 self.refresh_inline_values(cx);
20362 }
20363 }
20364
20365 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20366 let Some(project) = self.project.clone() else {
20367 return;
20368 };
20369
20370 if !self.inline_value_cache.enabled {
20371 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20372 self.splice_inlays(&inlays, Vec::new(), cx);
20373 return;
20374 }
20375
20376 let current_execution_position = self
20377 .highlighted_rows
20378 .get(&TypeId::of::<ActiveDebugLine>())
20379 .and_then(|lines| lines.last().map(|line| line.range.end));
20380
20381 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20382 let inline_values = editor
20383 .update(cx, |editor, cx| {
20384 let Some(current_execution_position) = current_execution_position else {
20385 return Some(Task::ready(Ok(Vec::new())));
20386 };
20387
20388 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20389 let snapshot = buffer.snapshot(cx);
20390
20391 let excerpt = snapshot.excerpt_containing(
20392 current_execution_position..current_execution_position,
20393 )?;
20394
20395 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20396 })?;
20397
20398 let range =
20399 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20400
20401 project.inline_values(buffer, range, cx)
20402 })
20403 .ok()
20404 .flatten()?
20405 .await
20406 .context("refreshing debugger inlays")
20407 .log_err()?;
20408
20409 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20410
20411 for (buffer_id, inline_value) in inline_values
20412 .into_iter()
20413 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20414 {
20415 buffer_inline_values
20416 .entry(buffer_id)
20417 .or_default()
20418 .push(inline_value);
20419 }
20420
20421 editor
20422 .update(cx, |editor, cx| {
20423 let snapshot = editor.buffer.read(cx).snapshot(cx);
20424 let mut new_inlays = Vec::default();
20425
20426 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20427 let buffer_id = buffer_snapshot.remote_id();
20428 buffer_inline_values
20429 .get(&buffer_id)
20430 .into_iter()
20431 .flatten()
20432 .for_each(|hint| {
20433 let inlay = Inlay::debugger(
20434 post_inc(&mut editor.next_inlay_id),
20435 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20436 hint.text(),
20437 );
20438 if !inlay.text.chars().contains(&'\n') {
20439 new_inlays.push(inlay);
20440 }
20441 });
20442 }
20443
20444 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20445 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20446
20447 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20448 })
20449 .ok()?;
20450 Some(())
20451 });
20452 }
20453
20454 fn on_buffer_event(
20455 &mut self,
20456 multibuffer: &Entity<MultiBuffer>,
20457 event: &multi_buffer::Event,
20458 window: &mut Window,
20459 cx: &mut Context<Self>,
20460 ) {
20461 match event {
20462 multi_buffer::Event::Edited {
20463 singleton_buffer_edited,
20464 edited_buffer,
20465 } => {
20466 self.scrollbar_marker_state.dirty = true;
20467 self.active_indent_guides_state.dirty = true;
20468 self.refresh_active_diagnostics(cx);
20469 self.refresh_code_actions(window, cx);
20470 self.refresh_selected_text_highlights(true, window, cx);
20471 self.refresh_single_line_folds(window, cx);
20472 refresh_matching_bracket_highlights(self, window, cx);
20473 if self.has_active_edit_prediction() {
20474 self.update_visible_edit_prediction(window, cx);
20475 }
20476 if let Some(project) = self.project.as_ref()
20477 && let Some(edited_buffer) = edited_buffer
20478 {
20479 project.update(cx, |project, cx| {
20480 self.registered_buffers
20481 .entry(edited_buffer.read(cx).remote_id())
20482 .or_insert_with(|| {
20483 project.register_buffer_with_language_servers(edited_buffer, cx)
20484 });
20485 });
20486 }
20487 cx.emit(EditorEvent::BufferEdited);
20488 cx.emit(SearchEvent::MatchesInvalidated);
20489
20490 if let Some(buffer) = edited_buffer {
20491 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20492 }
20493
20494 if *singleton_buffer_edited {
20495 if let Some(buffer) = edited_buffer
20496 && buffer.read(cx).file().is_none()
20497 {
20498 cx.emit(EditorEvent::TitleChanged);
20499 }
20500 if let Some(project) = &self.project {
20501 #[allow(clippy::mutable_key_type)]
20502 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20503 multibuffer
20504 .all_buffers()
20505 .into_iter()
20506 .filter_map(|buffer| {
20507 buffer.update(cx, |buffer, cx| {
20508 let language = buffer.language()?;
20509 let should_discard = project.update(cx, |project, cx| {
20510 project.is_local()
20511 && !project.has_language_servers_for(buffer, cx)
20512 });
20513 should_discard.not().then_some(language.clone())
20514 })
20515 })
20516 .collect::<HashSet<_>>()
20517 });
20518 if !languages_affected.is_empty() {
20519 self.refresh_inlay_hints(
20520 InlayHintRefreshReason::BufferEdited(languages_affected),
20521 cx,
20522 );
20523 }
20524 }
20525 }
20526
20527 let Some(project) = &self.project else { return };
20528 let (telemetry, is_via_ssh) = {
20529 let project = project.read(cx);
20530 let telemetry = project.client().telemetry().clone();
20531 let is_via_ssh = project.is_via_remote_server();
20532 (telemetry, is_via_ssh)
20533 };
20534 refresh_linked_ranges(self, window, cx);
20535 telemetry.log_edit_event("editor", is_via_ssh);
20536 }
20537 multi_buffer::Event::ExcerptsAdded {
20538 buffer,
20539 predecessor,
20540 excerpts,
20541 } => {
20542 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20543 let buffer_id = buffer.read(cx).remote_id();
20544 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20545 && let Some(project) = &self.project
20546 {
20547 update_uncommitted_diff_for_buffer(
20548 cx.entity(),
20549 project,
20550 [buffer.clone()],
20551 self.buffer.clone(),
20552 cx,
20553 )
20554 .detach();
20555 }
20556 if self.active_diagnostics != ActiveDiagnostic::All {
20557 self.update_lsp_data(false, Some(buffer_id), window, cx);
20558 }
20559 cx.emit(EditorEvent::ExcerptsAdded {
20560 buffer: buffer.clone(),
20561 predecessor: *predecessor,
20562 excerpts: excerpts.clone(),
20563 });
20564 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20565 }
20566 multi_buffer::Event::ExcerptsRemoved {
20567 ids,
20568 removed_buffer_ids,
20569 } => {
20570 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20571 let buffer = self.buffer.read(cx);
20572 self.registered_buffers
20573 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20574 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20575 cx.emit(EditorEvent::ExcerptsRemoved {
20576 ids: ids.clone(),
20577 removed_buffer_ids: removed_buffer_ids.clone(),
20578 });
20579 }
20580 multi_buffer::Event::ExcerptsEdited {
20581 excerpt_ids,
20582 buffer_ids,
20583 } => {
20584 self.display_map.update(cx, |map, cx| {
20585 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20586 });
20587 cx.emit(EditorEvent::ExcerptsEdited {
20588 ids: excerpt_ids.clone(),
20589 });
20590 }
20591 multi_buffer::Event::ExcerptsExpanded { ids } => {
20592 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20593 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20594 }
20595 multi_buffer::Event::Reparsed(buffer_id) => {
20596 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20597 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20598
20599 cx.emit(EditorEvent::Reparsed(*buffer_id));
20600 }
20601 multi_buffer::Event::DiffHunksToggled => {
20602 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20603 }
20604 multi_buffer::Event::LanguageChanged(buffer_id) => {
20605 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20606 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20607 cx.emit(EditorEvent::Reparsed(*buffer_id));
20608 cx.notify();
20609 }
20610 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20611 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20612 multi_buffer::Event::FileHandleChanged
20613 | multi_buffer::Event::Reloaded
20614 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20615 multi_buffer::Event::DiagnosticsUpdated => {
20616 self.update_diagnostics_state(window, cx);
20617 }
20618 _ => {}
20619 };
20620 }
20621
20622 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20623 if !self.diagnostics_enabled() {
20624 return;
20625 }
20626 self.refresh_active_diagnostics(cx);
20627 self.refresh_inline_diagnostics(true, window, cx);
20628 self.scrollbar_marker_state.dirty = true;
20629 cx.notify();
20630 }
20631
20632 pub fn start_temporary_diff_override(&mut self) {
20633 self.load_diff_task.take();
20634 self.temporary_diff_override = true;
20635 }
20636
20637 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20638 self.temporary_diff_override = false;
20639 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20640 self.buffer.update(cx, |buffer, cx| {
20641 buffer.set_all_diff_hunks_collapsed(cx);
20642 });
20643
20644 if let Some(project) = self.project.clone() {
20645 self.load_diff_task = Some(
20646 update_uncommitted_diff_for_buffer(
20647 cx.entity(),
20648 &project,
20649 self.buffer.read(cx).all_buffers(),
20650 self.buffer.clone(),
20651 cx,
20652 )
20653 .shared(),
20654 );
20655 }
20656 }
20657
20658 fn on_display_map_changed(
20659 &mut self,
20660 _: Entity<DisplayMap>,
20661 _: &mut Window,
20662 cx: &mut Context<Self>,
20663 ) {
20664 cx.notify();
20665 }
20666
20667 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20668 if self.diagnostics_enabled() {
20669 let new_severity = EditorSettings::get_global(cx)
20670 .diagnostics_max_severity
20671 .unwrap_or(DiagnosticSeverity::Hint);
20672 self.set_max_diagnostics_severity(new_severity, cx);
20673 }
20674 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20675 self.update_edit_prediction_settings(cx);
20676 self.refresh_edit_prediction(true, false, window, cx);
20677 self.refresh_inline_values(cx);
20678 self.refresh_inlay_hints(
20679 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20680 self.selections.newest_anchor().head(),
20681 &self.buffer.read(cx).snapshot(cx),
20682 cx,
20683 )),
20684 cx,
20685 );
20686
20687 let old_cursor_shape = self.cursor_shape;
20688 let old_show_breadcrumbs = self.show_breadcrumbs;
20689
20690 {
20691 let editor_settings = EditorSettings::get_global(cx);
20692 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20693 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20694 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20695 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20696 }
20697
20698 if old_cursor_shape != self.cursor_shape {
20699 cx.emit(EditorEvent::CursorShapeChanged);
20700 }
20701
20702 if old_show_breadcrumbs != self.show_breadcrumbs {
20703 cx.emit(EditorEvent::BreadcrumbsChanged);
20704 }
20705
20706 let project_settings = ProjectSettings::get_global(cx);
20707 self.serialize_dirty_buffers =
20708 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20709
20710 if self.mode.is_full() {
20711 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20712 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20713 if self.show_inline_diagnostics != show_inline_diagnostics {
20714 self.show_inline_diagnostics = show_inline_diagnostics;
20715 self.refresh_inline_diagnostics(false, window, cx);
20716 }
20717
20718 if self.git_blame_inline_enabled != inline_blame_enabled {
20719 self.toggle_git_blame_inline_internal(false, window, cx);
20720 }
20721
20722 let minimap_settings = EditorSettings::get_global(cx).minimap;
20723 if self.minimap_visibility != MinimapVisibility::Disabled {
20724 if self.minimap_visibility.settings_visibility()
20725 != minimap_settings.minimap_enabled()
20726 {
20727 self.set_minimap_visibility(
20728 MinimapVisibility::for_mode(self.mode(), cx),
20729 window,
20730 cx,
20731 );
20732 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20733 minimap_entity.update(cx, |minimap_editor, cx| {
20734 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20735 })
20736 }
20737 }
20738 }
20739
20740 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20741 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20742 }) {
20743 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20744 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20745 }
20746 self.refresh_colors(false, None, window, cx);
20747 }
20748
20749 cx.notify();
20750 }
20751
20752 pub fn set_searchable(&mut self, searchable: bool) {
20753 self.searchable = searchable;
20754 }
20755
20756 pub fn searchable(&self) -> bool {
20757 self.searchable
20758 }
20759
20760 fn open_proposed_changes_editor(
20761 &mut self,
20762 _: &OpenProposedChangesEditor,
20763 window: &mut Window,
20764 cx: &mut Context<Self>,
20765 ) {
20766 let Some(workspace) = self.workspace() else {
20767 cx.propagate();
20768 return;
20769 };
20770
20771 let selections = self.selections.all::<usize>(cx);
20772 let multi_buffer = self.buffer.read(cx);
20773 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20774 let mut new_selections_by_buffer = HashMap::default();
20775 for selection in selections {
20776 for (buffer, range, _) in
20777 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20778 {
20779 let mut range = range.to_point(buffer);
20780 range.start.column = 0;
20781 range.end.column = buffer.line_len(range.end.row);
20782 new_selections_by_buffer
20783 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20784 .or_insert(Vec::new())
20785 .push(range)
20786 }
20787 }
20788
20789 let proposed_changes_buffers = new_selections_by_buffer
20790 .into_iter()
20791 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20792 .collect::<Vec<_>>();
20793 let proposed_changes_editor = cx.new(|cx| {
20794 ProposedChangesEditor::new(
20795 "Proposed changes",
20796 proposed_changes_buffers,
20797 self.project.clone(),
20798 window,
20799 cx,
20800 )
20801 });
20802
20803 window.defer(cx, move |window, cx| {
20804 workspace.update(cx, |workspace, cx| {
20805 workspace.active_pane().update(cx, |pane, cx| {
20806 pane.add_item(
20807 Box::new(proposed_changes_editor),
20808 true,
20809 true,
20810 None,
20811 window,
20812 cx,
20813 );
20814 });
20815 });
20816 });
20817 }
20818
20819 pub fn open_excerpts_in_split(
20820 &mut self,
20821 _: &OpenExcerptsSplit,
20822 window: &mut Window,
20823 cx: &mut Context<Self>,
20824 ) {
20825 self.open_excerpts_common(None, true, window, cx)
20826 }
20827
20828 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20829 self.open_excerpts_common(None, false, window, cx)
20830 }
20831
20832 fn open_excerpts_common(
20833 &mut self,
20834 jump_data: Option<JumpData>,
20835 split: bool,
20836 window: &mut Window,
20837 cx: &mut Context<Self>,
20838 ) {
20839 let Some(workspace) = self.workspace() else {
20840 cx.propagate();
20841 return;
20842 };
20843
20844 if self.buffer.read(cx).is_singleton() {
20845 cx.propagate();
20846 return;
20847 }
20848
20849 let mut new_selections_by_buffer = HashMap::default();
20850 match &jump_data {
20851 Some(JumpData::MultiBufferPoint {
20852 excerpt_id,
20853 position,
20854 anchor,
20855 line_offset_from_top,
20856 }) => {
20857 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20858 if let Some(buffer) = multi_buffer_snapshot
20859 .buffer_id_for_excerpt(*excerpt_id)
20860 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20861 {
20862 let buffer_snapshot = buffer.read(cx).snapshot();
20863 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20864 language::ToPoint::to_point(anchor, &buffer_snapshot)
20865 } else {
20866 buffer_snapshot.clip_point(*position, Bias::Left)
20867 };
20868 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20869 new_selections_by_buffer.insert(
20870 buffer,
20871 (
20872 vec![jump_to_offset..jump_to_offset],
20873 Some(*line_offset_from_top),
20874 ),
20875 );
20876 }
20877 }
20878 Some(JumpData::MultiBufferRow {
20879 row,
20880 line_offset_from_top,
20881 }) => {
20882 let point = MultiBufferPoint::new(row.0, 0);
20883 if let Some((buffer, buffer_point, _)) =
20884 self.buffer.read(cx).point_to_buffer_point(point, cx)
20885 {
20886 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20887 new_selections_by_buffer
20888 .entry(buffer)
20889 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20890 .0
20891 .push(buffer_offset..buffer_offset)
20892 }
20893 }
20894 None => {
20895 let selections = self.selections.all::<usize>(cx);
20896 let multi_buffer = self.buffer.read(cx);
20897 for selection in selections {
20898 for (snapshot, range, _, anchor) in multi_buffer
20899 .snapshot(cx)
20900 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20901 {
20902 if let Some(anchor) = anchor {
20903 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20904 else {
20905 continue;
20906 };
20907 let offset = text::ToOffset::to_offset(
20908 &anchor.text_anchor,
20909 &buffer_handle.read(cx).snapshot(),
20910 );
20911 let range = offset..offset;
20912 new_selections_by_buffer
20913 .entry(buffer_handle)
20914 .or_insert((Vec::new(), None))
20915 .0
20916 .push(range)
20917 } else {
20918 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20919 else {
20920 continue;
20921 };
20922 new_selections_by_buffer
20923 .entry(buffer_handle)
20924 .or_insert((Vec::new(), None))
20925 .0
20926 .push(range)
20927 }
20928 }
20929 }
20930 }
20931 }
20932
20933 new_selections_by_buffer
20934 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20935
20936 if new_selections_by_buffer.is_empty() {
20937 return;
20938 }
20939
20940 // We defer the pane interaction because we ourselves are a workspace item
20941 // and activating a new item causes the pane to call a method on us reentrantly,
20942 // which panics if we're on the stack.
20943 window.defer(cx, move |window, cx| {
20944 workspace.update(cx, |workspace, cx| {
20945 let pane = if split {
20946 workspace.adjacent_pane(window, cx)
20947 } else {
20948 workspace.active_pane().clone()
20949 };
20950
20951 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20952 let editor = buffer
20953 .read(cx)
20954 .file()
20955 .is_none()
20956 .then(|| {
20957 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20958 // so `workspace.open_project_item` will never find them, always opening a new editor.
20959 // Instead, we try to activate the existing editor in the pane first.
20960 let (editor, pane_item_index) =
20961 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20962 let editor = item.downcast::<Editor>()?;
20963 let singleton_buffer =
20964 editor.read(cx).buffer().read(cx).as_singleton()?;
20965 if singleton_buffer == buffer {
20966 Some((editor, i))
20967 } else {
20968 None
20969 }
20970 })?;
20971 pane.update(cx, |pane, cx| {
20972 pane.activate_item(pane_item_index, true, true, window, cx)
20973 });
20974 Some(editor)
20975 })
20976 .flatten()
20977 .unwrap_or_else(|| {
20978 workspace.open_project_item::<Self>(
20979 pane.clone(),
20980 buffer,
20981 true,
20982 true,
20983 window,
20984 cx,
20985 )
20986 });
20987
20988 editor.update(cx, |editor, cx| {
20989 let autoscroll = match scroll_offset {
20990 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20991 None => Autoscroll::newest(),
20992 };
20993 let nav_history = editor.nav_history.take();
20994 editor.change_selections(
20995 SelectionEffects::scroll(autoscroll),
20996 window,
20997 cx,
20998 |s| {
20999 s.select_ranges(ranges);
21000 },
21001 );
21002 editor.nav_history = nav_history;
21003 });
21004 }
21005 })
21006 });
21007 }
21008
21009 // For now, don't allow opening excerpts in buffers that aren't backed by
21010 // regular project files.
21011 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21012 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21013 }
21014
21015 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21016 let snapshot = self.buffer.read(cx).read(cx);
21017 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21018 Some(
21019 ranges
21020 .iter()
21021 .map(move |range| {
21022 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21023 })
21024 .collect(),
21025 )
21026 }
21027
21028 fn selection_replacement_ranges(
21029 &self,
21030 range: Range<OffsetUtf16>,
21031 cx: &mut App,
21032 ) -> Vec<Range<OffsetUtf16>> {
21033 let selections = self.selections.all::<OffsetUtf16>(cx);
21034 let newest_selection = selections
21035 .iter()
21036 .max_by_key(|selection| selection.id)
21037 .unwrap();
21038 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21039 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21040 let snapshot = self.buffer.read(cx).read(cx);
21041 selections
21042 .into_iter()
21043 .map(|mut selection| {
21044 selection.start.0 =
21045 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21046 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21047 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21048 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21049 })
21050 .collect()
21051 }
21052
21053 fn report_editor_event(
21054 &self,
21055 reported_event: ReportEditorEvent,
21056 file_extension: Option<String>,
21057 cx: &App,
21058 ) {
21059 if cfg!(any(test, feature = "test-support")) {
21060 return;
21061 }
21062
21063 let Some(project) = &self.project else { return };
21064
21065 // If None, we are in a file without an extension
21066 let file = self
21067 .buffer
21068 .read(cx)
21069 .as_singleton()
21070 .and_then(|b| b.read(cx).file());
21071 let file_extension = file_extension.or(file
21072 .as_ref()
21073 .and_then(|file| Path::new(file.file_name(cx)).extension())
21074 .and_then(|e| e.to_str())
21075 .map(|a| a.to_string()));
21076
21077 let vim_mode = vim_enabled(cx);
21078
21079 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21080 let copilot_enabled = edit_predictions_provider
21081 == language::language_settings::EditPredictionProvider::Copilot;
21082 let copilot_enabled_for_language = self
21083 .buffer
21084 .read(cx)
21085 .language_settings(cx)
21086 .show_edit_predictions;
21087
21088 let project = project.read(cx);
21089 let event_type = reported_event.event_type();
21090
21091 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21092 telemetry::event!(
21093 event_type,
21094 type = if auto_saved {"autosave"} else {"manual"},
21095 file_extension,
21096 vim_mode,
21097 copilot_enabled,
21098 copilot_enabled_for_language,
21099 edit_predictions_provider,
21100 is_via_ssh = project.is_via_remote_server(),
21101 );
21102 } else {
21103 telemetry::event!(
21104 event_type,
21105 file_extension,
21106 vim_mode,
21107 copilot_enabled,
21108 copilot_enabled_for_language,
21109 edit_predictions_provider,
21110 is_via_ssh = project.is_via_remote_server(),
21111 );
21112 };
21113 }
21114
21115 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21116 /// with each line being an array of {text, highlight} objects.
21117 fn copy_highlight_json(
21118 &mut self,
21119 _: &CopyHighlightJson,
21120 window: &mut Window,
21121 cx: &mut Context<Self>,
21122 ) {
21123 #[derive(Serialize)]
21124 struct Chunk<'a> {
21125 text: String,
21126 highlight: Option<&'a str>,
21127 }
21128
21129 let snapshot = self.buffer.read(cx).snapshot(cx);
21130 let range = self
21131 .selected_text_range(false, window, cx)
21132 .and_then(|selection| {
21133 if selection.range.is_empty() {
21134 None
21135 } else {
21136 Some(selection.range)
21137 }
21138 })
21139 .unwrap_or_else(|| 0..snapshot.len());
21140
21141 let chunks = snapshot.chunks(range, true);
21142 let mut lines = Vec::new();
21143 let mut line: VecDeque<Chunk> = VecDeque::new();
21144
21145 let Some(style) = self.style.as_ref() else {
21146 return;
21147 };
21148
21149 for chunk in chunks {
21150 let highlight = chunk
21151 .syntax_highlight_id
21152 .and_then(|id| id.name(&style.syntax));
21153 let mut chunk_lines = chunk.text.split('\n').peekable();
21154 while let Some(text) = chunk_lines.next() {
21155 let mut merged_with_last_token = false;
21156 if let Some(last_token) = line.back_mut()
21157 && last_token.highlight == highlight
21158 {
21159 last_token.text.push_str(text);
21160 merged_with_last_token = true;
21161 }
21162
21163 if !merged_with_last_token {
21164 line.push_back(Chunk {
21165 text: text.into(),
21166 highlight,
21167 });
21168 }
21169
21170 if chunk_lines.peek().is_some() {
21171 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21172 line.pop_front();
21173 }
21174 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21175 line.pop_back();
21176 }
21177
21178 lines.push(mem::take(&mut line));
21179 }
21180 }
21181 }
21182
21183 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21184 return;
21185 };
21186 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21187 }
21188
21189 pub fn open_context_menu(
21190 &mut self,
21191 _: &OpenContextMenu,
21192 window: &mut Window,
21193 cx: &mut Context<Self>,
21194 ) {
21195 self.request_autoscroll(Autoscroll::newest(), cx);
21196 let position = self.selections.newest_display(cx).start;
21197 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21198 }
21199
21200 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21201 &self.inlay_hint_cache
21202 }
21203
21204 pub fn replay_insert_event(
21205 &mut self,
21206 text: &str,
21207 relative_utf16_range: Option<Range<isize>>,
21208 window: &mut Window,
21209 cx: &mut Context<Self>,
21210 ) {
21211 if !self.input_enabled {
21212 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21213 return;
21214 }
21215 if let Some(relative_utf16_range) = relative_utf16_range {
21216 let selections = self.selections.all::<OffsetUtf16>(cx);
21217 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21218 let new_ranges = selections.into_iter().map(|range| {
21219 let start = OffsetUtf16(
21220 range
21221 .head()
21222 .0
21223 .saturating_add_signed(relative_utf16_range.start),
21224 );
21225 let end = OffsetUtf16(
21226 range
21227 .head()
21228 .0
21229 .saturating_add_signed(relative_utf16_range.end),
21230 );
21231 start..end
21232 });
21233 s.select_ranges(new_ranges);
21234 });
21235 }
21236
21237 self.handle_input(text, window, cx);
21238 }
21239
21240 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21241 let Some(provider) = self.semantics_provider.as_ref() else {
21242 return false;
21243 };
21244
21245 let mut supports = false;
21246 self.buffer().update(cx, |this, cx| {
21247 this.for_each_buffer(|buffer| {
21248 supports |= provider.supports_inlay_hints(buffer, cx);
21249 });
21250 });
21251
21252 supports
21253 }
21254
21255 pub fn is_focused(&self, window: &Window) -> bool {
21256 self.focus_handle.is_focused(window)
21257 }
21258
21259 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21260 cx.emit(EditorEvent::Focused);
21261
21262 if let Some(descendant) = self
21263 .last_focused_descendant
21264 .take()
21265 .and_then(|descendant| descendant.upgrade())
21266 {
21267 window.focus(&descendant);
21268 } else {
21269 if let Some(blame) = self.blame.as_ref() {
21270 blame.update(cx, GitBlame::focus)
21271 }
21272
21273 self.blink_manager.update(cx, BlinkManager::enable);
21274 self.show_cursor_names(window, cx);
21275 self.buffer.update(cx, |buffer, cx| {
21276 buffer.finalize_last_transaction(cx);
21277 if self.leader_id.is_none() {
21278 buffer.set_active_selections(
21279 &self.selections.disjoint_anchors(),
21280 self.selections.line_mode,
21281 self.cursor_shape,
21282 cx,
21283 );
21284 }
21285 });
21286 }
21287 }
21288
21289 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21290 cx.emit(EditorEvent::FocusedIn)
21291 }
21292
21293 fn handle_focus_out(
21294 &mut self,
21295 event: FocusOutEvent,
21296 _window: &mut Window,
21297 cx: &mut Context<Self>,
21298 ) {
21299 if event.blurred != self.focus_handle {
21300 self.last_focused_descendant = Some(event.blurred);
21301 }
21302 self.selection_drag_state = SelectionDragState::None;
21303 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21304 }
21305
21306 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21307 self.blink_manager.update(cx, BlinkManager::disable);
21308 self.buffer
21309 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21310
21311 if let Some(blame) = self.blame.as_ref() {
21312 blame.update(cx, GitBlame::blur)
21313 }
21314 if !self.hover_state.focused(window, cx) {
21315 hide_hover(self, cx);
21316 }
21317 if !self
21318 .context_menu
21319 .borrow()
21320 .as_ref()
21321 .is_some_and(|context_menu| context_menu.focused(window, cx))
21322 {
21323 self.hide_context_menu(window, cx);
21324 }
21325 self.discard_edit_prediction(false, cx);
21326 cx.emit(EditorEvent::Blurred);
21327 cx.notify();
21328 }
21329
21330 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21331 let mut pending: String = window
21332 .pending_input_keystrokes()
21333 .into_iter()
21334 .flatten()
21335 .filter_map(|keystroke| {
21336 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21337 keystroke.key_char.clone()
21338 } else {
21339 None
21340 }
21341 })
21342 .collect();
21343
21344 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21345 pending = "".to_string();
21346 }
21347
21348 let existing_pending = self
21349 .text_highlights::<PendingInput>(cx)
21350 .map(|(_, ranges)| ranges.to_vec());
21351 if existing_pending.is_none() && pending.is_empty() {
21352 return;
21353 }
21354 let transaction =
21355 self.transact(window, cx, |this, window, cx| {
21356 let selections = this.selections.all::<usize>(cx);
21357 let edits = selections
21358 .iter()
21359 .map(|selection| (selection.end..selection.end, pending.clone()));
21360 this.edit(edits, cx);
21361 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21362 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21363 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21364 }));
21365 });
21366 if let Some(existing_ranges) = existing_pending {
21367 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21368 this.edit(edits, cx);
21369 }
21370 });
21371
21372 let snapshot = self.snapshot(window, cx);
21373 let ranges = self
21374 .selections
21375 .all::<usize>(cx)
21376 .into_iter()
21377 .map(|selection| {
21378 snapshot.buffer_snapshot.anchor_after(selection.end)
21379 ..snapshot
21380 .buffer_snapshot
21381 .anchor_before(selection.end + pending.len())
21382 })
21383 .collect();
21384
21385 if pending.is_empty() {
21386 self.clear_highlights::<PendingInput>(cx);
21387 } else {
21388 self.highlight_text::<PendingInput>(
21389 ranges,
21390 HighlightStyle {
21391 underline: Some(UnderlineStyle {
21392 thickness: px(1.),
21393 color: None,
21394 wavy: false,
21395 }),
21396 ..Default::default()
21397 },
21398 cx,
21399 );
21400 }
21401
21402 self.ime_transaction = self.ime_transaction.or(transaction);
21403 if let Some(transaction) = self.ime_transaction {
21404 self.buffer.update(cx, |buffer, cx| {
21405 buffer.group_until_transaction(transaction, cx);
21406 });
21407 }
21408
21409 if self.text_highlights::<PendingInput>(cx).is_none() {
21410 self.ime_transaction.take();
21411 }
21412 }
21413
21414 pub fn register_action_renderer(
21415 &mut self,
21416 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21417 ) -> Subscription {
21418 let id = self.next_editor_action_id.post_inc();
21419 self.editor_actions
21420 .borrow_mut()
21421 .insert(id, Box::new(listener));
21422
21423 let editor_actions = self.editor_actions.clone();
21424 Subscription::new(move || {
21425 editor_actions.borrow_mut().remove(&id);
21426 })
21427 }
21428
21429 pub fn register_action<A: Action>(
21430 &mut self,
21431 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21432 ) -> Subscription {
21433 let id = self.next_editor_action_id.post_inc();
21434 let listener = Arc::new(listener);
21435 self.editor_actions.borrow_mut().insert(
21436 id,
21437 Box::new(move |_, window, _| {
21438 let listener = listener.clone();
21439 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21440 let action = action.downcast_ref().unwrap();
21441 if phase == DispatchPhase::Bubble {
21442 listener(action, window, cx)
21443 }
21444 })
21445 }),
21446 );
21447
21448 let editor_actions = self.editor_actions.clone();
21449 Subscription::new(move || {
21450 editor_actions.borrow_mut().remove(&id);
21451 })
21452 }
21453
21454 pub fn file_header_size(&self) -> u32 {
21455 FILE_HEADER_HEIGHT
21456 }
21457
21458 pub fn restore(
21459 &mut self,
21460 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21461 window: &mut Window,
21462 cx: &mut Context<Self>,
21463 ) {
21464 let workspace = self.workspace();
21465 let project = self.project();
21466 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21467 let mut tasks = Vec::new();
21468 for (buffer_id, changes) in revert_changes {
21469 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21470 buffer.update(cx, |buffer, cx| {
21471 buffer.edit(
21472 changes
21473 .into_iter()
21474 .map(|(range, text)| (range, text.to_string())),
21475 None,
21476 cx,
21477 );
21478 });
21479
21480 if let Some(project) =
21481 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21482 {
21483 project.update(cx, |project, cx| {
21484 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21485 })
21486 }
21487 }
21488 }
21489 tasks
21490 });
21491 cx.spawn_in(window, async move |_, cx| {
21492 for (buffer, task) in save_tasks {
21493 let result = task.await;
21494 if result.is_err() {
21495 let Some(path) = buffer
21496 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21497 .ok()
21498 else {
21499 continue;
21500 };
21501 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21502 let Some(task) = cx
21503 .update_window_entity(workspace, |workspace, window, cx| {
21504 workspace
21505 .open_path_preview(path, None, false, false, false, window, cx)
21506 })
21507 .ok()
21508 else {
21509 continue;
21510 };
21511 task.await.log_err();
21512 }
21513 }
21514 }
21515 })
21516 .detach();
21517 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21518 selections.refresh()
21519 });
21520 }
21521
21522 pub fn to_pixel_point(
21523 &self,
21524 source: multi_buffer::Anchor,
21525 editor_snapshot: &EditorSnapshot,
21526 window: &mut Window,
21527 ) -> Option<gpui::Point<Pixels>> {
21528 let source_point = source.to_display_point(editor_snapshot);
21529 self.display_to_pixel_point(source_point, editor_snapshot, window)
21530 }
21531
21532 pub fn display_to_pixel_point(
21533 &self,
21534 source: DisplayPoint,
21535 editor_snapshot: &EditorSnapshot,
21536 window: &mut Window,
21537 ) -> Option<gpui::Point<Pixels>> {
21538 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21539 let text_layout_details = self.text_layout_details(window);
21540 let scroll_top = text_layout_details
21541 .scroll_anchor
21542 .scroll_position(editor_snapshot)
21543 .y;
21544
21545 if source.row().as_f32() < scroll_top.floor() {
21546 return None;
21547 }
21548 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21549 let source_y = line_height * (source.row().as_f32() - scroll_top);
21550 Some(gpui::Point::new(source_x, source_y))
21551 }
21552
21553 pub fn has_visible_completions_menu(&self) -> bool {
21554 !self.edit_prediction_preview_is_active()
21555 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21556 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21557 })
21558 }
21559
21560 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21561 if self.mode.is_minimap() {
21562 return;
21563 }
21564 self.addons
21565 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21566 }
21567
21568 pub fn unregister_addon<T: Addon>(&mut self) {
21569 self.addons.remove(&std::any::TypeId::of::<T>());
21570 }
21571
21572 pub fn addon<T: Addon>(&self) -> Option<&T> {
21573 let type_id = std::any::TypeId::of::<T>();
21574 self.addons
21575 .get(&type_id)
21576 .and_then(|item| item.to_any().downcast_ref::<T>())
21577 }
21578
21579 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21580 let type_id = std::any::TypeId::of::<T>();
21581 self.addons
21582 .get_mut(&type_id)
21583 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21584 }
21585
21586 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21587 let text_layout_details = self.text_layout_details(window);
21588 let style = &text_layout_details.editor_style;
21589 let font_id = window.text_system().resolve_font(&style.text.font());
21590 let font_size = style.text.font_size.to_pixels(window.rem_size());
21591 let line_height = style.text.line_height_in_pixels(window.rem_size());
21592 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21593 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21594
21595 CharacterDimensions {
21596 em_width,
21597 em_advance,
21598 line_height,
21599 }
21600 }
21601
21602 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21603 self.load_diff_task.clone()
21604 }
21605
21606 fn read_metadata_from_db(
21607 &mut self,
21608 item_id: u64,
21609 workspace_id: WorkspaceId,
21610 window: &mut Window,
21611 cx: &mut Context<Editor>,
21612 ) {
21613 if self.is_singleton(cx)
21614 && !self.mode.is_minimap()
21615 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21616 {
21617 let buffer_snapshot = OnceCell::new();
21618
21619 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21620 && !folds.is_empty()
21621 {
21622 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21623 self.fold_ranges(
21624 folds
21625 .into_iter()
21626 .map(|(start, end)| {
21627 snapshot.clip_offset(start, Bias::Left)
21628 ..snapshot.clip_offset(end, Bias::Right)
21629 })
21630 .collect(),
21631 false,
21632 window,
21633 cx,
21634 );
21635 }
21636
21637 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21638 && !selections.is_empty()
21639 {
21640 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21641 // skip adding the initial selection to selection history
21642 self.selection_history.mode = SelectionHistoryMode::Skipping;
21643 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21644 s.select_ranges(selections.into_iter().map(|(start, end)| {
21645 snapshot.clip_offset(start, Bias::Left)
21646 ..snapshot.clip_offset(end, Bias::Right)
21647 }));
21648 });
21649 self.selection_history.mode = SelectionHistoryMode::Normal;
21650 };
21651 }
21652
21653 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21654 }
21655
21656 fn update_lsp_data(
21657 &mut self,
21658 ignore_cache: bool,
21659 for_buffer: Option<BufferId>,
21660 window: &mut Window,
21661 cx: &mut Context<'_, Self>,
21662 ) {
21663 self.pull_diagnostics(for_buffer, window, cx);
21664 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21665 }
21666}
21667
21668fn vim_enabled(cx: &App) -> bool {
21669 cx.global::<SettingsStore>()
21670 .raw_user_settings()
21671 .get("vim_mode")
21672 == Some(&serde_json::Value::Bool(true))
21673}
21674
21675fn process_completion_for_edit(
21676 completion: &Completion,
21677 intent: CompletionIntent,
21678 buffer: &Entity<Buffer>,
21679 cursor_position: &text::Anchor,
21680 cx: &mut Context<Editor>,
21681) -> CompletionEdit {
21682 let buffer = buffer.read(cx);
21683 let buffer_snapshot = buffer.snapshot();
21684 let (snippet, new_text) = if completion.is_snippet() {
21685 // Workaround for typescript language server issues so that methods don't expand within
21686 // strings and functions with type expressions. The previous point is used because the query
21687 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21688 let mut snippet_source = completion.new_text.clone();
21689 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21690 previous_point.column = previous_point.column.saturating_sub(1);
21691 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21692 && scope.prefers_label_for_snippet_in_completion()
21693 && let Some(label) = completion.label()
21694 && matches!(
21695 completion.kind(),
21696 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21697 )
21698 {
21699 snippet_source = label;
21700 }
21701 match Snippet::parse(&snippet_source).log_err() {
21702 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21703 None => (None, completion.new_text.clone()),
21704 }
21705 } else {
21706 (None, completion.new_text.clone())
21707 };
21708
21709 let mut range_to_replace = {
21710 let replace_range = &completion.replace_range;
21711 if let CompletionSource::Lsp {
21712 insert_range: Some(insert_range),
21713 ..
21714 } = &completion.source
21715 {
21716 debug_assert_eq!(
21717 insert_range.start, replace_range.start,
21718 "insert_range and replace_range should start at the same position"
21719 );
21720 debug_assert!(
21721 insert_range
21722 .start
21723 .cmp(cursor_position, &buffer_snapshot)
21724 .is_le(),
21725 "insert_range should start before or at cursor position"
21726 );
21727 debug_assert!(
21728 replace_range
21729 .start
21730 .cmp(cursor_position, &buffer_snapshot)
21731 .is_le(),
21732 "replace_range should start before or at cursor position"
21733 );
21734
21735 let should_replace = match intent {
21736 CompletionIntent::CompleteWithInsert => false,
21737 CompletionIntent::CompleteWithReplace => true,
21738 CompletionIntent::Complete | CompletionIntent::Compose => {
21739 let insert_mode =
21740 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21741 .completions
21742 .lsp_insert_mode;
21743 match insert_mode {
21744 LspInsertMode::Insert => false,
21745 LspInsertMode::Replace => true,
21746 LspInsertMode::ReplaceSubsequence => {
21747 let mut text_to_replace = buffer.chars_for_range(
21748 buffer.anchor_before(replace_range.start)
21749 ..buffer.anchor_after(replace_range.end),
21750 );
21751 let mut current_needle = text_to_replace.next();
21752 for haystack_ch in completion.label.text.chars() {
21753 if let Some(needle_ch) = current_needle
21754 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21755 {
21756 current_needle = text_to_replace.next();
21757 }
21758 }
21759 current_needle.is_none()
21760 }
21761 LspInsertMode::ReplaceSuffix => {
21762 if replace_range
21763 .end
21764 .cmp(cursor_position, &buffer_snapshot)
21765 .is_gt()
21766 {
21767 let range_after_cursor = *cursor_position..replace_range.end;
21768 let text_after_cursor = buffer
21769 .text_for_range(
21770 buffer.anchor_before(range_after_cursor.start)
21771 ..buffer.anchor_after(range_after_cursor.end),
21772 )
21773 .collect::<String>()
21774 .to_ascii_lowercase();
21775 completion
21776 .label
21777 .text
21778 .to_ascii_lowercase()
21779 .ends_with(&text_after_cursor)
21780 } else {
21781 true
21782 }
21783 }
21784 }
21785 }
21786 };
21787
21788 if should_replace {
21789 replace_range.clone()
21790 } else {
21791 insert_range.clone()
21792 }
21793 } else {
21794 replace_range.clone()
21795 }
21796 };
21797
21798 if range_to_replace
21799 .end
21800 .cmp(cursor_position, &buffer_snapshot)
21801 .is_lt()
21802 {
21803 range_to_replace.end = *cursor_position;
21804 }
21805
21806 CompletionEdit {
21807 new_text,
21808 replace_range: range_to_replace.to_offset(buffer),
21809 snippet,
21810 }
21811}
21812
21813struct CompletionEdit {
21814 new_text: String,
21815 replace_range: Range<usize>,
21816 snippet: Option<Snippet>,
21817}
21818
21819fn insert_extra_newline_brackets(
21820 buffer: &MultiBufferSnapshot,
21821 range: Range<usize>,
21822 language: &language::LanguageScope,
21823) -> bool {
21824 let leading_whitespace_len = buffer
21825 .reversed_chars_at(range.start)
21826 .take_while(|c| c.is_whitespace() && *c != '\n')
21827 .map(|c| c.len_utf8())
21828 .sum::<usize>();
21829 let trailing_whitespace_len = buffer
21830 .chars_at(range.end)
21831 .take_while(|c| c.is_whitespace() && *c != '\n')
21832 .map(|c| c.len_utf8())
21833 .sum::<usize>();
21834 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21835
21836 language.brackets().any(|(pair, enabled)| {
21837 let pair_start = pair.start.trim_end();
21838 let pair_end = pair.end.trim_start();
21839
21840 enabled
21841 && pair.newline
21842 && buffer.contains_str_at(range.end, pair_end)
21843 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21844 })
21845}
21846
21847fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21848 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21849 [(buffer, range, _)] => (*buffer, range.clone()),
21850 _ => return false,
21851 };
21852 let pair = {
21853 let mut result: Option<BracketMatch> = None;
21854
21855 for pair in buffer
21856 .all_bracket_ranges(range.clone())
21857 .filter(move |pair| {
21858 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21859 })
21860 {
21861 let len = pair.close_range.end - pair.open_range.start;
21862
21863 if let Some(existing) = &result {
21864 let existing_len = existing.close_range.end - existing.open_range.start;
21865 if len > existing_len {
21866 continue;
21867 }
21868 }
21869
21870 result = Some(pair);
21871 }
21872
21873 result
21874 };
21875 let Some(pair) = pair else {
21876 return false;
21877 };
21878 pair.newline_only
21879 && buffer
21880 .chars_for_range(pair.open_range.end..range.start)
21881 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21882 .all(|c| c.is_whitespace() && c != '\n')
21883}
21884
21885fn update_uncommitted_diff_for_buffer(
21886 editor: Entity<Editor>,
21887 project: &Entity<Project>,
21888 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21889 buffer: Entity<MultiBuffer>,
21890 cx: &mut App,
21891) -> Task<()> {
21892 let mut tasks = Vec::new();
21893 project.update(cx, |project, cx| {
21894 for buffer in buffers {
21895 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21896 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21897 }
21898 }
21899 });
21900 cx.spawn(async move |cx| {
21901 let diffs = future::join_all(tasks).await;
21902 if editor
21903 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21904 .unwrap_or(false)
21905 {
21906 return;
21907 }
21908
21909 buffer
21910 .update(cx, |buffer, cx| {
21911 for diff in diffs.into_iter().flatten() {
21912 buffer.add_diff(diff, cx);
21913 }
21914 })
21915 .ok();
21916 })
21917}
21918
21919fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21920 let tab_size = tab_size.get() as usize;
21921 let mut width = offset;
21922
21923 for ch in text.chars() {
21924 width += if ch == '\t' {
21925 tab_size - (width % tab_size)
21926 } else {
21927 1
21928 };
21929 }
21930
21931 width - offset
21932}
21933
21934#[cfg(test)]
21935mod tests {
21936 use super::*;
21937
21938 #[test]
21939 fn test_string_size_with_expanded_tabs() {
21940 let nz = |val| NonZeroU32::new(val).unwrap();
21941 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21942 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21943 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21944 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21945 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21946 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21947 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21948 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21949 }
21950}
21951
21952/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21953struct WordBreakingTokenizer<'a> {
21954 input: &'a str,
21955}
21956
21957impl<'a> WordBreakingTokenizer<'a> {
21958 fn new(input: &'a str) -> Self {
21959 Self { input }
21960 }
21961}
21962
21963fn is_char_ideographic(ch: char) -> bool {
21964 use unicode_script::Script::*;
21965 use unicode_script::UnicodeScript;
21966 matches!(ch.script(), Han | Tangut | Yi)
21967}
21968
21969fn is_grapheme_ideographic(text: &str) -> bool {
21970 text.chars().any(is_char_ideographic)
21971}
21972
21973fn is_grapheme_whitespace(text: &str) -> bool {
21974 text.chars().any(|x| x.is_whitespace())
21975}
21976
21977fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21978 text.chars()
21979 .next()
21980 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21981}
21982
21983#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21984enum WordBreakToken<'a> {
21985 Word { token: &'a str, grapheme_len: usize },
21986 InlineWhitespace { token: &'a str, grapheme_len: usize },
21987 Newline,
21988}
21989
21990impl<'a> Iterator for WordBreakingTokenizer<'a> {
21991 /// Yields a span, the count of graphemes in the token, and whether it was
21992 /// whitespace. Note that it also breaks at word boundaries.
21993 type Item = WordBreakToken<'a>;
21994
21995 fn next(&mut self) -> Option<Self::Item> {
21996 use unicode_segmentation::UnicodeSegmentation;
21997 if self.input.is_empty() {
21998 return None;
21999 }
22000
22001 let mut iter = self.input.graphemes(true).peekable();
22002 let mut offset = 0;
22003 let mut grapheme_len = 0;
22004 if let Some(first_grapheme) = iter.next() {
22005 let is_newline = first_grapheme == "\n";
22006 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22007 offset += first_grapheme.len();
22008 grapheme_len += 1;
22009 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22010 if let Some(grapheme) = iter.peek().copied()
22011 && should_stay_with_preceding_ideograph(grapheme)
22012 {
22013 offset += grapheme.len();
22014 grapheme_len += 1;
22015 }
22016 } else {
22017 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22018 let mut next_word_bound = words.peek().copied();
22019 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22020 next_word_bound = words.next();
22021 }
22022 while let Some(grapheme) = iter.peek().copied() {
22023 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22024 break;
22025 };
22026 if is_grapheme_whitespace(grapheme) != is_whitespace
22027 || (grapheme == "\n") != is_newline
22028 {
22029 break;
22030 };
22031 offset += grapheme.len();
22032 grapheme_len += 1;
22033 iter.next();
22034 }
22035 }
22036 let token = &self.input[..offset];
22037 self.input = &self.input[offset..];
22038 if token == "\n" {
22039 Some(WordBreakToken::Newline)
22040 } else if is_whitespace {
22041 Some(WordBreakToken::InlineWhitespace {
22042 token,
22043 grapheme_len,
22044 })
22045 } else {
22046 Some(WordBreakToken::Word {
22047 token,
22048 grapheme_len,
22049 })
22050 }
22051 } else {
22052 None
22053 }
22054 }
22055}
22056
22057#[test]
22058fn test_word_breaking_tokenizer() {
22059 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22060 ("", &[]),
22061 (" ", &[whitespace(" ", 2)]),
22062 ("Ʒ", &[word("Ʒ", 1)]),
22063 ("Ǽ", &[word("Ǽ", 1)]),
22064 ("⋑", &[word("⋑", 1)]),
22065 ("⋑⋑", &[word("⋑⋑", 2)]),
22066 (
22067 "原理,进而",
22068 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22069 ),
22070 (
22071 "hello world",
22072 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22073 ),
22074 (
22075 "hello, world",
22076 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22077 ),
22078 (
22079 " hello world",
22080 &[
22081 whitespace(" ", 2),
22082 word("hello", 5),
22083 whitespace(" ", 1),
22084 word("world", 5),
22085 ],
22086 ),
22087 (
22088 "这是什么 \n 钢笔",
22089 &[
22090 word("这", 1),
22091 word("是", 1),
22092 word("什", 1),
22093 word("么", 1),
22094 whitespace(" ", 1),
22095 newline(),
22096 whitespace(" ", 1),
22097 word("钢", 1),
22098 word("笔", 1),
22099 ],
22100 ),
22101 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22102 ];
22103
22104 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22105 WordBreakToken::Word {
22106 token,
22107 grapheme_len,
22108 }
22109 }
22110
22111 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22112 WordBreakToken::InlineWhitespace {
22113 token,
22114 grapheme_len,
22115 }
22116 }
22117
22118 fn newline() -> WordBreakToken<'static> {
22119 WordBreakToken::Newline
22120 }
22121
22122 for (input, result) in tests {
22123 assert_eq!(
22124 WordBreakingTokenizer::new(input)
22125 .collect::<Vec<_>>()
22126 .as_slice(),
22127 *result,
22128 );
22129 }
22130}
22131
22132fn wrap_with_prefix(
22133 first_line_prefix: String,
22134 subsequent_lines_prefix: String,
22135 unwrapped_text: String,
22136 wrap_column: usize,
22137 tab_size: NonZeroU32,
22138 preserve_existing_whitespace: bool,
22139) -> String {
22140 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22141 let subsequent_lines_prefix_len =
22142 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22143 let mut wrapped_text = String::new();
22144 let mut current_line = first_line_prefix;
22145 let mut is_first_line = true;
22146
22147 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22148 let mut current_line_len = first_line_prefix_len;
22149 let mut in_whitespace = false;
22150 for token in tokenizer {
22151 let have_preceding_whitespace = in_whitespace;
22152 match token {
22153 WordBreakToken::Word {
22154 token,
22155 grapheme_len,
22156 } => {
22157 in_whitespace = false;
22158 let current_prefix_len = if is_first_line {
22159 first_line_prefix_len
22160 } else {
22161 subsequent_lines_prefix_len
22162 };
22163 if current_line_len + grapheme_len > wrap_column
22164 && current_line_len != current_prefix_len
22165 {
22166 wrapped_text.push_str(current_line.trim_end());
22167 wrapped_text.push('\n');
22168 is_first_line = false;
22169 current_line = subsequent_lines_prefix.clone();
22170 current_line_len = subsequent_lines_prefix_len;
22171 }
22172 current_line.push_str(token);
22173 current_line_len += grapheme_len;
22174 }
22175 WordBreakToken::InlineWhitespace {
22176 mut token,
22177 mut grapheme_len,
22178 } => {
22179 in_whitespace = true;
22180 if have_preceding_whitespace && !preserve_existing_whitespace {
22181 continue;
22182 }
22183 if !preserve_existing_whitespace {
22184 token = " ";
22185 grapheme_len = 1;
22186 }
22187 let current_prefix_len = if is_first_line {
22188 first_line_prefix_len
22189 } else {
22190 subsequent_lines_prefix_len
22191 };
22192 if current_line_len + grapheme_len > wrap_column {
22193 wrapped_text.push_str(current_line.trim_end());
22194 wrapped_text.push('\n');
22195 is_first_line = false;
22196 current_line = subsequent_lines_prefix.clone();
22197 current_line_len = subsequent_lines_prefix_len;
22198 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22199 current_line.push_str(token);
22200 current_line_len += grapheme_len;
22201 }
22202 }
22203 WordBreakToken::Newline => {
22204 in_whitespace = true;
22205 let current_prefix_len = if is_first_line {
22206 first_line_prefix_len
22207 } else {
22208 subsequent_lines_prefix_len
22209 };
22210 if preserve_existing_whitespace {
22211 wrapped_text.push_str(current_line.trim_end());
22212 wrapped_text.push('\n');
22213 is_first_line = false;
22214 current_line = subsequent_lines_prefix.clone();
22215 current_line_len = subsequent_lines_prefix_len;
22216 } else if have_preceding_whitespace {
22217 continue;
22218 } else if current_line_len + 1 > wrap_column
22219 && current_line_len != current_prefix_len
22220 {
22221 wrapped_text.push_str(current_line.trim_end());
22222 wrapped_text.push('\n');
22223 is_first_line = false;
22224 current_line = subsequent_lines_prefix.clone();
22225 current_line_len = subsequent_lines_prefix_len;
22226 } else if current_line_len != current_prefix_len {
22227 current_line.push(' ');
22228 current_line_len += 1;
22229 }
22230 }
22231 }
22232 }
22233
22234 if !current_line.is_empty() {
22235 wrapped_text.push_str(¤t_line);
22236 }
22237 wrapped_text
22238}
22239
22240#[test]
22241fn test_wrap_with_prefix() {
22242 assert_eq!(
22243 wrap_with_prefix(
22244 "# ".to_string(),
22245 "# ".to_string(),
22246 "abcdefg".to_string(),
22247 4,
22248 NonZeroU32::new(4).unwrap(),
22249 false,
22250 ),
22251 "# abcdefg"
22252 );
22253 assert_eq!(
22254 wrap_with_prefix(
22255 "".to_string(),
22256 "".to_string(),
22257 "\thello world".to_string(),
22258 8,
22259 NonZeroU32::new(4).unwrap(),
22260 false,
22261 ),
22262 "hello\nworld"
22263 );
22264 assert_eq!(
22265 wrap_with_prefix(
22266 "// ".to_string(),
22267 "// ".to_string(),
22268 "xx \nyy zz aa bb cc".to_string(),
22269 12,
22270 NonZeroU32::new(4).unwrap(),
22271 false,
22272 ),
22273 "// xx yy zz\n// aa bb cc"
22274 );
22275 assert_eq!(
22276 wrap_with_prefix(
22277 String::new(),
22278 String::new(),
22279 "这是什么 \n 钢笔".to_string(),
22280 3,
22281 NonZeroU32::new(4).unwrap(),
22282 false,
22283 ),
22284 "这是什\n么 钢\n笔"
22285 );
22286}
22287
22288pub trait CollaborationHub {
22289 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22290 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22291 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22292}
22293
22294impl CollaborationHub for Entity<Project> {
22295 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22296 self.read(cx).collaborators()
22297 }
22298
22299 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22300 self.read(cx).user_store().read(cx).participant_indices()
22301 }
22302
22303 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22304 let this = self.read(cx);
22305 let user_ids = this.collaborators().values().map(|c| c.user_id);
22306 this.user_store().read(cx).participant_names(user_ids, cx)
22307 }
22308}
22309
22310pub trait SemanticsProvider {
22311 fn hover(
22312 &self,
22313 buffer: &Entity<Buffer>,
22314 position: text::Anchor,
22315 cx: &mut App,
22316 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22317
22318 fn inline_values(
22319 &self,
22320 buffer_handle: Entity<Buffer>,
22321 range: Range<text::Anchor>,
22322 cx: &mut App,
22323 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22324
22325 fn inlay_hints(
22326 &self,
22327 buffer_handle: Entity<Buffer>,
22328 range: Range<text::Anchor>,
22329 cx: &mut App,
22330 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22331
22332 fn resolve_inlay_hint(
22333 &self,
22334 hint: InlayHint,
22335 buffer_handle: Entity<Buffer>,
22336 server_id: LanguageServerId,
22337 cx: &mut App,
22338 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22339
22340 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22341
22342 fn document_highlights(
22343 &self,
22344 buffer: &Entity<Buffer>,
22345 position: text::Anchor,
22346 cx: &mut App,
22347 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22348
22349 fn definitions(
22350 &self,
22351 buffer: &Entity<Buffer>,
22352 position: text::Anchor,
22353 kind: GotoDefinitionKind,
22354 cx: &mut App,
22355 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22356
22357 fn range_for_rename(
22358 &self,
22359 buffer: &Entity<Buffer>,
22360 position: text::Anchor,
22361 cx: &mut App,
22362 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22363
22364 fn perform_rename(
22365 &self,
22366 buffer: &Entity<Buffer>,
22367 position: text::Anchor,
22368 new_name: String,
22369 cx: &mut App,
22370 ) -> Option<Task<Result<ProjectTransaction>>>;
22371}
22372
22373pub trait CompletionProvider {
22374 fn completions(
22375 &self,
22376 excerpt_id: ExcerptId,
22377 buffer: &Entity<Buffer>,
22378 buffer_position: text::Anchor,
22379 trigger: CompletionContext,
22380 window: &mut Window,
22381 cx: &mut Context<Editor>,
22382 ) -> Task<Result<Vec<CompletionResponse>>>;
22383
22384 fn resolve_completions(
22385 &self,
22386 _buffer: Entity<Buffer>,
22387 _completion_indices: Vec<usize>,
22388 _completions: Rc<RefCell<Box<[Completion]>>>,
22389 _cx: &mut Context<Editor>,
22390 ) -> Task<Result<bool>> {
22391 Task::ready(Ok(false))
22392 }
22393
22394 fn apply_additional_edits_for_completion(
22395 &self,
22396 _buffer: Entity<Buffer>,
22397 _completions: Rc<RefCell<Box<[Completion]>>>,
22398 _completion_index: usize,
22399 _push_to_history: bool,
22400 _cx: &mut Context<Editor>,
22401 ) -> Task<Result<Option<language::Transaction>>> {
22402 Task::ready(Ok(None))
22403 }
22404
22405 fn is_completion_trigger(
22406 &self,
22407 buffer: &Entity<Buffer>,
22408 position: language::Anchor,
22409 text: &str,
22410 trigger_in_words: bool,
22411 menu_is_open: bool,
22412 cx: &mut Context<Editor>,
22413 ) -> bool;
22414
22415 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22416
22417 fn sort_completions(&self) -> bool {
22418 true
22419 }
22420
22421 fn filter_completions(&self) -> bool {
22422 true
22423 }
22424}
22425
22426pub trait CodeActionProvider {
22427 fn id(&self) -> Arc<str>;
22428
22429 fn code_actions(
22430 &self,
22431 buffer: &Entity<Buffer>,
22432 range: Range<text::Anchor>,
22433 window: &mut Window,
22434 cx: &mut App,
22435 ) -> Task<Result<Vec<CodeAction>>>;
22436
22437 fn apply_code_action(
22438 &self,
22439 buffer_handle: Entity<Buffer>,
22440 action: CodeAction,
22441 excerpt_id: ExcerptId,
22442 push_to_history: bool,
22443 window: &mut Window,
22444 cx: &mut App,
22445 ) -> Task<Result<ProjectTransaction>>;
22446}
22447
22448impl CodeActionProvider for Entity<Project> {
22449 fn id(&self) -> Arc<str> {
22450 "project".into()
22451 }
22452
22453 fn code_actions(
22454 &self,
22455 buffer: &Entity<Buffer>,
22456 range: Range<text::Anchor>,
22457 _window: &mut Window,
22458 cx: &mut App,
22459 ) -> Task<Result<Vec<CodeAction>>> {
22460 self.update(cx, |project, cx| {
22461 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22462 let code_actions = project.code_actions(buffer, range, None, cx);
22463 cx.background_spawn(async move {
22464 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22465 Ok(code_lens_actions
22466 .context("code lens fetch")?
22467 .into_iter()
22468 .flatten()
22469 .chain(
22470 code_actions
22471 .context("code action fetch")?
22472 .into_iter()
22473 .flatten(),
22474 )
22475 .collect())
22476 })
22477 })
22478 }
22479
22480 fn apply_code_action(
22481 &self,
22482 buffer_handle: Entity<Buffer>,
22483 action: CodeAction,
22484 _excerpt_id: ExcerptId,
22485 push_to_history: bool,
22486 _window: &mut Window,
22487 cx: &mut App,
22488 ) -> Task<Result<ProjectTransaction>> {
22489 self.update(cx, |project, cx| {
22490 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22491 })
22492 }
22493}
22494
22495fn snippet_completions(
22496 project: &Project,
22497 buffer: &Entity<Buffer>,
22498 buffer_position: text::Anchor,
22499 cx: &mut App,
22500) -> Task<Result<CompletionResponse>> {
22501 let languages = buffer.read(cx).languages_at(buffer_position);
22502 let snippet_store = project.snippets().read(cx);
22503
22504 let scopes: Vec<_> = languages
22505 .iter()
22506 .filter_map(|language| {
22507 let language_name = language.lsp_id();
22508 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22509
22510 if snippets.is_empty() {
22511 None
22512 } else {
22513 Some((language.default_scope(), snippets))
22514 }
22515 })
22516 .collect();
22517
22518 if scopes.is_empty() {
22519 return Task::ready(Ok(CompletionResponse {
22520 completions: vec![],
22521 display_options: CompletionDisplayOptions::default(),
22522 is_incomplete: false,
22523 }));
22524 }
22525
22526 let snapshot = buffer.read(cx).text_snapshot();
22527 let chars: String = snapshot
22528 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22529 .collect();
22530 let executor = cx.background_executor().clone();
22531
22532 cx.background_spawn(async move {
22533 let mut is_incomplete = false;
22534 let mut completions: Vec<Completion> = Vec::new();
22535 for (scope, snippets) in scopes.into_iter() {
22536 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22537 let mut last_word = chars
22538 .chars()
22539 .take_while(|c| classifier.is_word(*c))
22540 .collect::<String>();
22541 last_word = last_word.chars().rev().collect();
22542
22543 if last_word.is_empty() {
22544 return Ok(CompletionResponse {
22545 completions: vec![],
22546 display_options: CompletionDisplayOptions::default(),
22547 is_incomplete: true,
22548 });
22549 }
22550
22551 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22552 let to_lsp = |point: &text::Anchor| {
22553 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22554 point_to_lsp(end)
22555 };
22556 let lsp_end = to_lsp(&buffer_position);
22557
22558 let candidates = snippets
22559 .iter()
22560 .enumerate()
22561 .flat_map(|(ix, snippet)| {
22562 snippet
22563 .prefix
22564 .iter()
22565 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22566 })
22567 .collect::<Vec<StringMatchCandidate>>();
22568
22569 const MAX_RESULTS: usize = 100;
22570 let mut matches = fuzzy::match_strings(
22571 &candidates,
22572 &last_word,
22573 last_word.chars().any(|c| c.is_uppercase()),
22574 true,
22575 MAX_RESULTS,
22576 &Default::default(),
22577 executor.clone(),
22578 )
22579 .await;
22580
22581 if matches.len() >= MAX_RESULTS {
22582 is_incomplete = true;
22583 }
22584
22585 // Remove all candidates where the query's start does not match the start of any word in the candidate
22586 if let Some(query_start) = last_word.chars().next() {
22587 matches.retain(|string_match| {
22588 split_words(&string_match.string).any(|word| {
22589 // Check that the first codepoint of the word as lowercase matches the first
22590 // codepoint of the query as lowercase
22591 word.chars()
22592 .flat_map(|codepoint| codepoint.to_lowercase())
22593 .zip(query_start.to_lowercase())
22594 .all(|(word_cp, query_cp)| word_cp == query_cp)
22595 })
22596 });
22597 }
22598
22599 let matched_strings = matches
22600 .into_iter()
22601 .map(|m| m.string)
22602 .collect::<HashSet<_>>();
22603
22604 completions.extend(snippets.iter().filter_map(|snippet| {
22605 let matching_prefix = snippet
22606 .prefix
22607 .iter()
22608 .find(|prefix| matched_strings.contains(*prefix))?;
22609 let start = as_offset - last_word.len();
22610 let start = snapshot.anchor_before(start);
22611 let range = start..buffer_position;
22612 let lsp_start = to_lsp(&start);
22613 let lsp_range = lsp::Range {
22614 start: lsp_start,
22615 end: lsp_end,
22616 };
22617 Some(Completion {
22618 replace_range: range,
22619 new_text: snippet.body.clone(),
22620 source: CompletionSource::Lsp {
22621 insert_range: None,
22622 server_id: LanguageServerId(usize::MAX),
22623 resolved: true,
22624 lsp_completion: Box::new(lsp::CompletionItem {
22625 label: snippet.prefix.first().unwrap().clone(),
22626 kind: Some(CompletionItemKind::SNIPPET),
22627 label_details: snippet.description.as_ref().map(|description| {
22628 lsp::CompletionItemLabelDetails {
22629 detail: Some(description.clone()),
22630 description: None,
22631 }
22632 }),
22633 insert_text_format: Some(InsertTextFormat::SNIPPET),
22634 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22635 lsp::InsertReplaceEdit {
22636 new_text: snippet.body.clone(),
22637 insert: lsp_range,
22638 replace: lsp_range,
22639 },
22640 )),
22641 filter_text: Some(snippet.body.clone()),
22642 sort_text: Some(char::MAX.to_string()),
22643 ..lsp::CompletionItem::default()
22644 }),
22645 lsp_defaults: None,
22646 },
22647 label: CodeLabel {
22648 text: matching_prefix.clone(),
22649 runs: Vec::new(),
22650 filter_range: 0..matching_prefix.len(),
22651 },
22652 icon_path: None,
22653 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22654 single_line: snippet.name.clone().into(),
22655 plain_text: snippet
22656 .description
22657 .clone()
22658 .map(|description| description.into()),
22659 }),
22660 insert_text_mode: None,
22661 confirm: None,
22662 })
22663 }))
22664 }
22665
22666 Ok(CompletionResponse {
22667 completions,
22668 display_options: CompletionDisplayOptions::default(),
22669 is_incomplete,
22670 })
22671 })
22672}
22673
22674impl CompletionProvider for Entity<Project> {
22675 fn completions(
22676 &self,
22677 _excerpt_id: ExcerptId,
22678 buffer: &Entity<Buffer>,
22679 buffer_position: text::Anchor,
22680 options: CompletionContext,
22681 _window: &mut Window,
22682 cx: &mut Context<Editor>,
22683 ) -> Task<Result<Vec<CompletionResponse>>> {
22684 self.update(cx, |project, cx| {
22685 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22686 let project_completions = project.completions(buffer, buffer_position, options, cx);
22687 cx.background_spawn(async move {
22688 let mut responses = project_completions.await?;
22689 let snippets = snippets.await?;
22690 if !snippets.completions.is_empty() {
22691 responses.push(snippets);
22692 }
22693 Ok(responses)
22694 })
22695 })
22696 }
22697
22698 fn resolve_completions(
22699 &self,
22700 buffer: Entity<Buffer>,
22701 completion_indices: Vec<usize>,
22702 completions: Rc<RefCell<Box<[Completion]>>>,
22703 cx: &mut Context<Editor>,
22704 ) -> Task<Result<bool>> {
22705 self.update(cx, |project, cx| {
22706 project.lsp_store().update(cx, |lsp_store, cx| {
22707 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22708 })
22709 })
22710 }
22711
22712 fn apply_additional_edits_for_completion(
22713 &self,
22714 buffer: Entity<Buffer>,
22715 completions: Rc<RefCell<Box<[Completion]>>>,
22716 completion_index: usize,
22717 push_to_history: bool,
22718 cx: &mut Context<Editor>,
22719 ) -> Task<Result<Option<language::Transaction>>> {
22720 self.update(cx, |project, cx| {
22721 project.lsp_store().update(cx, |lsp_store, cx| {
22722 lsp_store.apply_additional_edits_for_completion(
22723 buffer,
22724 completions,
22725 completion_index,
22726 push_to_history,
22727 cx,
22728 )
22729 })
22730 })
22731 }
22732
22733 fn is_completion_trigger(
22734 &self,
22735 buffer: &Entity<Buffer>,
22736 position: language::Anchor,
22737 text: &str,
22738 trigger_in_words: bool,
22739 menu_is_open: bool,
22740 cx: &mut Context<Editor>,
22741 ) -> bool {
22742 let mut chars = text.chars();
22743 let char = if let Some(char) = chars.next() {
22744 char
22745 } else {
22746 return false;
22747 };
22748 if chars.next().is_some() {
22749 return false;
22750 }
22751
22752 let buffer = buffer.read(cx);
22753 let snapshot = buffer.snapshot();
22754 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22755 return false;
22756 }
22757 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22758 if trigger_in_words && classifier.is_word(char) {
22759 return true;
22760 }
22761
22762 buffer.completion_triggers().contains(text)
22763 }
22764}
22765
22766impl SemanticsProvider for Entity<Project> {
22767 fn hover(
22768 &self,
22769 buffer: &Entity<Buffer>,
22770 position: text::Anchor,
22771 cx: &mut App,
22772 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22773 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22774 }
22775
22776 fn document_highlights(
22777 &self,
22778 buffer: &Entity<Buffer>,
22779 position: text::Anchor,
22780 cx: &mut App,
22781 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22782 Some(self.update(cx, |project, cx| {
22783 project.document_highlights(buffer, position, cx)
22784 }))
22785 }
22786
22787 fn definitions(
22788 &self,
22789 buffer: &Entity<Buffer>,
22790 position: text::Anchor,
22791 kind: GotoDefinitionKind,
22792 cx: &mut App,
22793 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22794 Some(self.update(cx, |project, cx| match kind {
22795 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22796 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22797 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22798 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22799 }))
22800 }
22801
22802 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22803 self.update(cx, |project, cx| {
22804 if project
22805 .active_debug_session(cx)
22806 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22807 {
22808 return true;
22809 }
22810
22811 buffer.update(cx, |buffer, cx| {
22812 project.any_language_server_supports_inlay_hints(buffer, cx)
22813 })
22814 })
22815 }
22816
22817 fn inline_values(
22818 &self,
22819 buffer_handle: Entity<Buffer>,
22820 range: Range<text::Anchor>,
22821 cx: &mut App,
22822 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22823 self.update(cx, |project, cx| {
22824 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22825
22826 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22827 })
22828 }
22829
22830 fn inlay_hints(
22831 &self,
22832 buffer_handle: Entity<Buffer>,
22833 range: Range<text::Anchor>,
22834 cx: &mut App,
22835 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22836 Some(self.update(cx, |project, cx| {
22837 project.inlay_hints(buffer_handle, range, cx)
22838 }))
22839 }
22840
22841 fn resolve_inlay_hint(
22842 &self,
22843 hint: InlayHint,
22844 buffer_handle: Entity<Buffer>,
22845 server_id: LanguageServerId,
22846 cx: &mut App,
22847 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22848 Some(self.update(cx, |project, cx| {
22849 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22850 }))
22851 }
22852
22853 fn range_for_rename(
22854 &self,
22855 buffer: &Entity<Buffer>,
22856 position: text::Anchor,
22857 cx: &mut App,
22858 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22859 Some(self.update(cx, |project, cx| {
22860 let buffer = buffer.clone();
22861 let task = project.prepare_rename(buffer.clone(), position, cx);
22862 cx.spawn(async move |_, cx| {
22863 Ok(match task.await? {
22864 PrepareRenameResponse::Success(range) => Some(range),
22865 PrepareRenameResponse::InvalidPosition => None,
22866 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22867 // Fallback on using TreeSitter info to determine identifier range
22868 buffer.read_with(cx, |buffer, _| {
22869 let snapshot = buffer.snapshot();
22870 let (range, kind) = snapshot.surrounding_word(position, false);
22871 if kind != Some(CharKind::Word) {
22872 return None;
22873 }
22874 Some(
22875 snapshot.anchor_before(range.start)
22876 ..snapshot.anchor_after(range.end),
22877 )
22878 })?
22879 }
22880 })
22881 })
22882 }))
22883 }
22884
22885 fn perform_rename(
22886 &self,
22887 buffer: &Entity<Buffer>,
22888 position: text::Anchor,
22889 new_name: String,
22890 cx: &mut App,
22891 ) -> Option<Task<Result<ProjectTransaction>>> {
22892 Some(self.update(cx, |project, cx| {
22893 project.perform_rename(buffer.clone(), position, new_name, cx)
22894 }))
22895 }
22896}
22897
22898fn inlay_hint_settings(
22899 location: Anchor,
22900 snapshot: &MultiBufferSnapshot,
22901 cx: &mut Context<Editor>,
22902) -> InlayHintSettings {
22903 let file = snapshot.file_at(location);
22904 let language = snapshot.language_at(location).map(|l| l.name());
22905 language_settings(language, file, cx).inlay_hints
22906}
22907
22908fn consume_contiguous_rows(
22909 contiguous_row_selections: &mut Vec<Selection<Point>>,
22910 selection: &Selection<Point>,
22911 display_map: &DisplaySnapshot,
22912 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22913) -> (MultiBufferRow, MultiBufferRow) {
22914 contiguous_row_selections.push(selection.clone());
22915 let start_row = starting_row(selection, display_map);
22916 let mut end_row = ending_row(selection, display_map);
22917
22918 while let Some(next_selection) = selections.peek() {
22919 if next_selection.start.row <= end_row.0 {
22920 end_row = ending_row(next_selection, display_map);
22921 contiguous_row_selections.push(selections.next().unwrap().clone());
22922 } else {
22923 break;
22924 }
22925 }
22926 (start_row, end_row)
22927}
22928
22929fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22930 if selection.start.column > 0 {
22931 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22932 } else {
22933 MultiBufferRow(selection.start.row)
22934 }
22935}
22936
22937fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22938 if next_selection.end.column > 0 || next_selection.is_empty() {
22939 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22940 } else {
22941 MultiBufferRow(next_selection.end.row)
22942 }
22943}
22944
22945impl EditorSnapshot {
22946 pub fn remote_selections_in_range<'a>(
22947 &'a self,
22948 range: &'a Range<Anchor>,
22949 collaboration_hub: &dyn CollaborationHub,
22950 cx: &'a App,
22951 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22952 let participant_names = collaboration_hub.user_names(cx);
22953 let participant_indices = collaboration_hub.user_participant_indices(cx);
22954 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22955 let collaborators_by_replica_id = collaborators_by_peer_id
22956 .values()
22957 .map(|collaborator| (collaborator.replica_id, collaborator))
22958 .collect::<HashMap<_, _>>();
22959 self.buffer_snapshot
22960 .selections_in_range(range, false)
22961 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22962 if replica_id == AGENT_REPLICA_ID {
22963 Some(RemoteSelection {
22964 replica_id,
22965 selection,
22966 cursor_shape,
22967 line_mode,
22968 collaborator_id: CollaboratorId::Agent,
22969 user_name: Some("Agent".into()),
22970 color: cx.theme().players().agent(),
22971 })
22972 } else {
22973 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22974 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22975 let user_name = participant_names.get(&collaborator.user_id).cloned();
22976 Some(RemoteSelection {
22977 replica_id,
22978 selection,
22979 cursor_shape,
22980 line_mode,
22981 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22982 user_name,
22983 color: if let Some(index) = participant_index {
22984 cx.theme().players().color_for_participant(index.0)
22985 } else {
22986 cx.theme().players().absent()
22987 },
22988 })
22989 }
22990 })
22991 }
22992
22993 pub fn hunks_for_ranges(
22994 &self,
22995 ranges: impl IntoIterator<Item = Range<Point>>,
22996 ) -> Vec<MultiBufferDiffHunk> {
22997 let mut hunks = Vec::new();
22998 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22999 HashMap::default();
23000 for query_range in ranges {
23001 let query_rows =
23002 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23003 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23004 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23005 ) {
23006 // Include deleted hunks that are adjacent to the query range, because
23007 // otherwise they would be missed.
23008 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23009 if hunk.status().is_deleted() {
23010 intersects_range |= hunk.row_range.start == query_rows.end;
23011 intersects_range |= hunk.row_range.end == query_rows.start;
23012 }
23013 if intersects_range {
23014 if !processed_buffer_rows
23015 .entry(hunk.buffer_id)
23016 .or_default()
23017 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23018 {
23019 continue;
23020 }
23021 hunks.push(hunk);
23022 }
23023 }
23024 }
23025
23026 hunks
23027 }
23028
23029 fn display_diff_hunks_for_rows<'a>(
23030 &'a self,
23031 display_rows: Range<DisplayRow>,
23032 folded_buffers: &'a HashSet<BufferId>,
23033 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23034 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23035 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23036
23037 self.buffer_snapshot
23038 .diff_hunks_in_range(buffer_start..buffer_end)
23039 .filter_map(|hunk| {
23040 if folded_buffers.contains(&hunk.buffer_id) {
23041 return None;
23042 }
23043
23044 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23045 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23046
23047 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23048 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23049
23050 let display_hunk = if hunk_display_start.column() != 0 {
23051 DisplayDiffHunk::Folded {
23052 display_row: hunk_display_start.row(),
23053 }
23054 } else {
23055 let mut end_row = hunk_display_end.row();
23056 if hunk_display_end.column() > 0 {
23057 end_row.0 += 1;
23058 }
23059 let is_created_file = hunk.is_created_file();
23060 DisplayDiffHunk::Unfolded {
23061 status: hunk.status(),
23062 diff_base_byte_range: hunk.diff_base_byte_range,
23063 display_row_range: hunk_display_start.row()..end_row,
23064 multi_buffer_range: Anchor::range_in_buffer(
23065 hunk.excerpt_id,
23066 hunk.buffer_id,
23067 hunk.buffer_range,
23068 ),
23069 is_created_file,
23070 }
23071 };
23072
23073 Some(display_hunk)
23074 })
23075 }
23076
23077 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23078 self.display_snapshot.buffer_snapshot.language_at(position)
23079 }
23080
23081 pub fn is_focused(&self) -> bool {
23082 self.is_focused
23083 }
23084
23085 pub fn placeholder_text(&self) -> Option<String> {
23086 self.placeholder_display_snapshot
23087 .as_ref()
23088 .map(|display_map| display_map.text())
23089 }
23090
23091 pub fn scroll_position(&self) -> gpui::Point<f32> {
23092 self.scroll_anchor.scroll_position(&self.display_snapshot)
23093 }
23094
23095 fn gutter_dimensions(
23096 &self,
23097 font_id: FontId,
23098 font_size: Pixels,
23099 max_line_number_width: Pixels,
23100 cx: &App,
23101 ) -> Option<GutterDimensions> {
23102 if !self.show_gutter {
23103 return None;
23104 }
23105
23106 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23107 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23108
23109 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23110 matches!(
23111 ProjectSettings::get_global(cx).git.git_gutter,
23112 Some(GitGutterSetting::TrackedFiles)
23113 )
23114 });
23115 let gutter_settings = EditorSettings::get_global(cx).gutter;
23116 let show_line_numbers = self
23117 .show_line_numbers
23118 .unwrap_or(gutter_settings.line_numbers);
23119 let line_gutter_width = if show_line_numbers {
23120 // Avoid flicker-like gutter resizes when the line number gains another digit by
23121 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23122 let min_width_for_number_on_gutter =
23123 ch_advance * gutter_settings.min_line_number_digits as f32;
23124 max_line_number_width.max(min_width_for_number_on_gutter)
23125 } else {
23126 0.0.into()
23127 };
23128
23129 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23130 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23131
23132 let git_blame_entries_width =
23133 self.git_blame_gutter_max_author_length
23134 .map(|max_author_length| {
23135 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23136 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23137
23138 /// The number of characters to dedicate to gaps and margins.
23139 const SPACING_WIDTH: usize = 4;
23140
23141 let max_char_count = max_author_length.min(renderer.max_author_length())
23142 + ::git::SHORT_SHA_LENGTH
23143 + MAX_RELATIVE_TIMESTAMP.len()
23144 + SPACING_WIDTH;
23145
23146 ch_advance * max_char_count
23147 });
23148
23149 let is_singleton = self.buffer_snapshot.is_singleton();
23150
23151 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23152 left_padding += if !is_singleton {
23153 ch_width * 4.0
23154 } else if show_runnables || show_breakpoints {
23155 ch_width * 3.0
23156 } else if show_git_gutter && show_line_numbers {
23157 ch_width * 2.0
23158 } else if show_git_gutter || show_line_numbers {
23159 ch_width
23160 } else {
23161 px(0.)
23162 };
23163
23164 let shows_folds = is_singleton && gutter_settings.folds;
23165
23166 let right_padding = if shows_folds && show_line_numbers {
23167 ch_width * 4.0
23168 } else if shows_folds || (!is_singleton && show_line_numbers) {
23169 ch_width * 3.0
23170 } else if show_line_numbers {
23171 ch_width
23172 } else {
23173 px(0.)
23174 };
23175
23176 Some(GutterDimensions {
23177 left_padding,
23178 right_padding,
23179 width: line_gutter_width + left_padding + right_padding,
23180 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23181 git_blame_entries_width,
23182 })
23183 }
23184
23185 pub fn render_crease_toggle(
23186 &self,
23187 buffer_row: MultiBufferRow,
23188 row_contains_cursor: bool,
23189 editor: Entity<Editor>,
23190 window: &mut Window,
23191 cx: &mut App,
23192 ) -> Option<AnyElement> {
23193 let folded = self.is_line_folded(buffer_row);
23194 let mut is_foldable = false;
23195
23196 if let Some(crease) = self
23197 .crease_snapshot
23198 .query_row(buffer_row, &self.buffer_snapshot)
23199 {
23200 is_foldable = true;
23201 match crease {
23202 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23203 if let Some(render_toggle) = render_toggle {
23204 let toggle_callback =
23205 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23206 if folded {
23207 editor.update(cx, |editor, cx| {
23208 editor.fold_at(buffer_row, window, cx)
23209 });
23210 } else {
23211 editor.update(cx, |editor, cx| {
23212 editor.unfold_at(buffer_row, window, cx)
23213 });
23214 }
23215 });
23216 return Some((render_toggle)(
23217 buffer_row,
23218 folded,
23219 toggle_callback,
23220 window,
23221 cx,
23222 ));
23223 }
23224 }
23225 }
23226 }
23227
23228 is_foldable |= self.starts_indent(buffer_row);
23229
23230 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23231 Some(
23232 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23233 .toggle_state(folded)
23234 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23235 if folded {
23236 this.unfold_at(buffer_row, window, cx);
23237 } else {
23238 this.fold_at(buffer_row, window, cx);
23239 }
23240 }))
23241 .into_any_element(),
23242 )
23243 } else {
23244 None
23245 }
23246 }
23247
23248 pub fn render_crease_trailer(
23249 &self,
23250 buffer_row: MultiBufferRow,
23251 window: &mut Window,
23252 cx: &mut App,
23253 ) -> Option<AnyElement> {
23254 let folded = self.is_line_folded(buffer_row);
23255 if let Crease::Inline { render_trailer, .. } = self
23256 .crease_snapshot
23257 .query_row(buffer_row, &self.buffer_snapshot)?
23258 {
23259 let render_trailer = render_trailer.as_ref()?;
23260 Some(render_trailer(buffer_row, folded, window, cx))
23261 } else {
23262 None
23263 }
23264 }
23265}
23266
23267impl Deref for EditorSnapshot {
23268 type Target = DisplaySnapshot;
23269
23270 fn deref(&self) -> &Self::Target {
23271 &self.display_snapshot
23272 }
23273}
23274
23275#[derive(Clone, Debug, PartialEq, Eq)]
23276pub enum EditorEvent {
23277 InputIgnored {
23278 text: Arc<str>,
23279 },
23280 InputHandled {
23281 utf16_range_to_replace: Option<Range<isize>>,
23282 text: Arc<str>,
23283 },
23284 ExcerptsAdded {
23285 buffer: Entity<Buffer>,
23286 predecessor: ExcerptId,
23287 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23288 },
23289 ExcerptsRemoved {
23290 ids: Vec<ExcerptId>,
23291 removed_buffer_ids: Vec<BufferId>,
23292 },
23293 BufferFoldToggled {
23294 ids: Vec<ExcerptId>,
23295 folded: bool,
23296 },
23297 ExcerptsEdited {
23298 ids: Vec<ExcerptId>,
23299 },
23300 ExcerptsExpanded {
23301 ids: Vec<ExcerptId>,
23302 },
23303 BufferEdited,
23304 Edited {
23305 transaction_id: clock::Lamport,
23306 },
23307 Reparsed(BufferId),
23308 Focused,
23309 FocusedIn,
23310 Blurred,
23311 DirtyChanged,
23312 Saved,
23313 TitleChanged,
23314 SelectionsChanged {
23315 local: bool,
23316 },
23317 ScrollPositionChanged {
23318 local: bool,
23319 autoscroll: bool,
23320 },
23321 TransactionUndone {
23322 transaction_id: clock::Lamport,
23323 },
23324 TransactionBegun {
23325 transaction_id: clock::Lamport,
23326 },
23327 CursorShapeChanged,
23328 BreadcrumbsChanged,
23329 PushedToNavHistory {
23330 anchor: Anchor,
23331 is_deactivate: bool,
23332 },
23333}
23334
23335impl EventEmitter<EditorEvent> for Editor {}
23336
23337impl Focusable for Editor {
23338 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23339 self.focus_handle.clone()
23340 }
23341}
23342
23343impl Render for Editor {
23344 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23345 let settings = ThemeSettings::get_global(cx);
23346
23347 let mut text_style = match self.mode {
23348 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23349 color: cx.theme().colors().editor_foreground,
23350 font_family: settings.ui_font.family.clone(),
23351 font_features: settings.ui_font.features.clone(),
23352 font_fallbacks: settings.ui_font.fallbacks.clone(),
23353 font_size: rems(0.875).into(),
23354 font_weight: settings.ui_font.weight,
23355 line_height: relative(settings.buffer_line_height.value()),
23356 ..Default::default()
23357 },
23358 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23359 color: cx.theme().colors().editor_foreground,
23360 font_family: settings.buffer_font.family.clone(),
23361 font_features: settings.buffer_font.features.clone(),
23362 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23363 font_size: settings.buffer_font_size(cx).into(),
23364 font_weight: settings.buffer_font.weight,
23365 line_height: relative(settings.buffer_line_height.value()),
23366 ..Default::default()
23367 },
23368 };
23369 if let Some(text_style_refinement) = &self.text_style_refinement {
23370 text_style.refine(text_style_refinement)
23371 }
23372
23373 let background = match self.mode {
23374 EditorMode::SingleLine => cx.theme().system().transparent,
23375 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23376 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23377 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23378 };
23379
23380 EditorElement::new(
23381 &cx.entity(),
23382 EditorStyle {
23383 background,
23384 border: cx.theme().colors().border,
23385 local_player: cx.theme().players().local(),
23386 text: text_style,
23387 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23388 syntax: cx.theme().syntax().clone(),
23389 status: cx.theme().status().clone(),
23390 inlay_hints_style: make_inlay_hints_style(cx),
23391 edit_prediction_styles: make_suggestion_styles(cx),
23392 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23393 show_underlines: self.diagnostics_enabled(),
23394 },
23395 )
23396 }
23397}
23398
23399impl EntityInputHandler for Editor {
23400 fn text_for_range(
23401 &mut self,
23402 range_utf16: Range<usize>,
23403 adjusted_range: &mut Option<Range<usize>>,
23404 _: &mut Window,
23405 cx: &mut Context<Self>,
23406 ) -> Option<String> {
23407 let snapshot = self.buffer.read(cx).read(cx);
23408 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23409 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23410 if (start.0..end.0) != range_utf16 {
23411 adjusted_range.replace(start.0..end.0);
23412 }
23413 Some(snapshot.text_for_range(start..end).collect())
23414 }
23415
23416 fn selected_text_range(
23417 &mut self,
23418 ignore_disabled_input: bool,
23419 _: &mut Window,
23420 cx: &mut Context<Self>,
23421 ) -> Option<UTF16Selection> {
23422 // Prevent the IME menu from appearing when holding down an alphabetic key
23423 // while input is disabled.
23424 if !ignore_disabled_input && !self.input_enabled {
23425 return None;
23426 }
23427
23428 let selection = self.selections.newest::<OffsetUtf16>(cx);
23429 let range = selection.range();
23430
23431 Some(UTF16Selection {
23432 range: range.start.0..range.end.0,
23433 reversed: selection.reversed,
23434 })
23435 }
23436
23437 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23438 let snapshot = self.buffer.read(cx).read(cx);
23439 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23440 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23441 }
23442
23443 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23444 self.clear_highlights::<InputComposition>(cx);
23445 self.ime_transaction.take();
23446 }
23447
23448 fn replace_text_in_range(
23449 &mut self,
23450 range_utf16: Option<Range<usize>>,
23451 text: &str,
23452 window: &mut Window,
23453 cx: &mut Context<Self>,
23454 ) {
23455 if !self.input_enabled {
23456 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23457 return;
23458 }
23459
23460 self.transact(window, cx, |this, window, cx| {
23461 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23462 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23463 Some(this.selection_replacement_ranges(range_utf16, cx))
23464 } else {
23465 this.marked_text_ranges(cx)
23466 };
23467
23468 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23469 let newest_selection_id = this.selections.newest_anchor().id;
23470 this.selections
23471 .all::<OffsetUtf16>(cx)
23472 .iter()
23473 .zip(ranges_to_replace.iter())
23474 .find_map(|(selection, range)| {
23475 if selection.id == newest_selection_id {
23476 Some(
23477 (range.start.0 as isize - selection.head().0 as isize)
23478 ..(range.end.0 as isize - selection.head().0 as isize),
23479 )
23480 } else {
23481 None
23482 }
23483 })
23484 });
23485
23486 cx.emit(EditorEvent::InputHandled {
23487 utf16_range_to_replace: range_to_replace,
23488 text: text.into(),
23489 });
23490
23491 if let Some(new_selected_ranges) = new_selected_ranges {
23492 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23493 selections.select_ranges(new_selected_ranges)
23494 });
23495 this.backspace(&Default::default(), window, cx);
23496 }
23497
23498 this.handle_input(text, window, cx);
23499 });
23500
23501 if let Some(transaction) = self.ime_transaction {
23502 self.buffer.update(cx, |buffer, cx| {
23503 buffer.group_until_transaction(transaction, cx);
23504 });
23505 }
23506
23507 self.unmark_text(window, cx);
23508 }
23509
23510 fn replace_and_mark_text_in_range(
23511 &mut self,
23512 range_utf16: Option<Range<usize>>,
23513 text: &str,
23514 new_selected_range_utf16: Option<Range<usize>>,
23515 window: &mut Window,
23516 cx: &mut Context<Self>,
23517 ) {
23518 if !self.input_enabled {
23519 return;
23520 }
23521
23522 let transaction = self.transact(window, cx, |this, window, cx| {
23523 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23524 let snapshot = this.buffer.read(cx).read(cx);
23525 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23526 for marked_range in &mut marked_ranges {
23527 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23528 marked_range.start.0 += relative_range_utf16.start;
23529 marked_range.start =
23530 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23531 marked_range.end =
23532 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23533 }
23534 }
23535 Some(marked_ranges)
23536 } else if let Some(range_utf16) = range_utf16 {
23537 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23538 Some(this.selection_replacement_ranges(range_utf16, cx))
23539 } else {
23540 None
23541 };
23542
23543 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23544 let newest_selection_id = this.selections.newest_anchor().id;
23545 this.selections
23546 .all::<OffsetUtf16>(cx)
23547 .iter()
23548 .zip(ranges_to_replace.iter())
23549 .find_map(|(selection, range)| {
23550 if selection.id == newest_selection_id {
23551 Some(
23552 (range.start.0 as isize - selection.head().0 as isize)
23553 ..(range.end.0 as isize - selection.head().0 as isize),
23554 )
23555 } else {
23556 None
23557 }
23558 })
23559 });
23560
23561 cx.emit(EditorEvent::InputHandled {
23562 utf16_range_to_replace: range_to_replace,
23563 text: text.into(),
23564 });
23565
23566 if let Some(ranges) = ranges_to_replace {
23567 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23568 s.select_ranges(ranges)
23569 });
23570 }
23571
23572 let marked_ranges = {
23573 let snapshot = this.buffer.read(cx).read(cx);
23574 this.selections
23575 .disjoint_anchors()
23576 .iter()
23577 .map(|selection| {
23578 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23579 })
23580 .collect::<Vec<_>>()
23581 };
23582
23583 if text.is_empty() {
23584 this.unmark_text(window, cx);
23585 } else {
23586 this.highlight_text::<InputComposition>(
23587 marked_ranges.clone(),
23588 HighlightStyle {
23589 underline: Some(UnderlineStyle {
23590 thickness: px(1.),
23591 color: None,
23592 wavy: false,
23593 }),
23594 ..Default::default()
23595 },
23596 cx,
23597 );
23598 }
23599
23600 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23601 let use_autoclose = this.use_autoclose;
23602 let use_auto_surround = this.use_auto_surround;
23603 this.set_use_autoclose(false);
23604 this.set_use_auto_surround(false);
23605 this.handle_input(text, window, cx);
23606 this.set_use_autoclose(use_autoclose);
23607 this.set_use_auto_surround(use_auto_surround);
23608
23609 if let Some(new_selected_range) = new_selected_range_utf16 {
23610 let snapshot = this.buffer.read(cx).read(cx);
23611 let new_selected_ranges = marked_ranges
23612 .into_iter()
23613 .map(|marked_range| {
23614 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23615 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23616 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23617 snapshot.clip_offset_utf16(new_start, Bias::Left)
23618 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23619 })
23620 .collect::<Vec<_>>();
23621
23622 drop(snapshot);
23623 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23624 selections.select_ranges(new_selected_ranges)
23625 });
23626 }
23627 });
23628
23629 self.ime_transaction = self.ime_transaction.or(transaction);
23630 if let Some(transaction) = self.ime_transaction {
23631 self.buffer.update(cx, |buffer, cx| {
23632 buffer.group_until_transaction(transaction, cx);
23633 });
23634 }
23635
23636 if self.text_highlights::<InputComposition>(cx).is_none() {
23637 self.ime_transaction.take();
23638 }
23639 }
23640
23641 fn bounds_for_range(
23642 &mut self,
23643 range_utf16: Range<usize>,
23644 element_bounds: gpui::Bounds<Pixels>,
23645 window: &mut Window,
23646 cx: &mut Context<Self>,
23647 ) -> Option<gpui::Bounds<Pixels>> {
23648 let text_layout_details = self.text_layout_details(window);
23649 let CharacterDimensions {
23650 em_width,
23651 em_advance,
23652 line_height,
23653 } = self.character_dimensions(window);
23654
23655 let snapshot = self.snapshot(window, cx);
23656 let scroll_position = snapshot.scroll_position();
23657 let scroll_left = scroll_position.x * em_advance;
23658
23659 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23660 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23661 + self.gutter_dimensions.full_width();
23662 let y = line_height * (start.row().as_f32() - scroll_position.y);
23663
23664 Some(Bounds {
23665 origin: element_bounds.origin + point(x, y),
23666 size: size(em_width, line_height),
23667 })
23668 }
23669
23670 fn character_index_for_point(
23671 &mut self,
23672 point: gpui::Point<Pixels>,
23673 _window: &mut Window,
23674 _cx: &mut Context<Self>,
23675 ) -> Option<usize> {
23676 let position_map = self.last_position_map.as_ref()?;
23677 if !position_map.text_hitbox.contains(&point) {
23678 return None;
23679 }
23680 let display_point = position_map.point_for_position(point).previous_valid;
23681 let anchor = position_map
23682 .snapshot
23683 .display_point_to_anchor(display_point, Bias::Left);
23684 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23685 Some(utf16_offset.0)
23686 }
23687}
23688
23689trait SelectionExt {
23690 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23691 fn spanned_rows(
23692 &self,
23693 include_end_if_at_line_start: bool,
23694 map: &DisplaySnapshot,
23695 ) -> Range<MultiBufferRow>;
23696}
23697
23698impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23699 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23700 let start = self
23701 .start
23702 .to_point(&map.buffer_snapshot)
23703 .to_display_point(map);
23704 let end = self
23705 .end
23706 .to_point(&map.buffer_snapshot)
23707 .to_display_point(map);
23708 if self.reversed {
23709 end..start
23710 } else {
23711 start..end
23712 }
23713 }
23714
23715 fn spanned_rows(
23716 &self,
23717 include_end_if_at_line_start: bool,
23718 map: &DisplaySnapshot,
23719 ) -> Range<MultiBufferRow> {
23720 let start = self.start.to_point(&map.buffer_snapshot);
23721 let mut end = self.end.to_point(&map.buffer_snapshot);
23722 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23723 end.row -= 1;
23724 }
23725
23726 let buffer_start = map.prev_line_boundary(start).0;
23727 let buffer_end = map.next_line_boundary(end).0;
23728 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23729 }
23730}
23731
23732impl<T: InvalidationRegion> InvalidationStack<T> {
23733 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23734 where
23735 S: Clone + ToOffset,
23736 {
23737 while let Some(region) = self.last() {
23738 let all_selections_inside_invalidation_ranges =
23739 if selections.len() == region.ranges().len() {
23740 selections
23741 .iter()
23742 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23743 .all(|(selection, invalidation_range)| {
23744 let head = selection.head().to_offset(buffer);
23745 invalidation_range.start <= head && invalidation_range.end >= head
23746 })
23747 } else {
23748 false
23749 };
23750
23751 if all_selections_inside_invalidation_ranges {
23752 break;
23753 } else {
23754 self.pop();
23755 }
23756 }
23757 }
23758}
23759
23760impl<T> Default for InvalidationStack<T> {
23761 fn default() -> Self {
23762 Self(Default::default())
23763 }
23764}
23765
23766impl<T> Deref for InvalidationStack<T> {
23767 type Target = Vec<T>;
23768
23769 fn deref(&self) -> &Self::Target {
23770 &self.0
23771 }
23772}
23773
23774impl<T> DerefMut for InvalidationStack<T> {
23775 fn deref_mut(&mut self) -> &mut Self::Target {
23776 &mut self.0
23777 }
23778}
23779
23780impl InvalidationRegion for SnippetState {
23781 fn ranges(&self) -> &[Range<Anchor>] {
23782 &self.ranges[self.active_index]
23783 }
23784}
23785
23786fn edit_prediction_edit_text(
23787 current_snapshot: &BufferSnapshot,
23788 edits: &[(Range<Anchor>, String)],
23789 edit_preview: &EditPreview,
23790 include_deletions: bool,
23791 cx: &App,
23792) -> HighlightedText {
23793 let edits = edits
23794 .iter()
23795 .map(|(anchor, text)| {
23796 (
23797 anchor.start.text_anchor..anchor.end.text_anchor,
23798 text.clone(),
23799 )
23800 })
23801 .collect::<Vec<_>>();
23802
23803 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23804}
23805
23806fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23807 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23808 // Just show the raw edit text with basic styling
23809 let mut text = String::new();
23810 let mut highlights = Vec::new();
23811
23812 let insertion_highlight_style = HighlightStyle {
23813 color: Some(cx.theme().colors().text),
23814 ..Default::default()
23815 };
23816
23817 for (_, edit_text) in edits {
23818 let start_offset = text.len();
23819 text.push_str(edit_text);
23820 let end_offset = text.len();
23821
23822 if start_offset < end_offset {
23823 highlights.push((start_offset..end_offset, insertion_highlight_style));
23824 }
23825 }
23826
23827 HighlightedText {
23828 text: text.into(),
23829 highlights,
23830 }
23831}
23832
23833pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23834 match severity {
23835 lsp::DiagnosticSeverity::ERROR => colors.error,
23836 lsp::DiagnosticSeverity::WARNING => colors.warning,
23837 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23838 lsp::DiagnosticSeverity::HINT => colors.info,
23839 _ => colors.ignored,
23840 }
23841}
23842
23843pub fn styled_runs_for_code_label<'a>(
23844 label: &'a CodeLabel,
23845 syntax_theme: &'a theme::SyntaxTheme,
23846) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23847 let fade_out = HighlightStyle {
23848 fade_out: Some(0.35),
23849 ..Default::default()
23850 };
23851
23852 let mut prev_end = label.filter_range.end;
23853 label
23854 .runs
23855 .iter()
23856 .enumerate()
23857 .flat_map(move |(ix, (range, highlight_id))| {
23858 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23859 style
23860 } else {
23861 return Default::default();
23862 };
23863 let muted_style = style.highlight(fade_out);
23864
23865 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23866 if range.start >= label.filter_range.end {
23867 if range.start > prev_end {
23868 runs.push((prev_end..range.start, fade_out));
23869 }
23870 runs.push((range.clone(), muted_style));
23871 } else if range.end <= label.filter_range.end {
23872 runs.push((range.clone(), style));
23873 } else {
23874 runs.push((range.start..label.filter_range.end, style));
23875 runs.push((label.filter_range.end..range.end, muted_style));
23876 }
23877 prev_end = cmp::max(prev_end, range.end);
23878
23879 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23880 runs.push((prev_end..label.text.len(), fade_out));
23881 }
23882
23883 runs
23884 })
23885}
23886
23887pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23888 let mut prev_index = 0;
23889 let mut prev_codepoint: Option<char> = None;
23890 text.char_indices()
23891 .chain([(text.len(), '\0')])
23892 .filter_map(move |(index, codepoint)| {
23893 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23894 let is_boundary = index == text.len()
23895 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23896 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23897 if is_boundary {
23898 let chunk = &text[prev_index..index];
23899 prev_index = index;
23900 Some(chunk)
23901 } else {
23902 None
23903 }
23904 })
23905}
23906
23907pub trait RangeToAnchorExt: Sized {
23908 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23909
23910 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23911 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23912 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23913 }
23914}
23915
23916impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23917 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23918 let start_offset = self.start.to_offset(snapshot);
23919 let end_offset = self.end.to_offset(snapshot);
23920 if start_offset == end_offset {
23921 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23922 } else {
23923 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23924 }
23925 }
23926}
23927
23928pub trait RowExt {
23929 fn as_f32(&self) -> f32;
23930
23931 fn next_row(&self) -> Self;
23932
23933 fn previous_row(&self) -> Self;
23934
23935 fn minus(&self, other: Self) -> u32;
23936}
23937
23938impl RowExt for DisplayRow {
23939 fn as_f32(&self) -> f32 {
23940 self.0 as f32
23941 }
23942
23943 fn next_row(&self) -> Self {
23944 Self(self.0 + 1)
23945 }
23946
23947 fn previous_row(&self) -> Self {
23948 Self(self.0.saturating_sub(1))
23949 }
23950
23951 fn minus(&self, other: Self) -> u32 {
23952 self.0 - other.0
23953 }
23954}
23955
23956impl RowExt for MultiBufferRow {
23957 fn as_f32(&self) -> f32 {
23958 self.0 as f32
23959 }
23960
23961 fn next_row(&self) -> Self {
23962 Self(self.0 + 1)
23963 }
23964
23965 fn previous_row(&self) -> Self {
23966 Self(self.0.saturating_sub(1))
23967 }
23968
23969 fn minus(&self, other: Self) -> u32 {
23970 self.0 - other.0
23971 }
23972}
23973
23974trait RowRangeExt {
23975 type Row;
23976
23977 fn len(&self) -> usize;
23978
23979 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23980}
23981
23982impl RowRangeExt for Range<MultiBufferRow> {
23983 type Row = MultiBufferRow;
23984
23985 fn len(&self) -> usize {
23986 (self.end.0 - self.start.0) as usize
23987 }
23988
23989 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23990 (self.start.0..self.end.0).map(MultiBufferRow)
23991 }
23992}
23993
23994impl RowRangeExt for Range<DisplayRow> {
23995 type Row = DisplayRow;
23996
23997 fn len(&self) -> usize {
23998 (self.end.0 - self.start.0) as usize
23999 }
24000
24001 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24002 (self.start.0..self.end.0).map(DisplayRow)
24003 }
24004}
24005
24006/// If select range has more than one line, we
24007/// just point the cursor to range.start.
24008fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24009 if range.start.row == range.end.row {
24010 range
24011 } else {
24012 range.start..range.start
24013 }
24014}
24015pub struct KillRing(ClipboardItem);
24016impl Global for KillRing {}
24017
24018const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24019
24020enum BreakpointPromptEditAction {
24021 Log,
24022 Condition,
24023 HitCondition,
24024}
24025
24026struct BreakpointPromptEditor {
24027 pub(crate) prompt: Entity<Editor>,
24028 editor: WeakEntity<Editor>,
24029 breakpoint_anchor: Anchor,
24030 breakpoint: Breakpoint,
24031 edit_action: BreakpointPromptEditAction,
24032 block_ids: HashSet<CustomBlockId>,
24033 editor_margins: Arc<Mutex<EditorMargins>>,
24034 _subscriptions: Vec<Subscription>,
24035}
24036
24037impl BreakpointPromptEditor {
24038 const MAX_LINES: u8 = 4;
24039
24040 fn new(
24041 editor: WeakEntity<Editor>,
24042 breakpoint_anchor: Anchor,
24043 breakpoint: Breakpoint,
24044 edit_action: BreakpointPromptEditAction,
24045 window: &mut Window,
24046 cx: &mut Context<Self>,
24047 ) -> Self {
24048 let base_text = match edit_action {
24049 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24050 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24051 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24052 }
24053 .map(|msg| msg.to_string())
24054 .unwrap_or_default();
24055
24056 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24057 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24058
24059 let prompt = cx.new(|cx| {
24060 let mut prompt = Editor::new(
24061 EditorMode::AutoHeight {
24062 min_lines: 1,
24063 max_lines: Some(Self::MAX_LINES as usize),
24064 },
24065 buffer,
24066 None,
24067 window,
24068 cx,
24069 );
24070 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24071 prompt.set_show_cursor_when_unfocused(false, cx);
24072 prompt.set_placeholder_text(
24073 match edit_action {
24074 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24075 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24076 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24077 },
24078 window,
24079 cx,
24080 );
24081
24082 prompt
24083 });
24084
24085 Self {
24086 prompt,
24087 editor,
24088 breakpoint_anchor,
24089 breakpoint,
24090 edit_action,
24091 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24092 block_ids: Default::default(),
24093 _subscriptions: vec![],
24094 }
24095 }
24096
24097 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24098 self.block_ids.extend(block_ids)
24099 }
24100
24101 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24102 if let Some(editor) = self.editor.upgrade() {
24103 let message = self
24104 .prompt
24105 .read(cx)
24106 .buffer
24107 .read(cx)
24108 .as_singleton()
24109 .expect("A multi buffer in breakpoint prompt isn't possible")
24110 .read(cx)
24111 .as_rope()
24112 .to_string();
24113
24114 editor.update(cx, |editor, cx| {
24115 editor.edit_breakpoint_at_anchor(
24116 self.breakpoint_anchor,
24117 self.breakpoint.clone(),
24118 match self.edit_action {
24119 BreakpointPromptEditAction::Log => {
24120 BreakpointEditAction::EditLogMessage(message.into())
24121 }
24122 BreakpointPromptEditAction::Condition => {
24123 BreakpointEditAction::EditCondition(message.into())
24124 }
24125 BreakpointPromptEditAction::HitCondition => {
24126 BreakpointEditAction::EditHitCondition(message.into())
24127 }
24128 },
24129 cx,
24130 );
24131
24132 editor.remove_blocks(self.block_ids.clone(), None, cx);
24133 cx.focus_self(window);
24134 });
24135 }
24136 }
24137
24138 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24139 self.editor
24140 .update(cx, |editor, cx| {
24141 editor.remove_blocks(self.block_ids.clone(), None, cx);
24142 window.focus(&editor.focus_handle);
24143 })
24144 .log_err();
24145 }
24146
24147 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24148 let settings = ThemeSettings::get_global(cx);
24149 let text_style = TextStyle {
24150 color: if self.prompt.read(cx).read_only(cx) {
24151 cx.theme().colors().text_disabled
24152 } else {
24153 cx.theme().colors().text
24154 },
24155 font_family: settings.buffer_font.family.clone(),
24156 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24157 font_size: settings.buffer_font_size(cx).into(),
24158 font_weight: settings.buffer_font.weight,
24159 line_height: relative(settings.buffer_line_height.value()),
24160 ..Default::default()
24161 };
24162 EditorElement::new(
24163 &self.prompt,
24164 EditorStyle {
24165 background: cx.theme().colors().editor_background,
24166 local_player: cx.theme().players().local(),
24167 text: text_style,
24168 ..Default::default()
24169 },
24170 )
24171 }
24172}
24173
24174impl Render for BreakpointPromptEditor {
24175 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24176 let editor_margins = *self.editor_margins.lock();
24177 let gutter_dimensions = editor_margins.gutter;
24178 h_flex()
24179 .key_context("Editor")
24180 .bg(cx.theme().colors().editor_background)
24181 .border_y_1()
24182 .border_color(cx.theme().status().info_border)
24183 .size_full()
24184 .py(window.line_height() / 2.5)
24185 .on_action(cx.listener(Self::confirm))
24186 .on_action(cx.listener(Self::cancel))
24187 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24188 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24189 }
24190}
24191
24192impl Focusable for BreakpointPromptEditor {
24193 fn focus_handle(&self, cx: &App) -> FocusHandle {
24194 self.prompt.focus_handle(cx)
24195 }
24196}
24197
24198fn all_edits_insertions_or_deletions(
24199 edits: &Vec<(Range<Anchor>, String)>,
24200 snapshot: &MultiBufferSnapshot,
24201) -> bool {
24202 let mut all_insertions = true;
24203 let mut all_deletions = true;
24204
24205 for (range, new_text) in edits.iter() {
24206 let range_is_empty = range.to_offset(snapshot).is_empty();
24207 let text_is_empty = new_text.is_empty();
24208
24209 if range_is_empty != text_is_empty {
24210 if range_is_empty {
24211 all_deletions = false;
24212 } else {
24213 all_insertions = false;
24214 }
24215 } else {
24216 return false;
24217 }
24218
24219 if !all_insertions && !all_deletions {
24220 return false;
24221 }
24222 }
24223 all_insertions || all_deletions
24224}
24225
24226struct MissingEditPredictionKeybindingTooltip;
24227
24228impl Render for MissingEditPredictionKeybindingTooltip {
24229 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24230 ui::tooltip_container(window, cx, |container, _, cx| {
24231 container
24232 .flex_shrink_0()
24233 .max_w_80()
24234 .min_h(rems_from_px(124.))
24235 .justify_between()
24236 .child(
24237 v_flex()
24238 .flex_1()
24239 .text_ui_sm(cx)
24240 .child(Label::new("Conflict with Accept Keybinding"))
24241 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24242 )
24243 .child(
24244 h_flex()
24245 .pb_1()
24246 .gap_1()
24247 .items_end()
24248 .w_full()
24249 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24250 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24251 }))
24252 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24253 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24254 })),
24255 )
24256 })
24257 }
24258}
24259
24260#[derive(Debug, Clone, Copy, PartialEq)]
24261pub struct LineHighlight {
24262 pub background: Background,
24263 pub border: Option<gpui::Hsla>,
24264 pub include_gutter: bool,
24265 pub type_id: Option<TypeId>,
24266}
24267
24268struct LineManipulationResult {
24269 pub new_text: String,
24270 pub line_count_before: usize,
24271 pub line_count_after: usize,
24272}
24273
24274fn render_diff_hunk_controls(
24275 row: u32,
24276 status: &DiffHunkStatus,
24277 hunk_range: Range<Anchor>,
24278 is_created_file: bool,
24279 line_height: Pixels,
24280 editor: &Entity<Editor>,
24281 _window: &mut Window,
24282 cx: &mut App,
24283) -> AnyElement {
24284 h_flex()
24285 .h(line_height)
24286 .mr_1()
24287 .gap_1()
24288 .px_0p5()
24289 .pb_1()
24290 .border_x_1()
24291 .border_b_1()
24292 .border_color(cx.theme().colors().border_variant)
24293 .rounded_b_lg()
24294 .bg(cx.theme().colors().editor_background)
24295 .gap_1()
24296 .block_mouse_except_scroll()
24297 .shadow_md()
24298 .child(if status.has_secondary_hunk() {
24299 Button::new(("stage", row as u64), "Stage")
24300 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24301 .tooltip({
24302 let focus_handle = editor.focus_handle(cx);
24303 move |window, cx| {
24304 Tooltip::for_action_in(
24305 "Stage Hunk",
24306 &::git::ToggleStaged,
24307 &focus_handle,
24308 window,
24309 cx,
24310 )
24311 }
24312 })
24313 .on_click({
24314 let editor = editor.clone();
24315 move |_event, _window, cx| {
24316 editor.update(cx, |editor, cx| {
24317 editor.stage_or_unstage_diff_hunks(
24318 true,
24319 vec![hunk_range.start..hunk_range.start],
24320 cx,
24321 );
24322 });
24323 }
24324 })
24325 } else {
24326 Button::new(("unstage", row as u64), "Unstage")
24327 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24328 .tooltip({
24329 let focus_handle = editor.focus_handle(cx);
24330 move |window, cx| {
24331 Tooltip::for_action_in(
24332 "Unstage Hunk",
24333 &::git::ToggleStaged,
24334 &focus_handle,
24335 window,
24336 cx,
24337 )
24338 }
24339 })
24340 .on_click({
24341 let editor = editor.clone();
24342 move |_event, _window, cx| {
24343 editor.update(cx, |editor, cx| {
24344 editor.stage_or_unstage_diff_hunks(
24345 false,
24346 vec![hunk_range.start..hunk_range.start],
24347 cx,
24348 );
24349 });
24350 }
24351 })
24352 })
24353 .child(
24354 Button::new(("restore", row as u64), "Restore")
24355 .tooltip({
24356 let focus_handle = editor.focus_handle(cx);
24357 move |window, cx| {
24358 Tooltip::for_action_in(
24359 "Restore Hunk",
24360 &::git::Restore,
24361 &focus_handle,
24362 window,
24363 cx,
24364 )
24365 }
24366 })
24367 .on_click({
24368 let editor = editor.clone();
24369 move |_event, window, cx| {
24370 editor.update(cx, |editor, cx| {
24371 let snapshot = editor.snapshot(window, cx);
24372 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24373 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24374 });
24375 }
24376 })
24377 .disabled(is_created_file),
24378 )
24379 .when(
24380 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24381 |el| {
24382 el.child(
24383 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24384 .shape(IconButtonShape::Square)
24385 .icon_size(IconSize::Small)
24386 // .disabled(!has_multiple_hunks)
24387 .tooltip({
24388 let focus_handle = editor.focus_handle(cx);
24389 move |window, cx| {
24390 Tooltip::for_action_in(
24391 "Next Hunk",
24392 &GoToHunk,
24393 &focus_handle,
24394 window,
24395 cx,
24396 )
24397 }
24398 })
24399 .on_click({
24400 let editor = editor.clone();
24401 move |_event, window, cx| {
24402 editor.update(cx, |editor, cx| {
24403 let snapshot = editor.snapshot(window, cx);
24404 let position =
24405 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24406 editor.go_to_hunk_before_or_after_position(
24407 &snapshot,
24408 position,
24409 Direction::Next,
24410 window,
24411 cx,
24412 );
24413 editor.expand_selected_diff_hunks(cx);
24414 });
24415 }
24416 }),
24417 )
24418 .child(
24419 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24420 .shape(IconButtonShape::Square)
24421 .icon_size(IconSize::Small)
24422 // .disabled(!has_multiple_hunks)
24423 .tooltip({
24424 let focus_handle = editor.focus_handle(cx);
24425 move |window, cx| {
24426 Tooltip::for_action_in(
24427 "Previous Hunk",
24428 &GoToPreviousHunk,
24429 &focus_handle,
24430 window,
24431 cx,
24432 )
24433 }
24434 })
24435 .on_click({
24436 let editor = editor.clone();
24437 move |_event, window, cx| {
24438 editor.update(cx, |editor, cx| {
24439 let snapshot = editor.snapshot(window, cx);
24440 let point =
24441 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24442 editor.go_to_hunk_before_or_after_position(
24443 &snapshot,
24444 point,
24445 Direction::Prev,
24446 window,
24447 cx,
24448 );
24449 editor.expand_selected_diff_hunks(cx);
24450 });
24451 }
24452 }),
24453 )
24454 },
24455 )
24456 .into_any_element()
24457}
24458
24459pub fn multibuffer_context_lines(cx: &App) -> u32 {
24460 EditorSettings::try_get(cx)
24461 .map(|settings| settings.excerpt_context_lines)
24462 .unwrap_or(2)
24463 .min(32)
24464}