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//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction_types::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use inlays::Inlay;
65pub use items::MAX_TAB_TITLE_LEN;
66pub use lsp::CompletionContext;
67pub use lsp_ext::lsp_tasks;
68pub use multi_buffer::{
69 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
70 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
71 ToPoint,
72};
73pub use split::SplittableEditor;
74pub use text::Bias;
75
76use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
77use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
78use anyhow::{Context as _, Result, anyhow, bail};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction_types::{
92 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
93};
94use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
95use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
96use futures::{
97 FutureExt, StreamExt as _,
98 future::{self, Shared, join},
99 stream::FuturesUnordered,
100};
101use fuzzy::{StringMatch, StringMatchCandidate};
102use git::blame::{GitBlame, GlobalBlameRenderer};
103use gpui::{
104 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
105 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
106 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
107 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
108 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
109 Render, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun,
110 TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
111 WeakEntity, WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative,
112 size,
113};
114use hover_links::{HoverLink, HoveredLinkState, find_file};
115use hover_popover::{HoverState, hide_hover};
116use indent_guides::ActiveIndentGuidesState;
117use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
118use itertools::{Either, Itertools};
119use language::{
120 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
121 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
122 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
123 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
124 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
125 TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143};
144use parking_lot::Mutex;
145use persistence::DB;
146use project::{
147 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
148 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
149 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
150 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
151 debugger::{
152 breakpoint_store::{
153 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
154 BreakpointStore, BreakpointStoreEvent,
155 },
156 session::{Session, SessionEvent},
157 },
158 git_store::GitStoreEvent,
159 lsp_store::{
160 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
161 OpenLspBufferHandle,
162 },
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
169use serde::{Deserialize, Serialize};
170use settings::{
171 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
172 update_settings_file,
173};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::{Any, TypeId},
178 borrow::Cow,
179 cell::{OnceCell, RefCell},
180 cmp::{self, Ordering, Reverse},
181 collections::hash_map,
182 iter::{self, 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, ToOffset as _};
193use theme::{
194 AccentColors, 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::{ItemBufferKind, 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 inlays::{
216 InlineValueCache,
217 inlay_hints::{LspInlayHintData, inlay_hint_settings},
218 },
219 scroll::{ScrollOffset, ScrollPixelOffset},
220 selections_collection::resolve_selections_wrapping_blocks,
221 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
222};
223
224pub const FILE_HEADER_HEIGHT: u32 = 2;
225pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
226const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
227const MAX_LINE_LEN: usize = 1024;
228const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
229const MAX_SELECTION_HISTORY_LEN: usize = 1024;
230pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
231#[doc(hidden)]
232pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
233pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
234
235pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
236pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
237pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
238pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
239
240pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
241pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
242pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
243
244pub type RenderDiffHunkControlsFn = Arc<
245 dyn Fn(
246 u32,
247 &DiffHunkStatus,
248 Range<Anchor>,
249 bool,
250 Pixels,
251 &Entity<Editor>,
252 &mut Window,
253 &mut App,
254 ) -> AnyElement,
255>;
256
257enum ReportEditorEvent {
258 Saved { auto_saved: bool },
259 EditorOpened,
260 Closed,
261}
262
263impl ReportEditorEvent {
264 pub fn event_type(&self) -> &'static str {
265 match self {
266 Self::Saved { .. } => "Editor Saved",
267 Self::EditorOpened => "Editor Opened",
268 Self::Closed => "Editor Closed",
269 }
270 }
271}
272
273pub enum ActiveDebugLine {}
274pub enum DebugStackFrameLine {}
275enum DocumentHighlightRead {}
276enum DocumentHighlightWrite {}
277enum InputComposition {}
278pub enum PendingInput {}
279enum SelectedTextHighlight {}
280
281pub enum ConflictsOuter {}
282pub enum ConflictsOurs {}
283pub enum ConflictsTheirs {}
284pub enum ConflictsOursMarker {}
285pub enum ConflictsTheirsMarker {}
286
287pub struct HunkAddedColor;
288pub struct HunkRemovedColor;
289
290#[derive(Debug, Copy, Clone, PartialEq, Eq)]
291pub enum Navigated {
292 Yes,
293 No,
294}
295
296impl Navigated {
297 pub fn from_bool(yes: bool) -> Navigated {
298 if yes { Navigated::Yes } else { Navigated::No }
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303enum DisplayDiffHunk {
304 Folded {
305 display_row: DisplayRow,
306 },
307 Unfolded {
308 is_created_file: bool,
309 diff_base_byte_range: Range<usize>,
310 display_row_range: Range<DisplayRow>,
311 multi_buffer_range: Range<Anchor>,
312 status: DiffHunkStatus,
313 word_diffs: Vec<Range<MultiBufferOffset>>,
314 },
315}
316
317pub enum HideMouseCursorOrigin {
318 TypingAction,
319 MovementAction,
320}
321
322pub fn init(cx: &mut App) {
323 cx.set_global(GlobalBlameRenderer(Arc::new(())));
324
325 workspace::register_project_item::<Editor>(cx);
326 workspace::FollowableViewRegistry::register::<Editor>(cx);
327 workspace::register_serializable_item::<Editor>(cx);
328
329 cx.observe_new(
330 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
331 workspace.register_action(Editor::new_file);
332 workspace.register_action(Editor::new_file_split);
333 workspace.register_action(Editor::new_file_vertical);
334 workspace.register_action(Editor::new_file_horizontal);
335 workspace.register_action(Editor::cancel_language_server_work);
336 workspace.register_action(Editor::toggle_focus);
337 },
338 )
339 .detach();
340
341 cx.on_action(move |_: &workspace::NewFile, cx| {
342 let app_state = workspace::AppState::global(cx);
343 if let Some(app_state) = app_state.upgrade() {
344 workspace::open_new(
345 Default::default(),
346 app_state,
347 cx,
348 |workspace, window, cx| {
349 Editor::new_file(workspace, &Default::default(), window, cx)
350 },
351 )
352 .detach();
353 }
354 })
355 .on_action(move |_: &workspace::NewWindow, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 cx.activate(true);
364 Editor::new_file(workspace, &Default::default(), window, cx)
365 },
366 )
367 .detach();
368 }
369 });
370}
371
372pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
373 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
374}
375
376pub trait DiagnosticRenderer {
377 fn render_group(
378 &self,
379 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
380 buffer_id: BufferId,
381 snapshot: EditorSnapshot,
382 editor: WeakEntity<Editor>,
383 language_registry: Option<Arc<LanguageRegistry>>,
384 cx: &mut App,
385 ) -> Vec<BlockProperties<Anchor>>;
386
387 fn render_hover(
388 &self,
389 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
390 range: Range<Point>,
391 buffer_id: BufferId,
392 language_registry: Option<Arc<LanguageRegistry>>,
393 cx: &mut App,
394 ) -> Option<Entity<markdown::Markdown>>;
395
396 fn open_link(
397 &self,
398 editor: &mut Editor,
399 link: SharedString,
400 window: &mut Window,
401 cx: &mut Context<Editor>,
402 );
403}
404
405pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
406
407impl GlobalDiagnosticRenderer {
408 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
409 cx.try_global::<Self>().map(|g| g.0.clone())
410 }
411}
412
413impl gpui::Global for GlobalDiagnosticRenderer {}
414pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
415 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
416}
417
418pub struct SearchWithinRange;
419
420trait InvalidationRegion {
421 fn ranges(&self) -> &[Range<Anchor>];
422}
423
424#[derive(Clone, Debug, PartialEq)]
425pub enum SelectPhase {
426 Begin {
427 position: DisplayPoint,
428 add: bool,
429 click_count: usize,
430 },
431 BeginColumnar {
432 position: DisplayPoint,
433 reset: bool,
434 mode: ColumnarMode,
435 goal_column: u32,
436 },
437 Extend {
438 position: DisplayPoint,
439 click_count: usize,
440 },
441 Update {
442 position: DisplayPoint,
443 goal_column: u32,
444 scroll_delta: gpui::Point<f32>,
445 },
446 End,
447}
448
449#[derive(Clone, Debug, PartialEq)]
450pub enum ColumnarMode {
451 FromMouse,
452 FromSelection,
453}
454
455#[derive(Clone, Debug)]
456pub enum SelectMode {
457 Character,
458 Word(Range<Anchor>),
459 Line(Range<Anchor>),
460 All,
461}
462
463#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
464pub enum SizingBehavior {
465 /// The editor will layout itself using `size_full` and will include the vertical
466 /// scroll margin as requested by user settings.
467 #[default]
468 Default,
469 /// The editor will layout itself using `size_full`, but will not have any
470 /// vertical overscroll.
471 ExcludeOverscrollMargin,
472 /// The editor will request a vertical size according to its content and will be
473 /// layouted without a vertical scroll margin.
474 SizeByContent,
475}
476
477#[derive(Clone, PartialEq, Eq, Debug)]
478pub enum EditorMode {
479 SingleLine,
480 AutoHeight {
481 min_lines: usize,
482 max_lines: Option<usize>,
483 },
484 Full {
485 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
486 scale_ui_elements_with_buffer_font_size: bool,
487 /// When set to `true`, the editor will render a background for the active line.
488 show_active_line_background: bool,
489 /// Determines the sizing behavior for this editor
490 sizing_behavior: SizingBehavior,
491 },
492 Minimap {
493 parent: WeakEntity<Editor>,
494 },
495}
496
497impl EditorMode {
498 pub fn full() -> Self {
499 Self::Full {
500 scale_ui_elements_with_buffer_font_size: true,
501 show_active_line_background: true,
502 sizing_behavior: SizingBehavior::Default,
503 }
504 }
505
506 #[inline]
507 pub fn is_full(&self) -> bool {
508 matches!(self, Self::Full { .. })
509 }
510
511 #[inline]
512 pub fn is_single_line(&self) -> bool {
513 matches!(self, Self::SingleLine { .. })
514 }
515
516 #[inline]
517 fn is_minimap(&self) -> bool {
518 matches!(self, Self::Minimap { .. })
519 }
520}
521
522#[derive(Copy, Clone, Debug)]
523pub enum SoftWrap {
524 /// Prefer not to wrap at all.
525 ///
526 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
527 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
528 GitDiff,
529 /// Prefer a single line generally, unless an overly long line is encountered.
530 None,
531 /// Soft wrap lines that exceed the editor width.
532 EditorWidth,
533 /// Soft wrap lines at the preferred line length.
534 Column(u32),
535 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
536 Bounded(u32),
537}
538
539#[derive(Clone)]
540pub struct EditorStyle {
541 pub background: Hsla,
542 pub border: Hsla,
543 pub local_player: PlayerColor,
544 pub text: TextStyle,
545 pub scrollbar_width: Pixels,
546 pub syntax: Arc<SyntaxTheme>,
547 pub status: StatusColors,
548 pub inlay_hints_style: HighlightStyle,
549 pub edit_prediction_styles: EditPredictionStyles,
550 pub unnecessary_code_fade: f32,
551 pub show_underlines: bool,
552}
553
554impl Default for EditorStyle {
555 fn default() -> Self {
556 Self {
557 background: Hsla::default(),
558 border: Hsla::default(),
559 local_player: PlayerColor::default(),
560 text: TextStyle::default(),
561 scrollbar_width: Pixels::default(),
562 syntax: Default::default(),
563 // HACK: Status colors don't have a real default.
564 // We should look into removing the status colors from the editor
565 // style and retrieve them directly from the theme.
566 status: StatusColors::dark(),
567 inlay_hints_style: HighlightStyle::default(),
568 edit_prediction_styles: EditPredictionStyles {
569 insertion: HighlightStyle::default(),
570 whitespace: HighlightStyle::default(),
571 },
572 unnecessary_code_fade: Default::default(),
573 show_underlines: true,
574 }
575 }
576}
577
578pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
579 let show_background = language_settings::language_settings(None, None, cx)
580 .inlay_hints
581 .show_background;
582
583 let mut style = cx.theme().syntax().get("hint");
584
585 if style.color.is_none() {
586 style.color = Some(cx.theme().status().hint);
587 }
588
589 if !show_background {
590 style.background_color = None;
591 return style;
592 }
593
594 if style.background_color.is_none() {
595 style.background_color = Some(cx.theme().status().hint_background);
596 }
597
598 style
599}
600
601pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
602 EditPredictionStyles {
603 insertion: HighlightStyle {
604 color: Some(cx.theme().status().predictive),
605 ..HighlightStyle::default()
606 },
607 whitespace: HighlightStyle {
608 background_color: Some(cx.theme().status().created_background),
609 ..HighlightStyle::default()
610 },
611 }
612}
613
614type CompletionId = usize;
615
616pub(crate) enum EditDisplayMode {
617 TabAccept,
618 DiffPopover,
619 Inline,
620}
621
622enum EditPrediction {
623 Edit {
624 edits: Vec<(Range<Anchor>, Arc<str>)>,
625 edit_preview: Option<EditPreview>,
626 display_mode: EditDisplayMode,
627 snapshot: BufferSnapshot,
628 },
629 /// Move to a specific location in the active editor
630 MoveWithin {
631 target: Anchor,
632 snapshot: BufferSnapshot,
633 },
634 /// Move to a specific location in a different editor (not the active one)
635 MoveOutside {
636 target: language::Anchor,
637 snapshot: BufferSnapshot,
638 },
639}
640
641struct EditPredictionState {
642 inlay_ids: Vec<InlayId>,
643 completion: EditPrediction,
644 completion_id: Option<SharedString>,
645 invalidation_range: Option<Range<Anchor>>,
646}
647
648enum EditPredictionSettings {
649 Disabled,
650 Enabled {
651 show_in_menu: bool,
652 preview_requires_modifier: bool,
653 },
654}
655
656enum EditPredictionHighlight {}
657
658#[derive(Debug, Clone)]
659struct InlineDiagnostic {
660 message: SharedString,
661 group_id: usize,
662 is_primary: bool,
663 start: Point,
664 severity: lsp::DiagnosticSeverity,
665}
666
667pub enum MenuEditPredictionsPolicy {
668 Never,
669 ByProvider,
670}
671
672pub enum EditPredictionPreview {
673 /// Modifier is not pressed
674 Inactive { released_too_fast: bool },
675 /// Modifier pressed
676 Active {
677 since: Instant,
678 previous_scroll_position: Option<ScrollAnchor>,
679 },
680}
681
682impl EditPredictionPreview {
683 pub fn released_too_fast(&self) -> bool {
684 match self {
685 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
686 EditPredictionPreview::Active { .. } => false,
687 }
688 }
689
690 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
691 if let EditPredictionPreview::Active {
692 previous_scroll_position,
693 ..
694 } = self
695 {
696 *previous_scroll_position = scroll_position;
697 }
698 }
699}
700
701pub struct ContextMenuOptions {
702 pub min_entries_visible: usize,
703 pub max_entries_visible: usize,
704 pub placement: Option<ContextMenuPlacement>,
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708pub enum ContextMenuPlacement {
709 Above,
710 Below,
711}
712
713#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
714struct EditorActionId(usize);
715
716impl EditorActionId {
717 pub fn post_inc(&mut self) -> Self {
718 let answer = self.0;
719
720 *self = Self(answer + 1);
721
722 Self(answer)
723 }
724}
725
726// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
727// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
728
729type BackgroundHighlight = (
730 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
731 Arc<[Range<Anchor>]>,
732);
733type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
734
735#[derive(Default)]
736struct ScrollbarMarkerState {
737 scrollbar_size: Size<Pixels>,
738 dirty: bool,
739 markers: Arc<[PaintQuad]>,
740 pending_refresh: Option<Task<Result<()>>>,
741}
742
743impl ScrollbarMarkerState {
744 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
745 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
746 }
747}
748
749#[derive(Clone, Copy, PartialEq, Eq)]
750pub enum MinimapVisibility {
751 Disabled,
752 Enabled {
753 /// The configuration currently present in the users settings.
754 setting_configuration: bool,
755 /// Whether to override the currently set visibility from the users setting.
756 toggle_override: bool,
757 },
758}
759
760impl MinimapVisibility {
761 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
762 if mode.is_full() {
763 Self::Enabled {
764 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
765 toggle_override: false,
766 }
767 } else {
768 Self::Disabled
769 }
770 }
771
772 fn hidden(&self) -> Self {
773 match *self {
774 Self::Enabled {
775 setting_configuration,
776 ..
777 } => Self::Enabled {
778 setting_configuration,
779 toggle_override: setting_configuration,
780 },
781 Self::Disabled => Self::Disabled,
782 }
783 }
784
785 fn disabled(&self) -> bool {
786 matches!(*self, Self::Disabled)
787 }
788
789 fn settings_visibility(&self) -> bool {
790 match *self {
791 Self::Enabled {
792 setting_configuration,
793 ..
794 } => setting_configuration,
795 _ => false,
796 }
797 }
798
799 fn visible(&self) -> bool {
800 match *self {
801 Self::Enabled {
802 setting_configuration,
803 toggle_override,
804 } => setting_configuration ^ toggle_override,
805 _ => false,
806 }
807 }
808
809 fn toggle_visibility(&self) -> Self {
810 match *self {
811 Self::Enabled {
812 toggle_override,
813 setting_configuration,
814 } => Self::Enabled {
815 setting_configuration,
816 toggle_override: !toggle_override,
817 },
818 Self::Disabled => Self::Disabled,
819 }
820 }
821}
822
823#[derive(Debug, Clone, Copy, PartialEq, Eq)]
824pub enum BufferSerialization {
825 All,
826 NonDirtyBuffers,
827}
828
829impl BufferSerialization {
830 fn new(restore_unsaved_buffers: bool) -> Self {
831 if restore_unsaved_buffers {
832 Self::All
833 } else {
834 Self::NonDirtyBuffers
835 }
836 }
837}
838
839#[derive(Clone, Debug)]
840struct RunnableTasks {
841 templates: Vec<(TaskSourceKind, TaskTemplate)>,
842 offset: multi_buffer::Anchor,
843 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
844 column: u32,
845 // Values of all named captures, including those starting with '_'
846 extra_variables: HashMap<String, String>,
847 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
848 context_range: Range<BufferOffset>,
849}
850
851impl RunnableTasks {
852 fn resolve<'a>(
853 &'a self,
854 cx: &'a task::TaskContext,
855 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
856 self.templates.iter().filter_map(|(kind, template)| {
857 template
858 .resolve_task(&kind.to_id_base(), cx)
859 .map(|task| (kind.clone(), task))
860 })
861 }
862}
863
864#[derive(Clone)]
865pub struct ResolvedTasks {
866 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
867 position: Anchor,
868}
869
870/// Addons allow storing per-editor state in other crates (e.g. Vim)
871pub trait Addon: 'static {
872 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
873
874 fn render_buffer_header_controls(
875 &self,
876 _: &ExcerptInfo,
877 _: &Window,
878 _: &App,
879 ) -> Option<AnyElement> {
880 None
881 }
882
883 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
884 None
885 }
886
887 fn to_any(&self) -> &dyn std::any::Any;
888
889 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
890 None
891 }
892}
893
894struct ChangeLocation {
895 current: Option<Vec<Anchor>>,
896 original: Vec<Anchor>,
897}
898impl ChangeLocation {
899 fn locations(&self) -> &[Anchor] {
900 self.current.as_ref().unwrap_or(&self.original)
901 }
902}
903
904/// A set of caret positions, registered when the editor was edited.
905pub struct ChangeList {
906 changes: Vec<ChangeLocation>,
907 /// Currently "selected" change.
908 position: Option<usize>,
909}
910
911impl ChangeList {
912 pub fn new() -> Self {
913 Self {
914 changes: Vec::new(),
915 position: None,
916 }
917 }
918
919 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
920 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
921 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
922 if self.changes.is_empty() {
923 return None;
924 }
925
926 let prev = self.position.unwrap_or(self.changes.len());
927 let next = if direction == Direction::Prev {
928 prev.saturating_sub(count)
929 } else {
930 (prev + count).min(self.changes.len() - 1)
931 };
932 self.position = Some(next);
933 self.changes.get(next).map(|change| change.locations())
934 }
935
936 /// Adds a new change to the list, resetting the change list position.
937 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
938 self.position.take();
939 if let Some(last) = self.changes.last_mut()
940 && group
941 {
942 last.current = Some(new_positions)
943 } else {
944 self.changes.push(ChangeLocation {
945 original: new_positions,
946 current: None,
947 });
948 }
949 }
950
951 pub fn last(&self) -> Option<&[Anchor]> {
952 self.changes.last().map(|change| change.locations())
953 }
954
955 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
956 self.changes.last().map(|change| change.original.as_slice())
957 }
958
959 pub fn invert_last_group(&mut self) {
960 if let Some(last) = self.changes.last_mut()
961 && let Some(current) = last.current.as_mut()
962 {
963 mem::swap(&mut last.original, current);
964 }
965 }
966}
967
968#[derive(Clone)]
969struct InlineBlamePopoverState {
970 scroll_handle: ScrollHandle,
971 commit_message: Option<ParsedCommitMessage>,
972 markdown: Entity<Markdown>,
973}
974
975struct InlineBlamePopover {
976 position: gpui::Point<Pixels>,
977 hide_task: Option<Task<()>>,
978 popover_bounds: Option<Bounds<Pixels>>,
979 popover_state: InlineBlamePopoverState,
980 keyboard_grace: bool,
981}
982
983enum SelectionDragState {
984 /// State when no drag related activity is detected.
985 None,
986 /// State when the mouse is down on a selection that is about to be dragged.
987 ReadyToDrag {
988 selection: Selection<Anchor>,
989 click_position: gpui::Point<Pixels>,
990 mouse_down_time: Instant,
991 },
992 /// State when the mouse is dragging the selection in the editor.
993 Dragging {
994 selection: Selection<Anchor>,
995 drop_cursor: Selection<Anchor>,
996 hide_drop_cursor: bool,
997 },
998}
999
1000enum ColumnarSelectionState {
1001 FromMouse {
1002 selection_tail: Anchor,
1003 display_point: Option<DisplayPoint>,
1004 },
1005 FromSelection {
1006 selection_tail: Anchor,
1007 },
1008}
1009
1010/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1011/// a breakpoint on them.
1012#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1013struct PhantomBreakpointIndicator {
1014 display_row: DisplayRow,
1015 /// There's a small debounce between hovering over the line and showing the indicator.
1016 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1017 is_active: bool,
1018 collides_with_existing_breakpoint: bool,
1019}
1020
1021/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1022///
1023/// See the [module level documentation](self) for more information.
1024pub struct Editor {
1025 focus_handle: FocusHandle,
1026 last_focused_descendant: Option<WeakFocusHandle>,
1027 /// The text buffer being edited
1028 buffer: Entity<MultiBuffer>,
1029 /// Map of how text in the buffer should be displayed.
1030 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1031 pub display_map: Entity<DisplayMap>,
1032 placeholder_display_map: Option<Entity<DisplayMap>>,
1033 pub selections: SelectionsCollection,
1034 pub scroll_manager: ScrollManager,
1035 /// When inline assist editors are linked, they all render cursors because
1036 /// typing enters text into each of them, even the ones that aren't focused.
1037 pub(crate) show_cursor_when_unfocused: bool,
1038 columnar_selection_state: Option<ColumnarSelectionState>,
1039 add_selections_state: Option<AddSelectionsState>,
1040 select_next_state: Option<SelectNextState>,
1041 select_prev_state: Option<SelectNextState>,
1042 selection_history: SelectionHistory,
1043 defer_selection_effects: bool,
1044 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1045 autoclose_regions: Vec<AutocloseRegion>,
1046 snippet_stack: InvalidationStack<SnippetState>,
1047 select_syntax_node_history: SelectSyntaxNodeHistory,
1048 ime_transaction: Option<TransactionId>,
1049 pub diagnostics_max_severity: DiagnosticSeverity,
1050 active_diagnostics: ActiveDiagnostic,
1051 show_inline_diagnostics: bool,
1052 inline_diagnostics_update: Task<()>,
1053 inline_diagnostics_enabled: bool,
1054 diagnostics_enabled: bool,
1055 word_completions_enabled: bool,
1056 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1057 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1058 hard_wrap: Option<usize>,
1059 project: Option<Entity<Project>>,
1060 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1061 completion_provider: Option<Rc<dyn CompletionProvider>>,
1062 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1063 blink_manager: Entity<BlinkManager>,
1064 show_cursor_names: bool,
1065 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1066 pub show_local_selections: bool,
1067 mode: EditorMode,
1068 show_breadcrumbs: bool,
1069 show_gutter: bool,
1070 show_scrollbars: ScrollbarAxes,
1071 minimap_visibility: MinimapVisibility,
1072 offset_content: bool,
1073 disable_expand_excerpt_buttons: bool,
1074 show_line_numbers: Option<bool>,
1075 use_relative_line_numbers: Option<bool>,
1076 show_git_diff_gutter: Option<bool>,
1077 show_code_actions: Option<bool>,
1078 show_runnables: Option<bool>,
1079 show_breakpoints: Option<bool>,
1080 show_wrap_guides: Option<bool>,
1081 show_indent_guides: Option<bool>,
1082 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1083 highlight_order: usize,
1084 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1085 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1086 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1087 scrollbar_marker_state: ScrollbarMarkerState,
1088 active_indent_guides_state: ActiveIndentGuidesState,
1089 nav_history: Option<ItemNavHistory>,
1090 context_menu: RefCell<Option<CodeContextMenu>>,
1091 context_menu_options: Option<ContextMenuOptions>,
1092 mouse_context_menu: Option<MouseContextMenu>,
1093 completion_tasks: Vec<(CompletionId, Task<()>)>,
1094 inline_blame_popover: Option<InlineBlamePopover>,
1095 inline_blame_popover_show_task: Option<Task<()>>,
1096 signature_help_state: SignatureHelpState,
1097 auto_signature_help: Option<bool>,
1098 find_all_references_task_sources: Vec<Anchor>,
1099 next_completion_id: CompletionId,
1100 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1101 code_actions_task: Option<Task<Result<()>>>,
1102 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1103 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1104 document_highlights_task: Option<Task<()>>,
1105 linked_editing_range_task: Option<Task<Option<()>>>,
1106 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1107 pending_rename: Option<RenameState>,
1108 searchable: bool,
1109 cursor_shape: CursorShape,
1110 /// Whether the cursor is offset one character to the left when something is
1111 /// selected (needed for vim visual mode)
1112 cursor_offset_on_selection: bool,
1113 current_line_highlight: Option<CurrentLineHighlight>,
1114 pub collapse_matches: bool,
1115 autoindent_mode: Option<AutoindentMode>,
1116 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1117 input_enabled: bool,
1118 use_modal_editing: bool,
1119 read_only: bool,
1120 leader_id: Option<CollaboratorId>,
1121 remote_id: Option<ViewId>,
1122 pub hover_state: HoverState,
1123 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1124 prev_pressure_stage: Option<PressureStage>,
1125 gutter_hovered: bool,
1126 hovered_link_state: Option<HoveredLinkState>,
1127 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1128 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1129 active_edit_prediction: Option<EditPredictionState>,
1130 /// Used to prevent flickering as the user types while the menu is open
1131 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1132 edit_prediction_settings: EditPredictionSettings,
1133 edit_predictions_hidden_for_vim_mode: bool,
1134 show_edit_predictions_override: Option<bool>,
1135 show_completions_on_input_override: Option<bool>,
1136 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1137 edit_prediction_preview: EditPredictionPreview,
1138 edit_prediction_indent_conflict: bool,
1139 edit_prediction_requires_modifier_in_indent_conflict: bool,
1140 next_inlay_id: usize,
1141 next_color_inlay_id: usize,
1142 _subscriptions: Vec<Subscription>,
1143 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1144 gutter_dimensions: GutterDimensions,
1145 style: Option<EditorStyle>,
1146 text_style_refinement: Option<TextStyleRefinement>,
1147 next_editor_action_id: EditorActionId,
1148 editor_actions: Rc<
1149 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1150 >,
1151 use_autoclose: bool,
1152 use_auto_surround: bool,
1153 auto_replace_emoji_shortcode: bool,
1154 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1155 show_git_blame_gutter: bool,
1156 show_git_blame_inline: bool,
1157 show_git_blame_inline_delay_task: Option<Task<()>>,
1158 git_blame_inline_enabled: bool,
1159 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1160 buffer_serialization: Option<BufferSerialization>,
1161 show_selection_menu: Option<bool>,
1162 blame: Option<Entity<GitBlame>>,
1163 blame_subscription: Option<Subscription>,
1164 custom_context_menu: Option<
1165 Box<
1166 dyn 'static
1167 + Fn(
1168 &mut Self,
1169 DisplayPoint,
1170 &mut Window,
1171 &mut Context<Self>,
1172 ) -> Option<Entity<ui::ContextMenu>>,
1173 >,
1174 >,
1175 last_bounds: Option<Bounds<Pixels>>,
1176 last_position_map: Option<Rc<PositionMap>>,
1177 expect_bounds_change: Option<Bounds<Pixels>>,
1178 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1179 tasks_update_task: Option<Task<()>>,
1180 breakpoint_store: Option<Entity<BreakpointStore>>,
1181 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1182 hovered_diff_hunk_row: Option<DisplayRow>,
1183 pull_diagnostics_task: Task<()>,
1184 pull_diagnostics_background_task: Task<()>,
1185 in_project_search: bool,
1186 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1187 breadcrumb_header: Option<String>,
1188 focused_block: Option<FocusedBlock>,
1189 next_scroll_position: NextScrollCursorCenterTopBottom,
1190 addons: HashMap<TypeId, Box<dyn Addon>>,
1191 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1192 load_diff_task: Option<Shared<Task<()>>>,
1193 /// Whether we are temporarily displaying a diff other than git's
1194 temporary_diff_override: bool,
1195 selection_mark_mode: bool,
1196 toggle_fold_multiple_buffers: Task<()>,
1197 _scroll_cursor_center_top_bottom_task: Task<()>,
1198 serialize_selections: Task<()>,
1199 serialize_folds: Task<()>,
1200 mouse_cursor_hidden: bool,
1201 minimap: Option<Entity<Self>>,
1202 hide_mouse_mode: HideMouseMode,
1203 pub change_list: ChangeList,
1204 inline_value_cache: InlineValueCache,
1205
1206 selection_drag_state: SelectionDragState,
1207 colors: Option<LspColorData>,
1208 post_scroll_update: Task<()>,
1209 refresh_colors_task: Task<()>,
1210 inlay_hints: Option<LspInlayHintData>,
1211 folding_newlines: Task<()>,
1212 select_next_is_case_sensitive: Option<bool>,
1213 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1214 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1215 accent_data: Option<AccentData>,
1216 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1217 use_base_text_line_numbers: bool,
1218}
1219
1220#[derive(Debug, PartialEq)]
1221struct AccentData {
1222 colors: AccentColors,
1223 overrides: Vec<SharedString>,
1224}
1225
1226fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1227 if debounce_ms > 0 {
1228 Some(Duration::from_millis(debounce_ms))
1229 } else {
1230 None
1231 }
1232}
1233
1234#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1235enum NextScrollCursorCenterTopBottom {
1236 #[default]
1237 Center,
1238 Top,
1239 Bottom,
1240}
1241
1242impl NextScrollCursorCenterTopBottom {
1243 fn next(&self) -> Self {
1244 match self {
1245 Self::Center => Self::Top,
1246 Self::Top => Self::Bottom,
1247 Self::Bottom => Self::Center,
1248 }
1249 }
1250}
1251
1252#[derive(Clone)]
1253pub struct EditorSnapshot {
1254 pub mode: EditorMode,
1255 show_gutter: bool,
1256 offset_content: bool,
1257 show_line_numbers: Option<bool>,
1258 show_git_diff_gutter: Option<bool>,
1259 show_code_actions: Option<bool>,
1260 show_runnables: Option<bool>,
1261 show_breakpoints: Option<bool>,
1262 git_blame_gutter_max_author_length: Option<usize>,
1263 pub display_snapshot: DisplaySnapshot,
1264 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1265 is_focused: bool,
1266 scroll_anchor: ScrollAnchor,
1267 ongoing_scroll: OngoingScroll,
1268 current_line_highlight: CurrentLineHighlight,
1269 gutter_hovered: bool,
1270}
1271
1272#[derive(Default, Debug, Clone, Copy)]
1273pub struct GutterDimensions {
1274 pub left_padding: Pixels,
1275 pub right_padding: Pixels,
1276 pub width: Pixels,
1277 pub margin: Pixels,
1278 pub git_blame_entries_width: Option<Pixels>,
1279}
1280
1281impl GutterDimensions {
1282 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1283 Self {
1284 margin: Self::default_gutter_margin(font_id, font_size, cx),
1285 ..Default::default()
1286 }
1287 }
1288
1289 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1290 -cx.text_system().descent(font_id, font_size)
1291 }
1292 /// The full width of the space taken up by the gutter.
1293 pub fn full_width(&self) -> Pixels {
1294 self.margin + self.width
1295 }
1296
1297 /// The width of the space reserved for the fold indicators,
1298 /// use alongside 'justify_end' and `gutter_width` to
1299 /// right align content with the line numbers
1300 pub fn fold_area_width(&self) -> Pixels {
1301 self.margin + self.right_padding
1302 }
1303}
1304
1305struct CharacterDimensions {
1306 em_width: Pixels,
1307 em_advance: Pixels,
1308 line_height: Pixels,
1309}
1310
1311#[derive(Debug)]
1312pub struct RemoteSelection {
1313 pub replica_id: ReplicaId,
1314 pub selection: Selection<Anchor>,
1315 pub cursor_shape: CursorShape,
1316 pub collaborator_id: CollaboratorId,
1317 pub line_mode: bool,
1318 pub user_name: Option<SharedString>,
1319 pub color: PlayerColor,
1320}
1321
1322#[derive(Clone, Debug)]
1323struct SelectionHistoryEntry {
1324 selections: Arc<[Selection<Anchor>]>,
1325 select_next_state: Option<SelectNextState>,
1326 select_prev_state: Option<SelectNextState>,
1327 add_selections_state: Option<AddSelectionsState>,
1328}
1329
1330#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1331enum SelectionHistoryMode {
1332 #[default]
1333 Normal,
1334 Undoing,
1335 Redoing,
1336 Skipping,
1337}
1338
1339#[derive(Clone, PartialEq, Eq, Hash)]
1340struct HoveredCursor {
1341 replica_id: ReplicaId,
1342 selection_id: usize,
1343}
1344
1345#[derive(Debug)]
1346/// SelectionEffects controls the side-effects of updating the selection.
1347///
1348/// The default behaviour does "what you mostly want":
1349/// - it pushes to the nav history if the cursor moved by >10 lines
1350/// - it re-triggers completion requests
1351/// - it scrolls to fit
1352///
1353/// You might want to modify these behaviours. For example when doing a "jump"
1354/// like go to definition, we always want to add to nav history; but when scrolling
1355/// in vim mode we never do.
1356///
1357/// Similarly, you might want to disable scrolling if you don't want the viewport to
1358/// move.
1359#[derive(Clone)]
1360pub struct SelectionEffects {
1361 nav_history: Option<bool>,
1362 completions: bool,
1363 scroll: Option<Autoscroll>,
1364}
1365
1366impl Default for SelectionEffects {
1367 fn default() -> Self {
1368 Self {
1369 nav_history: None,
1370 completions: true,
1371 scroll: Some(Autoscroll::fit()),
1372 }
1373 }
1374}
1375impl SelectionEffects {
1376 pub fn scroll(scroll: Autoscroll) -> Self {
1377 Self {
1378 scroll: Some(scroll),
1379 ..Default::default()
1380 }
1381 }
1382
1383 pub fn no_scroll() -> Self {
1384 Self {
1385 scroll: None,
1386 ..Default::default()
1387 }
1388 }
1389
1390 pub fn completions(self, completions: bool) -> Self {
1391 Self {
1392 completions,
1393 ..self
1394 }
1395 }
1396
1397 pub fn nav_history(self, nav_history: bool) -> Self {
1398 Self {
1399 nav_history: Some(nav_history),
1400 ..self
1401 }
1402 }
1403}
1404
1405struct DeferredSelectionEffectsState {
1406 changed: bool,
1407 effects: SelectionEffects,
1408 old_cursor_position: Anchor,
1409 history_entry: SelectionHistoryEntry,
1410}
1411
1412#[derive(Default)]
1413struct SelectionHistory {
1414 #[allow(clippy::type_complexity)]
1415 selections_by_transaction:
1416 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1417 mode: SelectionHistoryMode,
1418 undo_stack: VecDeque<SelectionHistoryEntry>,
1419 redo_stack: VecDeque<SelectionHistoryEntry>,
1420}
1421
1422impl SelectionHistory {
1423 #[track_caller]
1424 fn insert_transaction(
1425 &mut self,
1426 transaction_id: TransactionId,
1427 selections: Arc<[Selection<Anchor>]>,
1428 ) {
1429 if selections.is_empty() {
1430 log::error!(
1431 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1432 std::panic::Location::caller()
1433 );
1434 return;
1435 }
1436 self.selections_by_transaction
1437 .insert(transaction_id, (selections, None));
1438 }
1439
1440 #[allow(clippy::type_complexity)]
1441 fn transaction(
1442 &self,
1443 transaction_id: TransactionId,
1444 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1445 self.selections_by_transaction.get(&transaction_id)
1446 }
1447
1448 #[allow(clippy::type_complexity)]
1449 fn transaction_mut(
1450 &mut self,
1451 transaction_id: TransactionId,
1452 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1453 self.selections_by_transaction.get_mut(&transaction_id)
1454 }
1455
1456 fn push(&mut self, entry: SelectionHistoryEntry) {
1457 if !entry.selections.is_empty() {
1458 match self.mode {
1459 SelectionHistoryMode::Normal => {
1460 self.push_undo(entry);
1461 self.redo_stack.clear();
1462 }
1463 SelectionHistoryMode::Undoing => self.push_redo(entry),
1464 SelectionHistoryMode::Redoing => self.push_undo(entry),
1465 SelectionHistoryMode::Skipping => {}
1466 }
1467 }
1468 }
1469
1470 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1471 if self
1472 .undo_stack
1473 .back()
1474 .is_none_or(|e| e.selections != entry.selections)
1475 {
1476 self.undo_stack.push_back(entry);
1477 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1478 self.undo_stack.pop_front();
1479 }
1480 }
1481 }
1482
1483 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1484 if self
1485 .redo_stack
1486 .back()
1487 .is_none_or(|e| e.selections != entry.selections)
1488 {
1489 self.redo_stack.push_back(entry);
1490 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1491 self.redo_stack.pop_front();
1492 }
1493 }
1494 }
1495}
1496
1497#[derive(Clone, Copy)]
1498pub struct RowHighlightOptions {
1499 pub autoscroll: bool,
1500 pub include_gutter: bool,
1501}
1502
1503impl Default for RowHighlightOptions {
1504 fn default() -> Self {
1505 Self {
1506 autoscroll: Default::default(),
1507 include_gutter: true,
1508 }
1509 }
1510}
1511
1512struct RowHighlight {
1513 index: usize,
1514 range: Range<Anchor>,
1515 color: Hsla,
1516 options: RowHighlightOptions,
1517 type_id: TypeId,
1518}
1519
1520#[derive(Clone, Debug)]
1521struct AddSelectionsState {
1522 groups: Vec<AddSelectionsGroup>,
1523}
1524
1525#[derive(Clone, Debug)]
1526struct AddSelectionsGroup {
1527 above: bool,
1528 stack: Vec<usize>,
1529}
1530
1531#[derive(Clone)]
1532struct SelectNextState {
1533 query: AhoCorasick,
1534 wordwise: bool,
1535 done: bool,
1536}
1537
1538impl std::fmt::Debug for SelectNextState {
1539 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1540 f.debug_struct(std::any::type_name::<Self>())
1541 .field("wordwise", &self.wordwise)
1542 .field("done", &self.done)
1543 .finish()
1544 }
1545}
1546
1547#[derive(Debug)]
1548struct AutocloseRegion {
1549 selection_id: usize,
1550 range: Range<Anchor>,
1551 pair: BracketPair,
1552}
1553
1554#[derive(Debug)]
1555struct SnippetState {
1556 ranges: Vec<Vec<Range<Anchor>>>,
1557 active_index: usize,
1558 choices: Vec<Option<Vec<String>>>,
1559}
1560
1561#[doc(hidden)]
1562pub struct RenameState {
1563 pub range: Range<Anchor>,
1564 pub old_name: Arc<str>,
1565 pub editor: Entity<Editor>,
1566 block_id: CustomBlockId,
1567}
1568
1569struct InvalidationStack<T>(Vec<T>);
1570
1571struct RegisteredEditPredictionDelegate {
1572 provider: Arc<dyn EditPredictionDelegateHandle>,
1573 _subscription: Subscription,
1574}
1575
1576#[derive(Debug, PartialEq, Eq)]
1577pub struct ActiveDiagnosticGroup {
1578 pub active_range: Range<Anchor>,
1579 pub active_message: String,
1580 pub group_id: usize,
1581 pub blocks: HashSet<CustomBlockId>,
1582}
1583
1584#[derive(Debug, PartialEq, Eq)]
1585
1586pub(crate) enum ActiveDiagnostic {
1587 None,
1588 All,
1589 Group(ActiveDiagnosticGroup),
1590}
1591
1592#[derive(Serialize, Deserialize, Clone, Debug)]
1593pub struct ClipboardSelection {
1594 /// The number of bytes in this selection.
1595 pub len: usize,
1596 /// Whether this was a full-line selection.
1597 pub is_entire_line: bool,
1598 /// The indentation of the first line when this content was originally copied.
1599 pub first_line_indent: u32,
1600 #[serde(default)]
1601 pub file_path: Option<PathBuf>,
1602 #[serde(default)]
1603 pub line_range: Option<RangeInclusive<u32>>,
1604}
1605
1606impl ClipboardSelection {
1607 pub fn for_buffer(
1608 len: usize,
1609 is_entire_line: bool,
1610 range: Range<Point>,
1611 buffer: &MultiBufferSnapshot,
1612 project: Option<&Entity<Project>>,
1613 cx: &App,
1614 ) -> Self {
1615 let first_line_indent = buffer
1616 .indent_size_for_line(MultiBufferRow(range.start.row))
1617 .len;
1618
1619 let file_path = util::maybe!({
1620 let project = project?.read(cx);
1621 let file = buffer.file_at(range.start)?;
1622 let project_path = ProjectPath {
1623 worktree_id: file.worktree_id(cx),
1624 path: file.path().clone(),
1625 };
1626 project.absolute_path(&project_path, cx)
1627 });
1628
1629 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1630
1631 Self {
1632 len,
1633 is_entire_line,
1634 first_line_indent,
1635 file_path,
1636 line_range,
1637 }
1638 }
1639}
1640
1641// selections, scroll behavior, was newest selection reversed
1642type SelectSyntaxNodeHistoryState = (
1643 Box<[Selection<MultiBufferOffset>]>,
1644 SelectSyntaxNodeScrollBehavior,
1645 bool,
1646);
1647
1648#[derive(Default)]
1649struct SelectSyntaxNodeHistory {
1650 stack: Vec<SelectSyntaxNodeHistoryState>,
1651 // disable temporarily to allow changing selections without losing the stack
1652 pub disable_clearing: bool,
1653}
1654
1655impl SelectSyntaxNodeHistory {
1656 pub fn try_clear(&mut self) {
1657 if !self.disable_clearing {
1658 self.stack.clear();
1659 }
1660 }
1661
1662 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1663 self.stack.push(selection);
1664 }
1665
1666 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1667 self.stack.pop()
1668 }
1669}
1670
1671enum SelectSyntaxNodeScrollBehavior {
1672 CursorTop,
1673 FitSelection,
1674 CursorBottom,
1675}
1676
1677#[derive(Debug)]
1678pub(crate) struct NavigationData {
1679 cursor_anchor: Anchor,
1680 cursor_position: Point,
1681 scroll_anchor: ScrollAnchor,
1682 scroll_top_row: u32,
1683}
1684
1685#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1686pub enum GotoDefinitionKind {
1687 Symbol,
1688 Declaration,
1689 Type,
1690 Implementation,
1691}
1692
1693pub enum FormatTarget {
1694 Buffers(HashSet<Entity<Buffer>>),
1695 Ranges(Vec<Range<MultiBufferPoint>>),
1696}
1697
1698pub(crate) struct FocusedBlock {
1699 id: BlockId,
1700 focus_handle: WeakFocusHandle,
1701}
1702
1703#[derive(Clone, Debug)]
1704enum JumpData {
1705 MultiBufferRow {
1706 row: MultiBufferRow,
1707 line_offset_from_top: u32,
1708 },
1709 MultiBufferPoint {
1710 excerpt_id: ExcerptId,
1711 position: Point,
1712 anchor: text::Anchor,
1713 line_offset_from_top: u32,
1714 },
1715}
1716
1717pub enum MultibufferSelectionMode {
1718 First,
1719 All,
1720}
1721
1722#[derive(Clone, Copy, Debug, Default)]
1723pub struct RewrapOptions {
1724 pub override_language_settings: bool,
1725 pub preserve_existing_whitespace: bool,
1726}
1727
1728impl Editor {
1729 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1730 let buffer = cx.new(|cx| Buffer::local("", cx));
1731 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1732 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1733 }
1734
1735 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1736 let buffer = cx.new(|cx| Buffer::local("", cx));
1737 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1738 Self::new(EditorMode::full(), buffer, None, window, cx)
1739 }
1740
1741 pub fn auto_height(
1742 min_lines: usize,
1743 max_lines: usize,
1744 window: &mut Window,
1745 cx: &mut Context<Self>,
1746 ) -> Self {
1747 let buffer = cx.new(|cx| Buffer::local("", cx));
1748 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1749 Self::new(
1750 EditorMode::AutoHeight {
1751 min_lines,
1752 max_lines: Some(max_lines),
1753 },
1754 buffer,
1755 None,
1756 window,
1757 cx,
1758 )
1759 }
1760
1761 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1762 /// The editor grows as tall as needed to fit its content.
1763 pub fn auto_height_unbounded(
1764 min_lines: usize,
1765 window: &mut Window,
1766 cx: &mut Context<Self>,
1767 ) -> Self {
1768 let buffer = cx.new(|cx| Buffer::local("", cx));
1769 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1770 Self::new(
1771 EditorMode::AutoHeight {
1772 min_lines,
1773 max_lines: None,
1774 },
1775 buffer,
1776 None,
1777 window,
1778 cx,
1779 )
1780 }
1781
1782 pub fn for_buffer(
1783 buffer: Entity<Buffer>,
1784 project: Option<Entity<Project>>,
1785 window: &mut Window,
1786 cx: &mut Context<Self>,
1787 ) -> Self {
1788 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1789 Self::new(EditorMode::full(), buffer, project, window, cx)
1790 }
1791
1792 pub fn for_multibuffer(
1793 buffer: Entity<MultiBuffer>,
1794 project: Option<Entity<Project>>,
1795 window: &mut Window,
1796 cx: &mut Context<Self>,
1797 ) -> Self {
1798 Self::new(EditorMode::full(), buffer, project, window, cx)
1799 }
1800
1801 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1802 let mut clone = Self::new(
1803 self.mode.clone(),
1804 self.buffer.clone(),
1805 self.project.clone(),
1806 window,
1807 cx,
1808 );
1809 self.display_map.update(cx, |display_map, cx| {
1810 let snapshot = display_map.snapshot(cx);
1811 clone.display_map.update(cx, |display_map, cx| {
1812 display_map.set_state(&snapshot, cx);
1813 });
1814 });
1815 clone.folds_did_change(cx);
1816 clone.selections.clone_state(&self.selections);
1817 clone.scroll_manager.clone_state(&self.scroll_manager);
1818 clone.searchable = self.searchable;
1819 clone.read_only = self.read_only;
1820 clone
1821 }
1822
1823 pub fn new(
1824 mode: EditorMode,
1825 buffer: Entity<MultiBuffer>,
1826 project: Option<Entity<Project>>,
1827 window: &mut Window,
1828 cx: &mut Context<Self>,
1829 ) -> Self {
1830 Editor::new_internal(mode, buffer, project, None, window, cx)
1831 }
1832
1833 pub fn sticky_headers(
1834 &self,
1835 style: &EditorStyle,
1836 cx: &App,
1837 ) -> Option<Vec<OutlineItem<Anchor>>> {
1838 let multi_buffer = self.buffer().read(cx);
1839 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1840 let multi_buffer_visible_start = self
1841 .scroll_manager
1842 .anchor()
1843 .anchor
1844 .to_point(&multi_buffer_snapshot);
1845 let max_row = multi_buffer_snapshot.max_point().row;
1846
1847 let start_row = (multi_buffer_visible_start.row).min(max_row);
1848 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1849
1850 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1851 let outline_items = buffer
1852 .outline_items_containing(
1853 Point::new(start_row, 0)..Point::new(end_row, 0),
1854 true,
1855 Some(style.syntax.as_ref()),
1856 )
1857 .into_iter()
1858 .map(|outline_item| OutlineItem {
1859 depth: outline_item.depth,
1860 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1861 source_range_for_text: Anchor::range_in_buffer(
1862 *excerpt_id,
1863 outline_item.source_range_for_text,
1864 ),
1865 text: outline_item.text,
1866 highlight_ranges: outline_item.highlight_ranges,
1867 name_ranges: outline_item.name_ranges,
1868 body_range: outline_item
1869 .body_range
1870 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1871 annotation_range: outline_item
1872 .annotation_range
1873 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1874 });
1875 return Some(outline_items.collect());
1876 }
1877
1878 None
1879 }
1880
1881 fn new_internal(
1882 mode: EditorMode,
1883 multi_buffer: Entity<MultiBuffer>,
1884 project: Option<Entity<Project>>,
1885 display_map: Option<Entity<DisplayMap>>,
1886 window: &mut Window,
1887 cx: &mut Context<Self>,
1888 ) -> Self {
1889 debug_assert!(
1890 display_map.is_none() || mode.is_minimap(),
1891 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1892 );
1893
1894 let full_mode = mode.is_full();
1895 let is_minimap = mode.is_minimap();
1896 let diagnostics_max_severity = if full_mode {
1897 EditorSettings::get_global(cx)
1898 .diagnostics_max_severity
1899 .unwrap_or(DiagnosticSeverity::Hint)
1900 } else {
1901 DiagnosticSeverity::Off
1902 };
1903 let style = window.text_style();
1904 let font_size = style.font_size.to_pixels(window.rem_size());
1905 let editor = cx.entity().downgrade();
1906 let fold_placeholder = FoldPlaceholder {
1907 constrain_width: false,
1908 render: Arc::new(move |fold_id, fold_range, cx| {
1909 let editor = editor.clone();
1910 div()
1911 .id(fold_id)
1912 .bg(cx.theme().colors().ghost_element_background)
1913 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1914 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1915 .rounded_xs()
1916 .size_full()
1917 .cursor_pointer()
1918 .child("⋯")
1919 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1920 .on_click(move |_, _window, cx| {
1921 editor
1922 .update(cx, |editor, cx| {
1923 editor.unfold_ranges(
1924 &[fold_range.start..fold_range.end],
1925 true,
1926 false,
1927 cx,
1928 );
1929 cx.stop_propagation();
1930 })
1931 .ok();
1932 })
1933 .into_any()
1934 }),
1935 merge_adjacent: true,
1936 ..FoldPlaceholder::default()
1937 };
1938 let display_map = display_map.unwrap_or_else(|| {
1939 cx.new(|cx| {
1940 DisplayMap::new(
1941 multi_buffer.clone(),
1942 style.font(),
1943 font_size,
1944 None,
1945 FILE_HEADER_HEIGHT,
1946 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1947 fold_placeholder,
1948 diagnostics_max_severity,
1949 cx,
1950 )
1951 })
1952 });
1953
1954 let selections = SelectionsCollection::new();
1955
1956 let blink_manager = cx.new(|cx| {
1957 let mut blink_manager = BlinkManager::new(
1958 CURSOR_BLINK_INTERVAL,
1959 |cx| EditorSettings::get_global(cx).cursor_blink,
1960 cx,
1961 );
1962 if is_minimap {
1963 blink_manager.disable(cx);
1964 }
1965 blink_manager
1966 });
1967
1968 let soft_wrap_mode_override =
1969 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1970
1971 let mut project_subscriptions = Vec::new();
1972 if full_mode && let Some(project) = project.as_ref() {
1973 project_subscriptions.push(cx.subscribe_in(
1974 project,
1975 window,
1976 |editor, _, event, window, cx| match event {
1977 project::Event::RefreshCodeLens => {
1978 // we always query lens with actions, without storing them, always refreshing them
1979 }
1980 project::Event::RefreshInlayHints {
1981 server_id,
1982 request_id,
1983 } => {
1984 editor.refresh_inlay_hints(
1985 InlayHintRefreshReason::RefreshRequested {
1986 server_id: *server_id,
1987 request_id: *request_id,
1988 },
1989 cx,
1990 );
1991 }
1992 project::Event::LanguageServerRemoved(..) => {
1993 if editor.tasks_update_task.is_none() {
1994 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1995 }
1996 editor.registered_buffers.clear();
1997 editor.register_visible_buffers(cx);
1998 }
1999 project::Event::LanguageServerAdded(..) => {
2000 if editor.tasks_update_task.is_none() {
2001 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2002 }
2003 }
2004 project::Event::SnippetEdit(id, snippet_edits) => {
2005 // todo(lw): Non singletons
2006 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2007 let snapshot = buffer.read(cx).snapshot();
2008 let focus_handle = editor.focus_handle(cx);
2009 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2010 for (range, snippet) in snippet_edits {
2011 let buffer_range =
2012 language::range_from_lsp(*range).to_offset(&snapshot);
2013 editor
2014 .insert_snippet(
2015 &[MultiBufferOffset(buffer_range.start)
2016 ..MultiBufferOffset(buffer_range.end)],
2017 snippet.clone(),
2018 window,
2019 cx,
2020 )
2021 .ok();
2022 }
2023 }
2024 }
2025 }
2026 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2027 let buffer_id = *buffer_id;
2028 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2029 editor.register_buffer(buffer_id, cx);
2030 editor.update_lsp_data(Some(buffer_id), window, cx);
2031 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2032 refresh_linked_ranges(editor, window, cx);
2033 editor.refresh_code_actions(window, cx);
2034 editor.refresh_document_highlights(cx);
2035 }
2036 }
2037
2038 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2039 let Some(workspace) = editor.workspace() else {
2040 return;
2041 };
2042 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2043 else {
2044 return;
2045 };
2046
2047 if active_editor.entity_id() == cx.entity_id() {
2048 let entity_id = cx.entity_id();
2049 workspace.update(cx, |this, cx| {
2050 this.panes_mut()
2051 .iter_mut()
2052 .filter(|pane| pane.entity_id() != entity_id)
2053 .for_each(|p| {
2054 p.update(cx, |pane, _| {
2055 pane.nav_history_mut().rename_item(
2056 entity_id,
2057 project_path.clone(),
2058 abs_path.clone().into(),
2059 );
2060 })
2061 });
2062 });
2063
2064 Self::open_transaction_for_hidden_buffers(
2065 workspace,
2066 transaction.clone(),
2067 "Rename".to_string(),
2068 window,
2069 cx,
2070 );
2071 }
2072 }
2073
2074 project::Event::WorkspaceEditApplied(transaction) => {
2075 let Some(workspace) = editor.workspace() else {
2076 return;
2077 };
2078 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2079 else {
2080 return;
2081 };
2082
2083 if active_editor.entity_id() == cx.entity_id() {
2084 Self::open_transaction_for_hidden_buffers(
2085 workspace,
2086 transaction.clone(),
2087 "LSP Edit".to_string(),
2088 window,
2089 cx,
2090 );
2091 }
2092 }
2093
2094 _ => {}
2095 },
2096 ));
2097 if let Some(task_inventory) = project
2098 .read(cx)
2099 .task_store()
2100 .read(cx)
2101 .task_inventory()
2102 .cloned()
2103 {
2104 project_subscriptions.push(cx.observe_in(
2105 &task_inventory,
2106 window,
2107 |editor, _, window, cx| {
2108 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2109 },
2110 ));
2111 };
2112
2113 project_subscriptions.push(cx.subscribe_in(
2114 &project.read(cx).breakpoint_store(),
2115 window,
2116 |editor, _, event, window, cx| match event {
2117 BreakpointStoreEvent::ClearDebugLines => {
2118 editor.clear_row_highlights::<ActiveDebugLine>();
2119 editor.refresh_inline_values(cx);
2120 }
2121 BreakpointStoreEvent::SetDebugLine => {
2122 if editor.go_to_active_debug_line(window, cx) {
2123 cx.stop_propagation();
2124 }
2125
2126 editor.refresh_inline_values(cx);
2127 }
2128 _ => {}
2129 },
2130 ));
2131 let git_store = project.read(cx).git_store().clone();
2132 let project = project.clone();
2133 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2134 if let GitStoreEvent::RepositoryAdded = event {
2135 this.load_diff_task = Some(
2136 update_uncommitted_diff_for_buffer(
2137 cx.entity(),
2138 &project,
2139 this.buffer.read(cx).all_buffers(),
2140 this.buffer.clone(),
2141 cx,
2142 )
2143 .shared(),
2144 );
2145 }
2146 }));
2147 }
2148
2149 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2150
2151 let inlay_hint_settings =
2152 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2153 let focus_handle = cx.focus_handle();
2154 if !is_minimap {
2155 cx.on_focus(&focus_handle, window, Self::handle_focus)
2156 .detach();
2157 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2158 .detach();
2159 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2160 .detach();
2161 cx.on_blur(&focus_handle, window, Self::handle_blur)
2162 .detach();
2163 cx.observe_pending_input(window, Self::observe_pending_input)
2164 .detach();
2165 }
2166
2167 let show_indent_guides =
2168 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2169 Some(false)
2170 } else {
2171 None
2172 };
2173
2174 let breakpoint_store = match (&mode, project.as_ref()) {
2175 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2176 _ => None,
2177 };
2178
2179 let mut code_action_providers = Vec::new();
2180 let mut load_uncommitted_diff = None;
2181 if let Some(project) = project.clone() {
2182 load_uncommitted_diff = Some(
2183 update_uncommitted_diff_for_buffer(
2184 cx.entity(),
2185 &project,
2186 multi_buffer.read(cx).all_buffers(),
2187 multi_buffer.clone(),
2188 cx,
2189 )
2190 .shared(),
2191 );
2192 code_action_providers.push(Rc::new(project) as Rc<_>);
2193 }
2194
2195 let mut editor = Self {
2196 focus_handle,
2197 show_cursor_when_unfocused: false,
2198 last_focused_descendant: None,
2199 buffer: multi_buffer.clone(),
2200 display_map: display_map.clone(),
2201 placeholder_display_map: None,
2202 selections,
2203 scroll_manager: ScrollManager::new(cx),
2204 columnar_selection_state: None,
2205 add_selections_state: None,
2206 select_next_state: None,
2207 select_prev_state: None,
2208 selection_history: SelectionHistory::default(),
2209 defer_selection_effects: false,
2210 deferred_selection_effects_state: None,
2211 autoclose_regions: Vec::new(),
2212 snippet_stack: InvalidationStack::default(),
2213 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2214 ime_transaction: None,
2215 active_diagnostics: ActiveDiagnostic::None,
2216 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2217 inline_diagnostics_update: Task::ready(()),
2218 inline_diagnostics: Vec::new(),
2219 soft_wrap_mode_override,
2220 diagnostics_max_severity,
2221 hard_wrap: None,
2222 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2223 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2224 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2225 project,
2226 blink_manager: blink_manager.clone(),
2227 show_local_selections: true,
2228 show_scrollbars: ScrollbarAxes {
2229 horizontal: full_mode,
2230 vertical: full_mode,
2231 },
2232 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2233 offset_content: !matches!(mode, EditorMode::SingleLine),
2234 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2235 show_gutter: full_mode,
2236 show_line_numbers: (!full_mode).then_some(false),
2237 use_relative_line_numbers: None,
2238 disable_expand_excerpt_buttons: !full_mode,
2239 show_git_diff_gutter: None,
2240 show_code_actions: None,
2241 show_runnables: None,
2242 show_breakpoints: None,
2243 show_wrap_guides: None,
2244 show_indent_guides,
2245 buffers_with_disabled_indent_guides: HashSet::default(),
2246 highlight_order: 0,
2247 highlighted_rows: HashMap::default(),
2248 background_highlights: HashMap::default(),
2249 gutter_highlights: HashMap::default(),
2250 scrollbar_marker_state: ScrollbarMarkerState::default(),
2251 active_indent_guides_state: ActiveIndentGuidesState::default(),
2252 nav_history: None,
2253 context_menu: RefCell::new(None),
2254 context_menu_options: None,
2255 mouse_context_menu: None,
2256 completion_tasks: Vec::new(),
2257 inline_blame_popover: None,
2258 inline_blame_popover_show_task: None,
2259 signature_help_state: SignatureHelpState::default(),
2260 auto_signature_help: None,
2261 find_all_references_task_sources: Vec::new(),
2262 next_completion_id: 0,
2263 next_inlay_id: 0,
2264 code_action_providers,
2265 available_code_actions: None,
2266 code_actions_task: None,
2267 quick_selection_highlight_task: None,
2268 debounced_selection_highlight_task: None,
2269 document_highlights_task: None,
2270 linked_editing_range_task: None,
2271 pending_rename: None,
2272 searchable: !is_minimap,
2273 cursor_shape: EditorSettings::get_global(cx)
2274 .cursor_shape
2275 .unwrap_or_default(),
2276 cursor_offset_on_selection: false,
2277 current_line_highlight: None,
2278 autoindent_mode: Some(AutoindentMode::EachLine),
2279 collapse_matches: false,
2280 workspace: None,
2281 input_enabled: !is_minimap,
2282 use_modal_editing: full_mode,
2283 read_only: is_minimap,
2284 use_autoclose: true,
2285 use_auto_surround: true,
2286 auto_replace_emoji_shortcode: false,
2287 jsx_tag_auto_close_enabled_in_any_buffer: false,
2288 leader_id: None,
2289 remote_id: None,
2290 hover_state: HoverState::default(),
2291 pending_mouse_down: None,
2292 prev_pressure_stage: None,
2293 hovered_link_state: None,
2294 edit_prediction_provider: None,
2295 active_edit_prediction: None,
2296 stale_edit_prediction_in_menu: None,
2297 edit_prediction_preview: EditPredictionPreview::Inactive {
2298 released_too_fast: false,
2299 },
2300 inline_diagnostics_enabled: full_mode,
2301 diagnostics_enabled: full_mode,
2302 word_completions_enabled: full_mode,
2303 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2304 gutter_hovered: false,
2305 pixel_position_of_newest_cursor: None,
2306 last_bounds: None,
2307 last_position_map: None,
2308 expect_bounds_change: None,
2309 gutter_dimensions: GutterDimensions::default(),
2310 style: None,
2311 show_cursor_names: false,
2312 hovered_cursors: HashMap::default(),
2313 next_editor_action_id: EditorActionId::default(),
2314 editor_actions: Rc::default(),
2315 edit_predictions_hidden_for_vim_mode: false,
2316 show_edit_predictions_override: None,
2317 show_completions_on_input_override: None,
2318 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2319 edit_prediction_settings: EditPredictionSettings::Disabled,
2320 edit_prediction_indent_conflict: false,
2321 edit_prediction_requires_modifier_in_indent_conflict: true,
2322 custom_context_menu: None,
2323 show_git_blame_gutter: false,
2324 show_git_blame_inline: false,
2325 show_selection_menu: None,
2326 show_git_blame_inline_delay_task: None,
2327 git_blame_inline_enabled: full_mode
2328 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2329 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2330 buffer_serialization: is_minimap.not().then(|| {
2331 BufferSerialization::new(
2332 ProjectSettings::get_global(cx)
2333 .session
2334 .restore_unsaved_buffers,
2335 )
2336 }),
2337 blame: None,
2338 blame_subscription: None,
2339 tasks: BTreeMap::default(),
2340
2341 breakpoint_store,
2342 gutter_breakpoint_indicator: (None, None),
2343 hovered_diff_hunk_row: None,
2344 _subscriptions: (!is_minimap)
2345 .then(|| {
2346 vec![
2347 cx.observe(&multi_buffer, Self::on_buffer_changed),
2348 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2349 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2350 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2351 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2352 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2353 cx.observe_window_activation(window, |editor, window, cx| {
2354 let active = window.is_window_active();
2355 editor.blink_manager.update(cx, |blink_manager, cx| {
2356 if active {
2357 blink_manager.enable(cx);
2358 } else {
2359 blink_manager.disable(cx);
2360 }
2361 });
2362 if active {
2363 editor.show_mouse_cursor(cx);
2364 }
2365 }),
2366 ]
2367 })
2368 .unwrap_or_default(),
2369 tasks_update_task: None,
2370 pull_diagnostics_task: Task::ready(()),
2371 pull_diagnostics_background_task: Task::ready(()),
2372 colors: None,
2373 refresh_colors_task: Task::ready(()),
2374 inlay_hints: None,
2375 next_color_inlay_id: 0,
2376 post_scroll_update: Task::ready(()),
2377 linked_edit_ranges: Default::default(),
2378 in_project_search: false,
2379 previous_search_ranges: None,
2380 breadcrumb_header: None,
2381 focused_block: None,
2382 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2383 addons: HashMap::default(),
2384 registered_buffers: HashMap::default(),
2385 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2386 selection_mark_mode: false,
2387 toggle_fold_multiple_buffers: Task::ready(()),
2388 serialize_selections: Task::ready(()),
2389 serialize_folds: Task::ready(()),
2390 text_style_refinement: None,
2391 load_diff_task: load_uncommitted_diff,
2392 temporary_diff_override: false,
2393 mouse_cursor_hidden: false,
2394 minimap: None,
2395 hide_mouse_mode: EditorSettings::get_global(cx)
2396 .hide_mouse
2397 .unwrap_or_default(),
2398 change_list: ChangeList::new(),
2399 mode,
2400 selection_drag_state: SelectionDragState::None,
2401 folding_newlines: Task::ready(()),
2402 lookup_key: None,
2403 select_next_is_case_sensitive: None,
2404 applicable_language_settings: HashMap::default(),
2405 accent_data: None,
2406 fetched_tree_sitter_chunks: HashMap::default(),
2407 use_base_text_line_numbers: false,
2408 };
2409
2410 if is_minimap {
2411 return editor;
2412 }
2413
2414 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2415 editor.accent_data = editor.fetch_accent_data(cx);
2416
2417 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2418 editor
2419 ._subscriptions
2420 .push(cx.observe(breakpoints, |_, _, cx| {
2421 cx.notify();
2422 }));
2423 }
2424 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2425 editor._subscriptions.extend(project_subscriptions);
2426
2427 editor._subscriptions.push(cx.subscribe_in(
2428 &cx.entity(),
2429 window,
2430 |editor, _, e: &EditorEvent, window, cx| match e {
2431 EditorEvent::ScrollPositionChanged { local, .. } => {
2432 if *local {
2433 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2434 editor.inline_blame_popover.take();
2435 let new_anchor = editor.scroll_manager.anchor();
2436 let snapshot = editor.snapshot(window, cx);
2437 editor.update_restoration_data(cx, move |data| {
2438 data.scroll_position = (
2439 new_anchor.top_row(snapshot.buffer_snapshot()),
2440 new_anchor.offset,
2441 );
2442 });
2443
2444 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2445 cx.background_executor()
2446 .timer(Duration::from_millis(50))
2447 .await;
2448 editor
2449 .update_in(cx, |editor, window, cx| {
2450 editor.register_visible_buffers(cx);
2451 editor.refresh_colors_for_visible_range(None, window, cx);
2452 editor.refresh_inlay_hints(
2453 InlayHintRefreshReason::NewLinesShown,
2454 cx,
2455 );
2456 editor.colorize_brackets(false, cx);
2457 })
2458 .ok();
2459 });
2460 }
2461 }
2462 EditorEvent::Edited { .. } => {
2463 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2464 .map(|vim_mode| vim_mode.0)
2465 .unwrap_or(false);
2466 if !vim_mode {
2467 let display_map = editor.display_snapshot(cx);
2468 let selections = editor.selections.all_adjusted_display(&display_map);
2469 let pop_state = editor
2470 .change_list
2471 .last()
2472 .map(|previous| {
2473 previous.len() == selections.len()
2474 && previous.iter().enumerate().all(|(ix, p)| {
2475 p.to_display_point(&display_map).row()
2476 == selections[ix].head().row()
2477 })
2478 })
2479 .unwrap_or(false);
2480 let new_positions = selections
2481 .into_iter()
2482 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2483 .collect();
2484 editor
2485 .change_list
2486 .push_to_change_list(pop_state, new_positions);
2487 }
2488 }
2489 _ => (),
2490 },
2491 ));
2492
2493 if let Some(dap_store) = editor
2494 .project
2495 .as_ref()
2496 .map(|project| project.read(cx).dap_store())
2497 {
2498 let weak_editor = cx.weak_entity();
2499
2500 editor
2501 ._subscriptions
2502 .push(
2503 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2504 let session_entity = cx.entity();
2505 weak_editor
2506 .update(cx, |editor, cx| {
2507 editor._subscriptions.push(
2508 cx.subscribe(&session_entity, Self::on_debug_session_event),
2509 );
2510 })
2511 .ok();
2512 }),
2513 );
2514
2515 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2516 editor
2517 ._subscriptions
2518 .push(cx.subscribe(&session, Self::on_debug_session_event));
2519 }
2520 }
2521
2522 // skip adding the initial selection to selection history
2523 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2524 editor.end_selection(window, cx);
2525 editor.selection_history.mode = SelectionHistoryMode::Normal;
2526
2527 editor.scroll_manager.show_scrollbars(window, cx);
2528 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2529
2530 if full_mode {
2531 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2532 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2533
2534 if editor.git_blame_inline_enabled {
2535 editor.start_git_blame_inline(false, window, cx);
2536 }
2537
2538 editor.go_to_active_debug_line(window, cx);
2539
2540 editor.minimap =
2541 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2542 editor.colors = Some(LspColorData::new(cx));
2543 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2544
2545 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2546 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2547 }
2548 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2549 }
2550
2551 editor
2552 }
2553
2554 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2555 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2556 }
2557
2558 pub fn deploy_mouse_context_menu(
2559 &mut self,
2560 position: gpui::Point<Pixels>,
2561 context_menu: Entity<ContextMenu>,
2562 window: &mut Window,
2563 cx: &mut Context<Self>,
2564 ) {
2565 self.mouse_context_menu = Some(MouseContextMenu::new(
2566 self,
2567 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2568 context_menu,
2569 window,
2570 cx,
2571 ));
2572 }
2573
2574 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2575 self.mouse_context_menu
2576 .as_ref()
2577 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2578 }
2579
2580 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2581 if self
2582 .selections
2583 .pending_anchor()
2584 .is_some_and(|pending_selection| {
2585 let snapshot = self.buffer().read(cx).snapshot(cx);
2586 pending_selection.range().includes(range, &snapshot)
2587 })
2588 {
2589 return true;
2590 }
2591
2592 self.selections
2593 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2594 .into_iter()
2595 .any(|selection| {
2596 // This is needed to cover a corner case, if we just check for an existing
2597 // selection in the fold range, having a cursor at the start of the fold
2598 // marks it as selected. Non-empty selections don't cause this.
2599 let length = selection.end - selection.start;
2600 length > 0
2601 })
2602 }
2603
2604 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2605 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2606 }
2607
2608 fn key_context_internal(
2609 &self,
2610 has_active_edit_prediction: bool,
2611 window: &mut Window,
2612 cx: &mut App,
2613 ) -> KeyContext {
2614 let mut key_context = KeyContext::new_with_defaults();
2615 key_context.add("Editor");
2616 let mode = match self.mode {
2617 EditorMode::SingleLine => "single_line",
2618 EditorMode::AutoHeight { .. } => "auto_height",
2619 EditorMode::Minimap { .. } => "minimap",
2620 EditorMode::Full { .. } => "full",
2621 };
2622
2623 if EditorSettings::jupyter_enabled(cx) {
2624 key_context.add("jupyter");
2625 }
2626
2627 key_context.set("mode", mode);
2628 if self.pending_rename.is_some() {
2629 key_context.add("renaming");
2630 }
2631
2632 if let Some(snippet_stack) = self.snippet_stack.last() {
2633 key_context.add("in_snippet");
2634
2635 if snippet_stack.active_index > 0 {
2636 key_context.add("has_previous_tabstop");
2637 }
2638
2639 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2640 key_context.add("has_next_tabstop");
2641 }
2642 }
2643
2644 match self.context_menu.borrow().as_ref() {
2645 Some(CodeContextMenu::Completions(menu)) => {
2646 if menu.visible() {
2647 key_context.add("menu");
2648 key_context.add("showing_completions");
2649 }
2650 }
2651 Some(CodeContextMenu::CodeActions(menu)) => {
2652 if menu.visible() {
2653 key_context.add("menu");
2654 key_context.add("showing_code_actions")
2655 }
2656 }
2657 None => {}
2658 }
2659
2660 if self.signature_help_state.has_multiple_signatures() {
2661 key_context.add("showing_signature_help");
2662 }
2663
2664 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2665 if !self.focus_handle(cx).contains_focused(window, cx)
2666 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2667 {
2668 for addon in self.addons.values() {
2669 addon.extend_key_context(&mut key_context, cx)
2670 }
2671 }
2672
2673 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2674 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2675 Some(
2676 file.full_path(cx)
2677 .extension()?
2678 .to_string_lossy()
2679 .into_owned(),
2680 )
2681 }) {
2682 key_context.set("extension", extension);
2683 }
2684 } else {
2685 key_context.add("multibuffer");
2686 }
2687
2688 if has_active_edit_prediction {
2689 if self.edit_prediction_in_conflict() {
2690 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2691 } else {
2692 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2693 key_context.add("copilot_suggestion");
2694 }
2695 }
2696
2697 if self.selection_mark_mode {
2698 key_context.add("selection_mode");
2699 }
2700
2701 let disjoint = self.selections.disjoint_anchors();
2702 let snapshot = self.snapshot(window, cx);
2703 let snapshot = snapshot.buffer_snapshot();
2704 if self.mode == EditorMode::SingleLine
2705 && let [selection] = disjoint
2706 && selection.start == selection.end
2707 && selection.end.to_offset(snapshot) == snapshot.len()
2708 {
2709 key_context.add("end_of_input");
2710 }
2711
2712 if self.has_any_expanded_diff_hunks(cx) {
2713 key_context.add("diffs_expanded");
2714 }
2715
2716 key_context
2717 }
2718
2719 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2720 self.last_bounds.as_ref()
2721 }
2722
2723 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2724 if self.mouse_cursor_hidden {
2725 self.mouse_cursor_hidden = false;
2726 cx.notify();
2727 }
2728 }
2729
2730 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2731 let hide_mouse_cursor = match origin {
2732 HideMouseCursorOrigin::TypingAction => {
2733 matches!(
2734 self.hide_mouse_mode,
2735 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2736 )
2737 }
2738 HideMouseCursorOrigin::MovementAction => {
2739 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2740 }
2741 };
2742 if self.mouse_cursor_hidden != hide_mouse_cursor {
2743 self.mouse_cursor_hidden = hide_mouse_cursor;
2744 cx.notify();
2745 }
2746 }
2747
2748 pub fn edit_prediction_in_conflict(&self) -> bool {
2749 if !self.show_edit_predictions_in_menu() {
2750 return false;
2751 }
2752
2753 let showing_completions = self
2754 .context_menu
2755 .borrow()
2756 .as_ref()
2757 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2758
2759 showing_completions
2760 || self.edit_prediction_requires_modifier()
2761 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2762 // bindings to insert tab characters.
2763 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2764 }
2765
2766 pub fn accept_edit_prediction_keybind(
2767 &self,
2768 granularity: EditPredictionGranularity,
2769 window: &mut Window,
2770 cx: &mut App,
2771 ) -> AcceptEditPredictionBinding {
2772 let key_context = self.key_context_internal(true, window, cx);
2773 let in_conflict = self.edit_prediction_in_conflict();
2774
2775 let bindings =
2776 match granularity {
2777 EditPredictionGranularity::Word => window
2778 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2779 EditPredictionGranularity::Line => window
2780 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2781 EditPredictionGranularity::Full => {
2782 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2783 }
2784 };
2785
2786 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2787 !in_conflict
2788 || binding
2789 .keystrokes()
2790 .first()
2791 .is_some_and(|keystroke| keystroke.modifiers().modified())
2792 }))
2793 }
2794
2795 pub fn new_file(
2796 workspace: &mut Workspace,
2797 _: &workspace::NewFile,
2798 window: &mut Window,
2799 cx: &mut Context<Workspace>,
2800 ) {
2801 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2802 "Failed to create buffer",
2803 window,
2804 cx,
2805 |e, _, _| match e.error_code() {
2806 ErrorCode::RemoteUpgradeRequired => Some(format!(
2807 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2808 e.error_tag("required").unwrap_or("the latest version")
2809 )),
2810 _ => None,
2811 },
2812 );
2813 }
2814
2815 pub fn new_in_workspace(
2816 workspace: &mut Workspace,
2817 window: &mut Window,
2818 cx: &mut Context<Workspace>,
2819 ) -> Task<Result<Entity<Editor>>> {
2820 let project = workspace.project().clone();
2821 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2822
2823 cx.spawn_in(window, async move |workspace, cx| {
2824 let buffer = create.await?;
2825 workspace.update_in(cx, |workspace, window, cx| {
2826 let editor =
2827 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2828 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2829 editor
2830 })
2831 })
2832 }
2833
2834 fn new_file_vertical(
2835 workspace: &mut Workspace,
2836 _: &workspace::NewFileSplitVertical,
2837 window: &mut Window,
2838 cx: &mut Context<Workspace>,
2839 ) {
2840 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2841 }
2842
2843 fn new_file_horizontal(
2844 workspace: &mut Workspace,
2845 _: &workspace::NewFileSplitHorizontal,
2846 window: &mut Window,
2847 cx: &mut Context<Workspace>,
2848 ) {
2849 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2850 }
2851
2852 fn new_file_split(
2853 workspace: &mut Workspace,
2854 action: &workspace::NewFileSplit,
2855 window: &mut Window,
2856 cx: &mut Context<Workspace>,
2857 ) {
2858 Self::new_file_in_direction(workspace, action.0, window, cx)
2859 }
2860
2861 fn new_file_in_direction(
2862 workspace: &mut Workspace,
2863 direction: SplitDirection,
2864 window: &mut Window,
2865 cx: &mut Context<Workspace>,
2866 ) {
2867 let project = workspace.project().clone();
2868 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2869
2870 cx.spawn_in(window, async move |workspace, cx| {
2871 let buffer = create.await?;
2872 workspace.update_in(cx, move |workspace, window, cx| {
2873 workspace.split_item(
2874 direction,
2875 Box::new(
2876 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2877 ),
2878 window,
2879 cx,
2880 )
2881 })?;
2882 anyhow::Ok(())
2883 })
2884 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2885 match e.error_code() {
2886 ErrorCode::RemoteUpgradeRequired => Some(format!(
2887 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2888 e.error_tag("required").unwrap_or("the latest version")
2889 )),
2890 _ => None,
2891 }
2892 });
2893 }
2894
2895 pub fn leader_id(&self) -> Option<CollaboratorId> {
2896 self.leader_id
2897 }
2898
2899 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2900 &self.buffer
2901 }
2902
2903 pub fn project(&self) -> Option<&Entity<Project>> {
2904 self.project.as_ref()
2905 }
2906
2907 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2908 self.workspace.as_ref()?.0.upgrade()
2909 }
2910
2911 /// Returns the workspace serialization ID if this editor should be serialized.
2912 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2913 self.workspace
2914 .as_ref()
2915 .filter(|_| self.should_serialize_buffer())
2916 .and_then(|workspace| workspace.1)
2917 }
2918
2919 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2920 self.buffer().read(cx).title(cx)
2921 }
2922
2923 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2924 let git_blame_gutter_max_author_length = self
2925 .render_git_blame_gutter(cx)
2926 .then(|| {
2927 if let Some(blame) = self.blame.as_ref() {
2928 let max_author_length =
2929 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2930 Some(max_author_length)
2931 } else {
2932 None
2933 }
2934 })
2935 .flatten();
2936
2937 EditorSnapshot {
2938 mode: self.mode.clone(),
2939 show_gutter: self.show_gutter,
2940 offset_content: self.offset_content,
2941 show_line_numbers: self.show_line_numbers,
2942 show_git_diff_gutter: self.show_git_diff_gutter,
2943 show_code_actions: self.show_code_actions,
2944 show_runnables: self.show_runnables,
2945 show_breakpoints: self.show_breakpoints,
2946 git_blame_gutter_max_author_length,
2947 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2948 placeholder_display_snapshot: self
2949 .placeholder_display_map
2950 .as_ref()
2951 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2952 scroll_anchor: self.scroll_manager.anchor(),
2953 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2954 is_focused: self.focus_handle.is_focused(window),
2955 current_line_highlight: self
2956 .current_line_highlight
2957 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2958 gutter_hovered: self.gutter_hovered,
2959 }
2960 }
2961
2962 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2963 self.buffer.read(cx).language_at(point, cx)
2964 }
2965
2966 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2967 self.buffer.read(cx).read(cx).file_at(point).cloned()
2968 }
2969
2970 pub fn active_excerpt(
2971 &self,
2972 cx: &App,
2973 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2974 self.buffer
2975 .read(cx)
2976 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2977 }
2978
2979 pub fn mode(&self) -> &EditorMode {
2980 &self.mode
2981 }
2982
2983 pub fn set_mode(&mut self, mode: EditorMode) {
2984 self.mode = mode;
2985 }
2986
2987 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2988 self.collaboration_hub.as_deref()
2989 }
2990
2991 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2992 self.collaboration_hub = Some(hub);
2993 }
2994
2995 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2996 self.in_project_search = in_project_search;
2997 }
2998
2999 pub fn set_custom_context_menu(
3000 &mut self,
3001 f: impl 'static
3002 + Fn(
3003 &mut Self,
3004 DisplayPoint,
3005 &mut Window,
3006 &mut Context<Self>,
3007 ) -> Option<Entity<ui::ContextMenu>>,
3008 ) {
3009 self.custom_context_menu = Some(Box::new(f))
3010 }
3011
3012 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3013 self.completion_provider = provider;
3014 }
3015
3016 #[cfg(any(test, feature = "test-support"))]
3017 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3018 self.completion_provider.clone()
3019 }
3020
3021 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3022 self.semantics_provider.clone()
3023 }
3024
3025 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3026 self.semantics_provider = provider;
3027 }
3028
3029 pub fn set_edit_prediction_provider<T>(
3030 &mut self,
3031 provider: Option<Entity<T>>,
3032 window: &mut Window,
3033 cx: &mut Context<Self>,
3034 ) where
3035 T: EditPredictionDelegate,
3036 {
3037 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3038 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3039 if this.focus_handle.is_focused(window) {
3040 this.update_visible_edit_prediction(window, cx);
3041 }
3042 }),
3043 provider: Arc::new(provider),
3044 });
3045 self.update_edit_prediction_settings(cx);
3046 self.refresh_edit_prediction(false, false, window, cx);
3047 }
3048
3049 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3050 self.placeholder_display_map
3051 .as_ref()
3052 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3053 }
3054
3055 pub fn set_placeholder_text(
3056 &mut self,
3057 placeholder_text: &str,
3058 window: &mut Window,
3059 cx: &mut Context<Self>,
3060 ) {
3061 let multibuffer = cx
3062 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3063
3064 let style = window.text_style();
3065
3066 self.placeholder_display_map = Some(cx.new(|cx| {
3067 DisplayMap::new(
3068 multibuffer,
3069 style.font(),
3070 style.font_size.to_pixels(window.rem_size()),
3071 None,
3072 FILE_HEADER_HEIGHT,
3073 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3074 Default::default(),
3075 DiagnosticSeverity::Off,
3076 cx,
3077 )
3078 }));
3079 cx.notify();
3080 }
3081
3082 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3083 self.cursor_shape = cursor_shape;
3084
3085 // Disrupt blink for immediate user feedback that the cursor shape has changed
3086 self.blink_manager.update(cx, BlinkManager::show_cursor);
3087
3088 cx.notify();
3089 }
3090
3091 pub fn cursor_shape(&self) -> CursorShape {
3092 self.cursor_shape
3093 }
3094
3095 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3096 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3097 }
3098
3099 pub fn set_current_line_highlight(
3100 &mut self,
3101 current_line_highlight: Option<CurrentLineHighlight>,
3102 ) {
3103 self.current_line_highlight = current_line_highlight;
3104 }
3105
3106 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3107 self.collapse_matches = collapse_matches;
3108 }
3109
3110 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3111 if self.collapse_matches {
3112 return range.start..range.start;
3113 }
3114 range.clone()
3115 }
3116
3117 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3118 self.display_map.read(cx).clip_at_line_ends
3119 }
3120
3121 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3122 if self.display_map.read(cx).clip_at_line_ends != clip {
3123 self.display_map
3124 .update(cx, |map, _| map.clip_at_line_ends = clip);
3125 }
3126 }
3127
3128 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3129 self.input_enabled = input_enabled;
3130 }
3131
3132 pub fn set_edit_predictions_hidden_for_vim_mode(
3133 &mut self,
3134 hidden: bool,
3135 window: &mut Window,
3136 cx: &mut Context<Self>,
3137 ) {
3138 if hidden != self.edit_predictions_hidden_for_vim_mode {
3139 self.edit_predictions_hidden_for_vim_mode = hidden;
3140 if hidden {
3141 self.update_visible_edit_prediction(window, cx);
3142 } else {
3143 self.refresh_edit_prediction(true, false, window, cx);
3144 }
3145 }
3146 }
3147
3148 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3149 self.menu_edit_predictions_policy = value;
3150 }
3151
3152 pub fn set_autoindent(&mut self, autoindent: bool) {
3153 if autoindent {
3154 self.autoindent_mode = Some(AutoindentMode::EachLine);
3155 } else {
3156 self.autoindent_mode = None;
3157 }
3158 }
3159
3160 pub fn read_only(&self, cx: &App) -> bool {
3161 self.read_only || self.buffer.read(cx).read_only()
3162 }
3163
3164 pub fn set_read_only(&mut self, read_only: bool) {
3165 self.read_only = read_only;
3166 }
3167
3168 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3169 self.use_autoclose = autoclose;
3170 }
3171
3172 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3173 self.use_auto_surround = auto_surround;
3174 }
3175
3176 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3177 self.auto_replace_emoji_shortcode = auto_replace;
3178 }
3179
3180 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3181 self.buffer_serialization = should_serialize.then(|| {
3182 BufferSerialization::new(
3183 ProjectSettings::get_global(cx)
3184 .session
3185 .restore_unsaved_buffers,
3186 )
3187 })
3188 }
3189
3190 fn should_serialize_buffer(&self) -> bool {
3191 self.buffer_serialization.is_some()
3192 }
3193
3194 pub fn toggle_edit_predictions(
3195 &mut self,
3196 _: &ToggleEditPrediction,
3197 window: &mut Window,
3198 cx: &mut Context<Self>,
3199 ) {
3200 if self.show_edit_predictions_override.is_some() {
3201 self.set_show_edit_predictions(None, window, cx);
3202 } else {
3203 let show_edit_predictions = !self.edit_predictions_enabled();
3204 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3205 }
3206 }
3207
3208 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3209 self.show_completions_on_input_override = show_completions_on_input;
3210 }
3211
3212 pub fn set_show_edit_predictions(
3213 &mut self,
3214 show_edit_predictions: Option<bool>,
3215 window: &mut Window,
3216 cx: &mut Context<Self>,
3217 ) {
3218 self.show_edit_predictions_override = show_edit_predictions;
3219 self.update_edit_prediction_settings(cx);
3220
3221 if let Some(false) = show_edit_predictions {
3222 self.discard_edit_prediction(false, cx);
3223 } else {
3224 self.refresh_edit_prediction(false, true, window, cx);
3225 }
3226 }
3227
3228 fn edit_predictions_disabled_in_scope(
3229 &self,
3230 buffer: &Entity<Buffer>,
3231 buffer_position: language::Anchor,
3232 cx: &App,
3233 ) -> bool {
3234 let snapshot = buffer.read(cx).snapshot();
3235 let settings = snapshot.settings_at(buffer_position, cx);
3236
3237 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3238 return false;
3239 };
3240
3241 scope.override_name().is_some_and(|scope_name| {
3242 settings
3243 .edit_predictions_disabled_in
3244 .iter()
3245 .any(|s| s == scope_name)
3246 })
3247 }
3248
3249 pub fn set_use_modal_editing(&mut self, to: bool) {
3250 self.use_modal_editing = to;
3251 }
3252
3253 pub fn use_modal_editing(&self) -> bool {
3254 self.use_modal_editing
3255 }
3256
3257 fn selections_did_change(
3258 &mut self,
3259 local: bool,
3260 old_cursor_position: &Anchor,
3261 effects: SelectionEffects,
3262 window: &mut Window,
3263 cx: &mut Context<Self>,
3264 ) {
3265 window.invalidate_character_coordinates();
3266
3267 // Copy selections to primary selection buffer
3268 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3269 if local {
3270 let selections = self
3271 .selections
3272 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3273 let buffer_handle = self.buffer.read(cx).read(cx);
3274
3275 let mut text = String::new();
3276 for (index, selection) in selections.iter().enumerate() {
3277 let text_for_selection = buffer_handle
3278 .text_for_range(selection.start..selection.end)
3279 .collect::<String>();
3280
3281 text.push_str(&text_for_selection);
3282 if index != selections.len() - 1 {
3283 text.push('\n');
3284 }
3285 }
3286
3287 if !text.is_empty() {
3288 cx.write_to_primary(ClipboardItem::new_string(text));
3289 }
3290 }
3291
3292 let selection_anchors = self.selections.disjoint_anchors_arc();
3293
3294 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3295 self.buffer.update(cx, |buffer, cx| {
3296 buffer.set_active_selections(
3297 &selection_anchors,
3298 self.selections.line_mode(),
3299 self.cursor_shape,
3300 cx,
3301 )
3302 });
3303 }
3304 let display_map = self
3305 .display_map
3306 .update(cx, |display_map, cx| display_map.snapshot(cx));
3307 let buffer = display_map.buffer_snapshot();
3308 if self.selections.count() == 1 {
3309 self.add_selections_state = None;
3310 }
3311 self.select_next_state = None;
3312 self.select_prev_state = None;
3313 self.select_syntax_node_history.try_clear();
3314 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3315 self.snippet_stack.invalidate(&selection_anchors, buffer);
3316 self.take_rename(false, window, cx);
3317
3318 let newest_selection = self.selections.newest_anchor();
3319 let new_cursor_position = newest_selection.head();
3320 let selection_start = newest_selection.start;
3321
3322 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3323 self.push_to_nav_history(
3324 *old_cursor_position,
3325 Some(new_cursor_position.to_point(buffer)),
3326 false,
3327 effects.nav_history == Some(true),
3328 cx,
3329 );
3330 }
3331
3332 if local {
3333 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3334 self.register_buffer(buffer_id, cx);
3335 }
3336
3337 let mut context_menu = self.context_menu.borrow_mut();
3338 let completion_menu = match context_menu.as_ref() {
3339 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3340 Some(CodeContextMenu::CodeActions(_)) => {
3341 *context_menu = None;
3342 None
3343 }
3344 None => None,
3345 };
3346 let completion_position = completion_menu.map(|menu| menu.initial_position);
3347 drop(context_menu);
3348
3349 if effects.completions
3350 && let Some(completion_position) = completion_position
3351 {
3352 let start_offset = selection_start.to_offset(buffer);
3353 let position_matches = start_offset == completion_position.to_offset(buffer);
3354 let continue_showing = if let Some((snap, ..)) =
3355 buffer.point_to_buffer_offset(completion_position)
3356 && !snap.capability.editable()
3357 {
3358 false
3359 } else if position_matches {
3360 if self.snippet_stack.is_empty() {
3361 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3362 == Some(CharKind::Word)
3363 } else {
3364 // Snippet choices can be shown even when the cursor is in whitespace.
3365 // Dismissing the menu with actions like backspace is handled by
3366 // invalidation regions.
3367 true
3368 }
3369 } else {
3370 false
3371 };
3372
3373 if continue_showing {
3374 self.open_or_update_completions_menu(None, None, false, window, cx);
3375 } else {
3376 self.hide_context_menu(window, cx);
3377 }
3378 }
3379
3380 hide_hover(self, cx);
3381
3382 if old_cursor_position.to_display_point(&display_map).row()
3383 != new_cursor_position.to_display_point(&display_map).row()
3384 {
3385 self.available_code_actions.take();
3386 }
3387 self.refresh_code_actions(window, cx);
3388 self.refresh_document_highlights(cx);
3389 refresh_linked_ranges(self, window, cx);
3390
3391 self.refresh_selected_text_highlights(false, window, cx);
3392 self.refresh_matching_bracket_highlights(window, cx);
3393 self.update_visible_edit_prediction(window, cx);
3394 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3395 self.inline_blame_popover.take();
3396 if self.git_blame_inline_enabled {
3397 self.start_inline_blame_timer(window, cx);
3398 }
3399 }
3400
3401 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3402 cx.emit(EditorEvent::SelectionsChanged { local });
3403
3404 let selections = &self.selections.disjoint_anchors_arc();
3405 if selections.len() == 1 {
3406 cx.emit(SearchEvent::ActiveMatchChanged)
3407 }
3408 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3409 let inmemory_selections = selections
3410 .iter()
3411 .map(|s| {
3412 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3413 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3414 })
3415 .collect();
3416 self.update_restoration_data(cx, |data| {
3417 data.selections = inmemory_selections;
3418 });
3419
3420 if WorkspaceSettings::get(None, cx).restore_on_startup
3421 != RestoreOnStartupBehavior::EmptyTab
3422 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3423 {
3424 let snapshot = self.buffer().read(cx).snapshot(cx);
3425 let selections = selections.clone();
3426 let background_executor = cx.background_executor().clone();
3427 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3428 self.serialize_selections = cx.background_spawn(async move {
3429 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3430 let db_selections = selections
3431 .iter()
3432 .map(|selection| {
3433 (
3434 selection.start.to_offset(&snapshot).0,
3435 selection.end.to_offset(&snapshot).0,
3436 )
3437 })
3438 .collect();
3439
3440 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3441 .await
3442 .with_context(|| {
3443 format!(
3444 "persisting editor selections for editor {editor_id}, \
3445 workspace {workspace_id:?}"
3446 )
3447 })
3448 .log_err();
3449 });
3450 }
3451 }
3452
3453 cx.notify();
3454 }
3455
3456 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3457 use text::ToOffset as _;
3458 use text::ToPoint as _;
3459
3460 if self.mode.is_minimap()
3461 || WorkspaceSettings::get(None, cx).restore_on_startup
3462 == RestoreOnStartupBehavior::EmptyTab
3463 {
3464 return;
3465 }
3466
3467 if !self.buffer().read(cx).is_singleton() {
3468 return;
3469 }
3470
3471 let display_snapshot = self
3472 .display_map
3473 .update(cx, |display_map, cx| display_map.snapshot(cx));
3474 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3475 return;
3476 };
3477 let inmemory_folds = display_snapshot
3478 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3479 .map(|fold| {
3480 fold.range.start.text_anchor.to_point(&snapshot)
3481 ..fold.range.end.text_anchor.to_point(&snapshot)
3482 })
3483 .collect();
3484 self.update_restoration_data(cx, |data| {
3485 data.folds = inmemory_folds;
3486 });
3487
3488 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3489 return;
3490 };
3491 let background_executor = cx.background_executor().clone();
3492 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3493 let db_folds = display_snapshot
3494 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3495 .map(|fold| {
3496 (
3497 fold.range.start.text_anchor.to_offset(&snapshot),
3498 fold.range.end.text_anchor.to_offset(&snapshot),
3499 )
3500 })
3501 .collect();
3502 self.serialize_folds = cx.background_spawn(async move {
3503 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3504 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3505 .await
3506 .with_context(|| {
3507 format!(
3508 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3509 )
3510 })
3511 .log_err();
3512 });
3513 }
3514
3515 pub fn sync_selections(
3516 &mut self,
3517 other: Entity<Editor>,
3518 cx: &mut Context<Self>,
3519 ) -> gpui::Subscription {
3520 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3521 if !other_selections.is_empty() {
3522 self.selections
3523 .change_with(&self.display_snapshot(cx), |selections| {
3524 selections.select_anchors(other_selections);
3525 });
3526 }
3527
3528 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3529 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3530 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3531 if other_selections.is_empty() {
3532 return;
3533 }
3534 let snapshot = this.display_snapshot(cx);
3535 this.selections.change_with(&snapshot, |selections| {
3536 selections.select_anchors(other_selections);
3537 });
3538 }
3539 });
3540
3541 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3542 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3543 let these_selections = this.selections.disjoint_anchors().to_vec();
3544 if these_selections.is_empty() {
3545 return;
3546 }
3547 other.update(cx, |other_editor, cx| {
3548 let snapshot = other_editor.display_snapshot(cx);
3549 other_editor
3550 .selections
3551 .change_with(&snapshot, |selections| {
3552 selections.select_anchors(these_selections);
3553 })
3554 });
3555 }
3556 });
3557
3558 Subscription::join(other_subscription, this_subscription)
3559 }
3560
3561 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3562 if self.buffer().read(cx).is_singleton() {
3563 return;
3564 }
3565 let snapshot = self.buffer.read(cx).snapshot(cx);
3566 let buffer_ids: HashSet<BufferId> = self
3567 .selections
3568 .disjoint_anchor_ranges()
3569 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3570 .collect();
3571 for buffer_id in buffer_ids {
3572 self.unfold_buffer(buffer_id, cx);
3573 }
3574 }
3575
3576 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3577 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3578 /// effects of selection change occur at the end of the transaction.
3579 pub fn change_selections<R>(
3580 &mut self,
3581 effects: SelectionEffects,
3582 window: &mut Window,
3583 cx: &mut Context<Self>,
3584 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3585 ) -> R {
3586 let snapshot = self.display_snapshot(cx);
3587 if let Some(state) = &mut self.deferred_selection_effects_state {
3588 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3589 state.effects.completions = effects.completions;
3590 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3591 let (changed, result) = self.selections.change_with(&snapshot, change);
3592 state.changed |= changed;
3593 return result;
3594 }
3595 let mut state = DeferredSelectionEffectsState {
3596 changed: false,
3597 effects,
3598 old_cursor_position: self.selections.newest_anchor().head(),
3599 history_entry: SelectionHistoryEntry {
3600 selections: self.selections.disjoint_anchors_arc(),
3601 select_next_state: self.select_next_state.clone(),
3602 select_prev_state: self.select_prev_state.clone(),
3603 add_selections_state: self.add_selections_state.clone(),
3604 },
3605 };
3606 let (changed, result) = self.selections.change_with(&snapshot, change);
3607 state.changed = state.changed || changed;
3608 if self.defer_selection_effects {
3609 self.deferred_selection_effects_state = Some(state);
3610 } else {
3611 self.apply_selection_effects(state, window, cx);
3612 }
3613 result
3614 }
3615
3616 /// Defers the effects of selection change, so that the effects of multiple calls to
3617 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3618 /// to selection history and the state of popovers based on selection position aren't
3619 /// erroneously updated.
3620 pub fn with_selection_effects_deferred<R>(
3621 &mut self,
3622 window: &mut Window,
3623 cx: &mut Context<Self>,
3624 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3625 ) -> R {
3626 let already_deferred = self.defer_selection_effects;
3627 self.defer_selection_effects = true;
3628 let result = update(self, window, cx);
3629 if !already_deferred {
3630 self.defer_selection_effects = false;
3631 if let Some(state) = self.deferred_selection_effects_state.take() {
3632 self.apply_selection_effects(state, window, cx);
3633 }
3634 }
3635 result
3636 }
3637
3638 fn apply_selection_effects(
3639 &mut self,
3640 state: DeferredSelectionEffectsState,
3641 window: &mut Window,
3642 cx: &mut Context<Self>,
3643 ) {
3644 if state.changed {
3645 self.selection_history.push(state.history_entry);
3646
3647 if let Some(autoscroll) = state.effects.scroll {
3648 self.request_autoscroll(autoscroll, cx);
3649 }
3650
3651 let old_cursor_position = &state.old_cursor_position;
3652
3653 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3654
3655 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3656 self.show_signature_help(&ShowSignatureHelp, window, cx);
3657 }
3658 }
3659 }
3660
3661 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3662 where
3663 I: IntoIterator<Item = (Range<S>, T)>,
3664 S: ToOffset,
3665 T: Into<Arc<str>>,
3666 {
3667 if self.read_only(cx) {
3668 return;
3669 }
3670
3671 self.buffer
3672 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3673 }
3674
3675 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3676 where
3677 I: IntoIterator<Item = (Range<S>, T)>,
3678 S: ToOffset,
3679 T: Into<Arc<str>>,
3680 {
3681 if self.read_only(cx) {
3682 return;
3683 }
3684
3685 self.buffer.update(cx, |buffer, cx| {
3686 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3687 });
3688 }
3689
3690 pub fn edit_with_block_indent<I, S, T>(
3691 &mut self,
3692 edits: I,
3693 original_indent_columns: Vec<Option<u32>>,
3694 cx: &mut Context<Self>,
3695 ) where
3696 I: IntoIterator<Item = (Range<S>, T)>,
3697 S: ToOffset,
3698 T: Into<Arc<str>>,
3699 {
3700 if self.read_only(cx) {
3701 return;
3702 }
3703
3704 self.buffer.update(cx, |buffer, cx| {
3705 buffer.edit(
3706 edits,
3707 Some(AutoindentMode::Block {
3708 original_indent_columns,
3709 }),
3710 cx,
3711 )
3712 });
3713 }
3714
3715 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3716 self.hide_context_menu(window, cx);
3717
3718 match phase {
3719 SelectPhase::Begin {
3720 position,
3721 add,
3722 click_count,
3723 } => self.begin_selection(position, add, click_count, window, cx),
3724 SelectPhase::BeginColumnar {
3725 position,
3726 goal_column,
3727 reset,
3728 mode,
3729 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3730 SelectPhase::Extend {
3731 position,
3732 click_count,
3733 } => self.extend_selection(position, click_count, window, cx),
3734 SelectPhase::Update {
3735 position,
3736 goal_column,
3737 scroll_delta,
3738 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3739 SelectPhase::End => self.end_selection(window, cx),
3740 }
3741 }
3742
3743 fn extend_selection(
3744 &mut self,
3745 position: DisplayPoint,
3746 click_count: usize,
3747 window: &mut Window,
3748 cx: &mut Context<Self>,
3749 ) {
3750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3751 let tail = self
3752 .selections
3753 .newest::<MultiBufferOffset>(&display_map)
3754 .tail();
3755 let click_count = click_count.max(match self.selections.select_mode() {
3756 SelectMode::Character => 1,
3757 SelectMode::Word(_) => 2,
3758 SelectMode::Line(_) => 3,
3759 SelectMode::All => 4,
3760 });
3761 self.begin_selection(position, false, click_count, window, cx);
3762
3763 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3764
3765 let current_selection = match self.selections.select_mode() {
3766 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3767 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3768 };
3769
3770 let mut pending_selection = self
3771 .selections
3772 .pending_anchor()
3773 .cloned()
3774 .expect("extend_selection not called with pending selection");
3775
3776 if pending_selection
3777 .start
3778 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3779 == Ordering::Greater
3780 {
3781 pending_selection.start = current_selection.start;
3782 }
3783 if pending_selection
3784 .end
3785 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3786 == Ordering::Less
3787 {
3788 pending_selection.end = current_selection.end;
3789 pending_selection.reversed = true;
3790 }
3791
3792 let mut pending_mode = self.selections.pending_mode().unwrap();
3793 match &mut pending_mode {
3794 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3795 _ => {}
3796 }
3797
3798 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3799 SelectionEffects::scroll(Autoscroll::fit())
3800 } else {
3801 SelectionEffects::no_scroll()
3802 };
3803
3804 self.change_selections(effects, window, cx, |s| {
3805 s.set_pending(pending_selection.clone(), pending_mode);
3806 s.set_is_extending(true);
3807 });
3808 }
3809
3810 fn begin_selection(
3811 &mut self,
3812 position: DisplayPoint,
3813 add: bool,
3814 click_count: usize,
3815 window: &mut Window,
3816 cx: &mut Context<Self>,
3817 ) {
3818 if !self.focus_handle.is_focused(window) {
3819 self.last_focused_descendant = None;
3820 window.focus(&self.focus_handle, cx);
3821 }
3822
3823 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3824 let buffer = display_map.buffer_snapshot();
3825 let position = display_map.clip_point(position, Bias::Left);
3826
3827 let start;
3828 let end;
3829 let mode;
3830 let mut auto_scroll;
3831 match click_count {
3832 1 => {
3833 start = buffer.anchor_before(position.to_point(&display_map));
3834 end = start;
3835 mode = SelectMode::Character;
3836 auto_scroll = true;
3837 }
3838 2 => {
3839 let position = display_map
3840 .clip_point(position, Bias::Left)
3841 .to_offset(&display_map, Bias::Left);
3842 let (range, _) = buffer.surrounding_word(position, None);
3843 start = buffer.anchor_before(range.start);
3844 end = buffer.anchor_before(range.end);
3845 mode = SelectMode::Word(start..end);
3846 auto_scroll = true;
3847 }
3848 3 => {
3849 let position = display_map
3850 .clip_point(position, Bias::Left)
3851 .to_point(&display_map);
3852 let line_start = display_map.prev_line_boundary(position).0;
3853 let next_line_start = buffer.clip_point(
3854 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3855 Bias::Left,
3856 );
3857 start = buffer.anchor_before(line_start);
3858 end = buffer.anchor_before(next_line_start);
3859 mode = SelectMode::Line(start..end);
3860 auto_scroll = true;
3861 }
3862 _ => {
3863 start = buffer.anchor_before(MultiBufferOffset(0));
3864 end = buffer.anchor_before(buffer.len());
3865 mode = SelectMode::All;
3866 auto_scroll = false;
3867 }
3868 }
3869 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3870
3871 let point_to_delete: Option<usize> = {
3872 let selected_points: Vec<Selection<Point>> =
3873 self.selections.disjoint_in_range(start..end, &display_map);
3874
3875 if !add || click_count > 1 {
3876 None
3877 } else if !selected_points.is_empty() {
3878 Some(selected_points[0].id)
3879 } else {
3880 let clicked_point_already_selected =
3881 self.selections.disjoint_anchors().iter().find(|selection| {
3882 selection.start.to_point(buffer) == start.to_point(buffer)
3883 || selection.end.to_point(buffer) == end.to_point(buffer)
3884 });
3885
3886 clicked_point_already_selected.map(|selection| selection.id)
3887 }
3888 };
3889
3890 let selections_count = self.selections.count();
3891 let effects = if auto_scroll {
3892 SelectionEffects::default()
3893 } else {
3894 SelectionEffects::no_scroll()
3895 };
3896
3897 self.change_selections(effects, window, cx, |s| {
3898 if let Some(point_to_delete) = point_to_delete {
3899 s.delete(point_to_delete);
3900
3901 if selections_count == 1 {
3902 s.set_pending_anchor_range(start..end, mode);
3903 }
3904 } else {
3905 if !add {
3906 s.clear_disjoint();
3907 }
3908
3909 s.set_pending_anchor_range(start..end, mode);
3910 }
3911 });
3912 }
3913
3914 fn begin_columnar_selection(
3915 &mut self,
3916 position: DisplayPoint,
3917 goal_column: u32,
3918 reset: bool,
3919 mode: ColumnarMode,
3920 window: &mut Window,
3921 cx: &mut Context<Self>,
3922 ) {
3923 if !self.focus_handle.is_focused(window) {
3924 self.last_focused_descendant = None;
3925 window.focus(&self.focus_handle, cx);
3926 }
3927
3928 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3929
3930 if reset {
3931 let pointer_position = display_map
3932 .buffer_snapshot()
3933 .anchor_before(position.to_point(&display_map));
3934
3935 self.change_selections(
3936 SelectionEffects::scroll(Autoscroll::newest()),
3937 window,
3938 cx,
3939 |s| {
3940 s.clear_disjoint();
3941 s.set_pending_anchor_range(
3942 pointer_position..pointer_position,
3943 SelectMode::Character,
3944 );
3945 },
3946 );
3947 };
3948
3949 let tail = self.selections.newest::<Point>(&display_map).tail();
3950 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3951 self.columnar_selection_state = match mode {
3952 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3953 selection_tail: selection_anchor,
3954 display_point: if reset {
3955 if position.column() != goal_column {
3956 Some(DisplayPoint::new(position.row(), goal_column))
3957 } else {
3958 None
3959 }
3960 } else {
3961 None
3962 },
3963 }),
3964 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3965 selection_tail: selection_anchor,
3966 }),
3967 };
3968
3969 if !reset {
3970 self.select_columns(position, goal_column, &display_map, window, cx);
3971 }
3972 }
3973
3974 fn update_selection(
3975 &mut self,
3976 position: DisplayPoint,
3977 goal_column: u32,
3978 scroll_delta: gpui::Point<f32>,
3979 window: &mut Window,
3980 cx: &mut Context<Self>,
3981 ) {
3982 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3983
3984 if self.columnar_selection_state.is_some() {
3985 self.select_columns(position, goal_column, &display_map, window, cx);
3986 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3987 let buffer = display_map.buffer_snapshot();
3988 let head;
3989 let tail;
3990 let mode = self.selections.pending_mode().unwrap();
3991 match &mode {
3992 SelectMode::Character => {
3993 head = position.to_point(&display_map);
3994 tail = pending.tail().to_point(buffer);
3995 }
3996 SelectMode::Word(original_range) => {
3997 let offset = display_map
3998 .clip_point(position, Bias::Left)
3999 .to_offset(&display_map, Bias::Left);
4000 let original_range = original_range.to_offset(buffer);
4001
4002 let head_offset = if buffer.is_inside_word(offset, None)
4003 || original_range.contains(&offset)
4004 {
4005 let (word_range, _) = buffer.surrounding_word(offset, None);
4006 if word_range.start < original_range.start {
4007 word_range.start
4008 } else {
4009 word_range.end
4010 }
4011 } else {
4012 offset
4013 };
4014
4015 head = head_offset.to_point(buffer);
4016 if head_offset <= original_range.start {
4017 tail = original_range.end.to_point(buffer);
4018 } else {
4019 tail = original_range.start.to_point(buffer);
4020 }
4021 }
4022 SelectMode::Line(original_range) => {
4023 let original_range = original_range.to_point(display_map.buffer_snapshot());
4024
4025 let position = display_map
4026 .clip_point(position, Bias::Left)
4027 .to_point(&display_map);
4028 let line_start = display_map.prev_line_boundary(position).0;
4029 let next_line_start = buffer.clip_point(
4030 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4031 Bias::Left,
4032 );
4033
4034 if line_start < original_range.start {
4035 head = line_start
4036 } else {
4037 head = next_line_start
4038 }
4039
4040 if head <= original_range.start {
4041 tail = original_range.end;
4042 } else {
4043 tail = original_range.start;
4044 }
4045 }
4046 SelectMode::All => {
4047 return;
4048 }
4049 };
4050
4051 if head < tail {
4052 pending.start = buffer.anchor_before(head);
4053 pending.end = buffer.anchor_before(tail);
4054 pending.reversed = true;
4055 } else {
4056 pending.start = buffer.anchor_before(tail);
4057 pending.end = buffer.anchor_before(head);
4058 pending.reversed = false;
4059 }
4060
4061 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4062 s.set_pending(pending.clone(), mode);
4063 });
4064 } else {
4065 log::error!("update_selection dispatched with no pending selection");
4066 return;
4067 }
4068
4069 self.apply_scroll_delta(scroll_delta, window, cx);
4070 cx.notify();
4071 }
4072
4073 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4074 self.columnar_selection_state.take();
4075 if let Some(pending_mode) = self.selections.pending_mode() {
4076 let selections = self
4077 .selections
4078 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4079 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4080 s.select(selections);
4081 s.clear_pending();
4082 if s.is_extending() {
4083 s.set_is_extending(false);
4084 } else {
4085 s.set_select_mode(pending_mode);
4086 }
4087 });
4088 }
4089 }
4090
4091 fn select_columns(
4092 &mut self,
4093 head: DisplayPoint,
4094 goal_column: u32,
4095 display_map: &DisplaySnapshot,
4096 window: &mut Window,
4097 cx: &mut Context<Self>,
4098 ) {
4099 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4100 return;
4101 };
4102
4103 let tail = match columnar_state {
4104 ColumnarSelectionState::FromMouse {
4105 selection_tail,
4106 display_point,
4107 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4108 ColumnarSelectionState::FromSelection { selection_tail } => {
4109 selection_tail.to_display_point(display_map)
4110 }
4111 };
4112
4113 let start_row = cmp::min(tail.row(), head.row());
4114 let end_row = cmp::max(tail.row(), head.row());
4115 let start_column = cmp::min(tail.column(), goal_column);
4116 let end_column = cmp::max(tail.column(), goal_column);
4117 let reversed = start_column < tail.column();
4118
4119 let selection_ranges = (start_row.0..=end_row.0)
4120 .map(DisplayRow)
4121 .filter_map(|row| {
4122 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4123 || start_column <= display_map.line_len(row))
4124 && !display_map.is_block_line(row)
4125 {
4126 let start = display_map
4127 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4128 .to_point(display_map);
4129 let end = display_map
4130 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4131 .to_point(display_map);
4132 if reversed {
4133 Some(end..start)
4134 } else {
4135 Some(start..end)
4136 }
4137 } else {
4138 None
4139 }
4140 })
4141 .collect::<Vec<_>>();
4142 if selection_ranges.is_empty() {
4143 return;
4144 }
4145
4146 let ranges = match columnar_state {
4147 ColumnarSelectionState::FromMouse { .. } => {
4148 let mut non_empty_ranges = selection_ranges
4149 .iter()
4150 .filter(|selection_range| selection_range.start != selection_range.end)
4151 .peekable();
4152 if non_empty_ranges.peek().is_some() {
4153 non_empty_ranges.cloned().collect()
4154 } else {
4155 selection_ranges
4156 }
4157 }
4158 _ => selection_ranges,
4159 };
4160
4161 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4162 s.select_ranges(ranges);
4163 });
4164 cx.notify();
4165 }
4166
4167 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4168 self.selections
4169 .all_adjusted(snapshot)
4170 .iter()
4171 .any(|selection| !selection.is_empty())
4172 }
4173
4174 pub fn has_pending_nonempty_selection(&self) -> bool {
4175 let pending_nonempty_selection = match self.selections.pending_anchor() {
4176 Some(Selection { start, end, .. }) => start != end,
4177 None => false,
4178 };
4179
4180 pending_nonempty_selection
4181 || (self.columnar_selection_state.is_some()
4182 && self.selections.disjoint_anchors().len() > 1)
4183 }
4184
4185 pub fn has_pending_selection(&self) -> bool {
4186 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4187 }
4188
4189 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4190 self.selection_mark_mode = false;
4191 self.selection_drag_state = SelectionDragState::None;
4192
4193 if self.dismiss_menus_and_popups(true, window, cx) {
4194 cx.notify();
4195 return;
4196 }
4197 if self.clear_expanded_diff_hunks(cx) {
4198 cx.notify();
4199 return;
4200 }
4201 if self.show_git_blame_gutter {
4202 self.show_git_blame_gutter = false;
4203 cx.notify();
4204 return;
4205 }
4206
4207 if self.mode.is_full()
4208 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4209 {
4210 cx.notify();
4211 return;
4212 }
4213
4214 cx.propagate();
4215 }
4216
4217 pub fn dismiss_menus_and_popups(
4218 &mut self,
4219 is_user_requested: bool,
4220 window: &mut Window,
4221 cx: &mut Context<Self>,
4222 ) -> bool {
4223 let mut dismissed = false;
4224
4225 dismissed |= self.take_rename(false, window, cx).is_some();
4226 dismissed |= self.hide_blame_popover(true, cx);
4227 dismissed |= hide_hover(self, cx);
4228 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4229 dismissed |= self.hide_context_menu(window, cx).is_some();
4230 dismissed |= self.mouse_context_menu.take().is_some();
4231 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4232 dismissed |= self.snippet_stack.pop().is_some();
4233
4234 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4235 self.dismiss_diagnostics(cx);
4236 dismissed = true;
4237 }
4238
4239 dismissed
4240 }
4241
4242 fn linked_editing_ranges_for(
4243 &self,
4244 selection: Range<text::Anchor>,
4245 cx: &App,
4246 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4247 if self.linked_edit_ranges.is_empty() {
4248 return None;
4249 }
4250 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4251 selection.end.buffer_id.and_then(|end_buffer_id| {
4252 if selection.start.buffer_id != Some(end_buffer_id) {
4253 return None;
4254 }
4255 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4256 let snapshot = buffer.read(cx).snapshot();
4257 self.linked_edit_ranges
4258 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4259 .map(|ranges| (ranges, snapshot, buffer))
4260 })?;
4261 use text::ToOffset as TO;
4262 // find offset from the start of current range to current cursor position
4263 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4264
4265 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4266 let start_difference = start_offset - start_byte_offset;
4267 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4268 let end_difference = end_offset - start_byte_offset;
4269 // Current range has associated linked ranges.
4270 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4271 for range in linked_ranges.iter() {
4272 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4273 let end_offset = start_offset + end_difference;
4274 let start_offset = start_offset + start_difference;
4275 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4276 continue;
4277 }
4278 if self.selections.disjoint_anchor_ranges().any(|s| {
4279 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4280 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4281 {
4282 return false;
4283 }
4284 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4285 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4286 }) {
4287 continue;
4288 }
4289 let start = buffer_snapshot.anchor_after(start_offset);
4290 let end = buffer_snapshot.anchor_after(end_offset);
4291 linked_edits
4292 .entry(buffer.clone())
4293 .or_default()
4294 .push(start..end);
4295 }
4296 Some(linked_edits)
4297 }
4298
4299 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4300 let text: Arc<str> = text.into();
4301
4302 if self.read_only(cx) {
4303 return;
4304 }
4305
4306 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4307
4308 self.unfold_buffers_with_selections(cx);
4309
4310 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4311 let mut bracket_inserted = false;
4312 let mut edits = Vec::new();
4313 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4314 let mut new_selections = Vec::with_capacity(selections.len());
4315 let mut new_autoclose_regions = Vec::new();
4316 let snapshot = self.buffer.read(cx).read(cx);
4317 let mut clear_linked_edit_ranges = false;
4318 let mut all_selections_read_only = true;
4319
4320 for (selection, autoclose_region) in
4321 self.selections_with_autoclose_regions(selections, &snapshot)
4322 {
4323 if snapshot
4324 .point_to_buffer_point(selection.head())
4325 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4326 {
4327 continue;
4328 }
4329 if snapshot
4330 .point_to_buffer_point(selection.tail())
4331 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4332 {
4333 // note, ideally we'd clip the tail to the closest writeable region towards the head
4334 continue;
4335 }
4336 all_selections_read_only = false;
4337
4338 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4339 // Determine if the inserted text matches the opening or closing
4340 // bracket of any of this language's bracket pairs.
4341 let mut bracket_pair = None;
4342 let mut is_bracket_pair_start = false;
4343 let mut is_bracket_pair_end = false;
4344 if !text.is_empty() {
4345 let mut bracket_pair_matching_end = None;
4346 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4347 // and they are removing the character that triggered IME popup.
4348 for (pair, enabled) in scope.brackets() {
4349 if !pair.close && !pair.surround {
4350 continue;
4351 }
4352
4353 if enabled && pair.start.ends_with(text.as_ref()) {
4354 let prefix_len = pair.start.len() - text.len();
4355 let preceding_text_matches_prefix = prefix_len == 0
4356 || (selection.start.column >= (prefix_len as u32)
4357 && snapshot.contains_str_at(
4358 Point::new(
4359 selection.start.row,
4360 selection.start.column - (prefix_len as u32),
4361 ),
4362 &pair.start[..prefix_len],
4363 ));
4364 if preceding_text_matches_prefix {
4365 bracket_pair = Some(pair.clone());
4366 is_bracket_pair_start = true;
4367 break;
4368 }
4369 }
4370 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4371 {
4372 // take first bracket pair matching end, but don't break in case a later bracket
4373 // pair matches start
4374 bracket_pair_matching_end = Some(pair.clone());
4375 }
4376 }
4377 if let Some(end) = bracket_pair_matching_end
4378 && bracket_pair.is_none()
4379 {
4380 bracket_pair = Some(end);
4381 is_bracket_pair_end = true;
4382 }
4383 }
4384
4385 if let Some(bracket_pair) = bracket_pair {
4386 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4387 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4388 let auto_surround =
4389 self.use_auto_surround && snapshot_settings.use_auto_surround;
4390 if selection.is_empty() {
4391 if is_bracket_pair_start {
4392 // If the inserted text is a suffix of an opening bracket and the
4393 // selection is preceded by the rest of the opening bracket, then
4394 // insert the closing bracket.
4395 let following_text_allows_autoclose = snapshot
4396 .chars_at(selection.start)
4397 .next()
4398 .is_none_or(|c| scope.should_autoclose_before(c));
4399
4400 let preceding_text_allows_autoclose = selection.start.column == 0
4401 || snapshot
4402 .reversed_chars_at(selection.start)
4403 .next()
4404 .is_none_or(|c| {
4405 bracket_pair.start != bracket_pair.end
4406 || !snapshot
4407 .char_classifier_at(selection.start)
4408 .is_word(c)
4409 });
4410
4411 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4412 && bracket_pair.start.len() == 1
4413 {
4414 let target = bracket_pair.start.chars().next().unwrap();
4415 let mut byte_offset = 0u32;
4416 let current_line_count = snapshot
4417 .reversed_chars_at(selection.start)
4418 .take_while(|&c| c != '\n')
4419 .filter(|c| {
4420 byte_offset += c.len_utf8() as u32;
4421 if *c != target {
4422 return false;
4423 }
4424
4425 let point = Point::new(
4426 selection.start.row,
4427 selection.start.column.saturating_sub(byte_offset),
4428 );
4429
4430 let is_enabled = snapshot
4431 .language_scope_at(point)
4432 .and_then(|scope| {
4433 scope
4434 .brackets()
4435 .find(|(pair, _)| {
4436 pair.start == bracket_pair.start
4437 })
4438 .map(|(_, enabled)| enabled)
4439 })
4440 .unwrap_or(true);
4441
4442 let is_delimiter = snapshot
4443 .language_scope_at(Point::new(
4444 point.row,
4445 point.column + 1,
4446 ))
4447 .and_then(|scope| {
4448 scope
4449 .brackets()
4450 .find(|(pair, _)| {
4451 pair.start == bracket_pair.start
4452 })
4453 .map(|(_, enabled)| !enabled)
4454 })
4455 .unwrap_or(false);
4456
4457 is_enabled && !is_delimiter
4458 })
4459 .count();
4460 current_line_count % 2 == 1
4461 } else {
4462 false
4463 };
4464
4465 if autoclose
4466 && bracket_pair.close
4467 && following_text_allows_autoclose
4468 && preceding_text_allows_autoclose
4469 && !is_closing_quote
4470 {
4471 let anchor = snapshot.anchor_before(selection.end);
4472 new_selections.push((selection.map(|_| anchor), text.len()));
4473 new_autoclose_regions.push((
4474 anchor,
4475 text.len(),
4476 selection.id,
4477 bracket_pair.clone(),
4478 ));
4479 edits.push((
4480 selection.range(),
4481 format!("{}{}", text, bracket_pair.end).into(),
4482 ));
4483 bracket_inserted = true;
4484 continue;
4485 }
4486 }
4487
4488 if let Some(region) = autoclose_region {
4489 // If the selection is followed by an auto-inserted closing bracket,
4490 // then don't insert that closing bracket again; just move the selection
4491 // past the closing bracket.
4492 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4493 && text.as_ref() == region.pair.end.as_str()
4494 && snapshot.contains_str_at(region.range.end, text.as_ref());
4495 if should_skip {
4496 let anchor = snapshot.anchor_after(selection.end);
4497 new_selections
4498 .push((selection.map(|_| anchor), region.pair.end.len()));
4499 continue;
4500 }
4501 }
4502
4503 let always_treat_brackets_as_autoclosed = snapshot
4504 .language_settings_at(selection.start, cx)
4505 .always_treat_brackets_as_autoclosed;
4506 if always_treat_brackets_as_autoclosed
4507 && is_bracket_pair_end
4508 && snapshot.contains_str_at(selection.end, text.as_ref())
4509 {
4510 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4511 // and the inserted text is a closing bracket and the selection is followed
4512 // by the closing bracket then move the selection past the closing bracket.
4513 let anchor = snapshot.anchor_after(selection.end);
4514 new_selections.push((selection.map(|_| anchor), text.len()));
4515 continue;
4516 }
4517 }
4518 // If an opening bracket is 1 character long and is typed while
4519 // text is selected, then surround that text with the bracket pair.
4520 else if auto_surround
4521 && bracket_pair.surround
4522 && is_bracket_pair_start
4523 && bracket_pair.start.chars().count() == 1
4524 {
4525 edits.push((selection.start..selection.start, text.clone()));
4526 edits.push((
4527 selection.end..selection.end,
4528 bracket_pair.end.as_str().into(),
4529 ));
4530 bracket_inserted = true;
4531 new_selections.push((
4532 Selection {
4533 id: selection.id,
4534 start: snapshot.anchor_after(selection.start),
4535 end: snapshot.anchor_before(selection.end),
4536 reversed: selection.reversed,
4537 goal: selection.goal,
4538 },
4539 0,
4540 ));
4541 continue;
4542 }
4543 }
4544 }
4545
4546 if self.auto_replace_emoji_shortcode
4547 && selection.is_empty()
4548 && text.as_ref().ends_with(':')
4549 && let Some(possible_emoji_short_code) =
4550 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4551 && !possible_emoji_short_code.is_empty()
4552 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4553 {
4554 let emoji_shortcode_start = Point::new(
4555 selection.start.row,
4556 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4557 );
4558
4559 // Remove shortcode from buffer
4560 edits.push((
4561 emoji_shortcode_start..selection.start,
4562 "".to_string().into(),
4563 ));
4564 new_selections.push((
4565 Selection {
4566 id: selection.id,
4567 start: snapshot.anchor_after(emoji_shortcode_start),
4568 end: snapshot.anchor_before(selection.start),
4569 reversed: selection.reversed,
4570 goal: selection.goal,
4571 },
4572 0,
4573 ));
4574
4575 // Insert emoji
4576 let selection_start_anchor = snapshot.anchor_after(selection.start);
4577 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4578 edits.push((selection.start..selection.end, emoji.to_string().into()));
4579
4580 continue;
4581 }
4582
4583 // If not handling any auto-close operation, then just replace the selected
4584 // text with the given input and move the selection to the end of the
4585 // newly inserted text.
4586 let anchor = snapshot.anchor_after(selection.end);
4587 if !self.linked_edit_ranges.is_empty() {
4588 let start_anchor = snapshot.anchor_before(selection.start);
4589
4590 let is_word_char = text.chars().next().is_none_or(|char| {
4591 let classifier = snapshot
4592 .char_classifier_at(start_anchor.to_offset(&snapshot))
4593 .scope_context(Some(CharScopeContext::LinkedEdit));
4594 classifier.is_word(char)
4595 });
4596
4597 if is_word_char {
4598 if let Some(ranges) = self
4599 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4600 {
4601 for (buffer, edits) in ranges {
4602 linked_edits
4603 .entry(buffer.clone())
4604 .or_default()
4605 .extend(edits.into_iter().map(|range| (range, text.clone())));
4606 }
4607 }
4608 } else {
4609 clear_linked_edit_ranges = true;
4610 }
4611 }
4612
4613 new_selections.push((selection.map(|_| anchor), 0));
4614 edits.push((selection.start..selection.end, text.clone()));
4615 }
4616
4617 if all_selections_read_only {
4618 return;
4619 }
4620
4621 drop(snapshot);
4622
4623 self.transact(window, cx, |this, window, cx| {
4624 if clear_linked_edit_ranges {
4625 this.linked_edit_ranges.clear();
4626 }
4627 let initial_buffer_versions =
4628 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4629
4630 this.buffer.update(cx, |buffer, cx| {
4631 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4632 });
4633 for (buffer, edits) in linked_edits {
4634 buffer.update(cx, |buffer, cx| {
4635 let snapshot = buffer.snapshot();
4636 let edits = edits
4637 .into_iter()
4638 .map(|(range, text)| {
4639 use text::ToPoint as TP;
4640 let end_point = TP::to_point(&range.end, &snapshot);
4641 let start_point = TP::to_point(&range.start, &snapshot);
4642 (start_point..end_point, text)
4643 })
4644 .sorted_by_key(|(range, _)| range.start);
4645 buffer.edit(edits, None, cx);
4646 })
4647 }
4648 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4649 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4650 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4651 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4652 new_anchor_selections,
4653 &map,
4654 )
4655 .zip(new_selection_deltas)
4656 .map(|(selection, delta)| Selection {
4657 id: selection.id,
4658 start: selection.start + delta,
4659 end: selection.end + delta,
4660 reversed: selection.reversed,
4661 goal: SelectionGoal::None,
4662 })
4663 .collect::<Vec<_>>();
4664
4665 let mut i = 0;
4666 for (position, delta, selection_id, pair) in new_autoclose_regions {
4667 let position = position.to_offset(map.buffer_snapshot()) + delta;
4668 let start = map.buffer_snapshot().anchor_before(position);
4669 let end = map.buffer_snapshot().anchor_after(position);
4670 while let Some(existing_state) = this.autoclose_regions.get(i) {
4671 match existing_state
4672 .range
4673 .start
4674 .cmp(&start, map.buffer_snapshot())
4675 {
4676 Ordering::Less => i += 1,
4677 Ordering::Greater => break,
4678 Ordering::Equal => {
4679 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4680 Ordering::Less => i += 1,
4681 Ordering::Equal => break,
4682 Ordering::Greater => break,
4683 }
4684 }
4685 }
4686 }
4687 this.autoclose_regions.insert(
4688 i,
4689 AutocloseRegion {
4690 selection_id,
4691 range: start..end,
4692 pair,
4693 },
4694 );
4695 }
4696
4697 let had_active_edit_prediction = this.has_active_edit_prediction();
4698 this.change_selections(
4699 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4700 window,
4701 cx,
4702 |s| s.select(new_selections),
4703 );
4704
4705 if !bracket_inserted
4706 && let Some(on_type_format_task) =
4707 this.trigger_on_type_formatting(text.to_string(), window, cx)
4708 {
4709 on_type_format_task.detach_and_log_err(cx);
4710 }
4711
4712 let editor_settings = EditorSettings::get_global(cx);
4713 if bracket_inserted
4714 && (editor_settings.auto_signature_help
4715 || editor_settings.show_signature_help_after_edits)
4716 {
4717 this.show_signature_help(&ShowSignatureHelp, window, cx);
4718 }
4719
4720 let trigger_in_words =
4721 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4722 if this.hard_wrap.is_some() {
4723 let latest: Range<Point> = this.selections.newest(&map).range();
4724 if latest.is_empty()
4725 && this
4726 .buffer()
4727 .read(cx)
4728 .snapshot(cx)
4729 .line_len(MultiBufferRow(latest.start.row))
4730 == latest.start.column
4731 {
4732 this.rewrap_impl(
4733 RewrapOptions {
4734 override_language_settings: true,
4735 preserve_existing_whitespace: true,
4736 },
4737 cx,
4738 )
4739 }
4740 }
4741 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4742 refresh_linked_ranges(this, window, cx);
4743 this.refresh_edit_prediction(true, false, window, cx);
4744 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4745 });
4746 }
4747
4748 fn find_possible_emoji_shortcode_at_position(
4749 snapshot: &MultiBufferSnapshot,
4750 position: Point,
4751 ) -> Option<String> {
4752 let mut chars = Vec::new();
4753 let mut found_colon = false;
4754 for char in snapshot.reversed_chars_at(position).take(100) {
4755 // Found a possible emoji shortcode in the middle of the buffer
4756 if found_colon {
4757 if char.is_whitespace() {
4758 chars.reverse();
4759 return Some(chars.iter().collect());
4760 }
4761 // If the previous character is not a whitespace, we are in the middle of a word
4762 // and we only want to complete the shortcode if the word is made up of other emojis
4763 let mut containing_word = String::new();
4764 for ch in snapshot
4765 .reversed_chars_at(position)
4766 .skip(chars.len() + 1)
4767 .take(100)
4768 {
4769 if ch.is_whitespace() {
4770 break;
4771 }
4772 containing_word.push(ch);
4773 }
4774 let containing_word = containing_word.chars().rev().collect::<String>();
4775 if util::word_consists_of_emojis(containing_word.as_str()) {
4776 chars.reverse();
4777 return Some(chars.iter().collect());
4778 }
4779 }
4780
4781 if char.is_whitespace() || !char.is_ascii() {
4782 return None;
4783 }
4784 if char == ':' {
4785 found_colon = true;
4786 } else {
4787 chars.push(char);
4788 }
4789 }
4790 // Found a possible emoji shortcode at the beginning of the buffer
4791 chars.reverse();
4792 Some(chars.iter().collect())
4793 }
4794
4795 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4797 self.transact(window, cx, |this, window, cx| {
4798 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4799 let selections = this
4800 .selections
4801 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4802 let multi_buffer = this.buffer.read(cx);
4803 let buffer = multi_buffer.snapshot(cx);
4804 selections
4805 .iter()
4806 .map(|selection| {
4807 let start_point = selection.start.to_point(&buffer);
4808 let mut existing_indent =
4809 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4810 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4811 let start = selection.start;
4812 let end = selection.end;
4813 let selection_is_empty = start == end;
4814 let language_scope = buffer.language_scope_at(start);
4815 let (comment_delimiter, doc_delimiter, newline_formatting) =
4816 if let Some(language) = &language_scope {
4817 let mut newline_formatting =
4818 NewlineFormatting::new(&buffer, start..end, language);
4819
4820 // Comment extension on newline is allowed only for cursor selections
4821 let comment_delimiter = maybe!({
4822 if !selection_is_empty {
4823 return None;
4824 }
4825
4826 if !multi_buffer.language_settings(cx).extend_comment_on_newline
4827 {
4828 return None;
4829 }
4830
4831 return comment_delimiter_for_newline(
4832 &start_point,
4833 &buffer,
4834 language,
4835 );
4836 });
4837
4838 let doc_delimiter = maybe!({
4839 if !selection_is_empty {
4840 return None;
4841 }
4842
4843 if !multi_buffer.language_settings(cx).extend_comment_on_newline
4844 {
4845 return None;
4846 }
4847
4848 return documentation_delimiter_for_newline(
4849 &start_point,
4850 &buffer,
4851 language,
4852 &mut newline_formatting,
4853 );
4854 });
4855
4856 (comment_delimiter, doc_delimiter, newline_formatting)
4857 } else {
4858 (None, None, NewlineFormatting::default())
4859 };
4860
4861 let prevent_auto_indent = doc_delimiter.is_some();
4862 let delimiter = comment_delimiter.or(doc_delimiter);
4863
4864 let capacity_for_delimiter =
4865 delimiter.as_deref().map(str::len).unwrap_or_default();
4866 let mut new_text = String::with_capacity(
4867 1 + capacity_for_delimiter
4868 + existing_indent.len as usize
4869 + newline_formatting.indent_on_newline.len as usize
4870 + newline_formatting.indent_on_extra_newline.len as usize,
4871 );
4872 new_text.push('\n');
4873 new_text.extend(existing_indent.chars());
4874 new_text.extend(newline_formatting.indent_on_newline.chars());
4875
4876 if let Some(delimiter) = &delimiter {
4877 new_text.push_str(delimiter);
4878 }
4879
4880 if newline_formatting.insert_extra_newline {
4881 new_text.push('\n');
4882 new_text.extend(existing_indent.chars());
4883 new_text.extend(newline_formatting.indent_on_extra_newline.chars());
4884 }
4885
4886 let anchor = buffer.anchor_after(end);
4887 let new_selection = selection.map(|_| anchor);
4888 (
4889 ((start..end, new_text), prevent_auto_indent),
4890 (newline_formatting.insert_extra_newline, new_selection),
4891 )
4892 })
4893 .unzip()
4894 };
4895
4896 let mut auto_indent_edits = Vec::new();
4897 let mut edits = Vec::new();
4898 for (edit, prevent_auto_indent) in edits_with_flags {
4899 if prevent_auto_indent {
4900 edits.push(edit);
4901 } else {
4902 auto_indent_edits.push(edit);
4903 }
4904 }
4905 if !edits.is_empty() {
4906 this.edit(edits, cx);
4907 }
4908 if !auto_indent_edits.is_empty() {
4909 this.edit_with_autoindent(auto_indent_edits, cx);
4910 }
4911
4912 let buffer = this.buffer.read(cx).snapshot(cx);
4913 let new_selections = selection_info
4914 .into_iter()
4915 .map(|(extra_newline_inserted, new_selection)| {
4916 let mut cursor = new_selection.end.to_point(&buffer);
4917 if extra_newline_inserted {
4918 cursor.row -= 1;
4919 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4920 }
4921 new_selection.map(|_| cursor)
4922 })
4923 .collect();
4924
4925 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4926 this.refresh_edit_prediction(true, false, window, cx);
4927 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
4928 task.detach_and_log_err(cx);
4929 }
4930 });
4931 }
4932
4933 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4934 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4935
4936 let buffer = self.buffer.read(cx);
4937 let snapshot = buffer.snapshot(cx);
4938
4939 let mut edits = Vec::new();
4940 let mut rows = Vec::new();
4941
4942 for (rows_inserted, selection) in self
4943 .selections
4944 .all_adjusted(&self.display_snapshot(cx))
4945 .into_iter()
4946 .enumerate()
4947 {
4948 let cursor = selection.head();
4949 let row = cursor.row;
4950
4951 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4952
4953 let newline = "\n".to_string();
4954 edits.push((start_of_line..start_of_line, newline));
4955
4956 rows.push(row + rows_inserted as u32);
4957 }
4958
4959 self.transact(window, cx, |editor, window, cx| {
4960 editor.edit(edits, cx);
4961
4962 editor.change_selections(Default::default(), window, cx, |s| {
4963 let mut index = 0;
4964 s.move_cursors_with(|map, _, _| {
4965 let row = rows[index];
4966 index += 1;
4967
4968 let point = Point::new(row, 0);
4969 let boundary = map.next_line_boundary(point).1;
4970 let clipped = map.clip_point(boundary, Bias::Left);
4971
4972 (clipped, SelectionGoal::None)
4973 });
4974 });
4975
4976 let mut indent_edits = Vec::new();
4977 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4978 for row in rows {
4979 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4980 for (row, indent) in indents {
4981 if indent.len == 0 {
4982 continue;
4983 }
4984
4985 let text = match indent.kind {
4986 IndentKind::Space => " ".repeat(indent.len as usize),
4987 IndentKind::Tab => "\t".repeat(indent.len as usize),
4988 };
4989 let point = Point::new(row.0, 0);
4990 indent_edits.push((point..point, text));
4991 }
4992 }
4993 editor.edit(indent_edits, cx);
4994 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
4995 format.detach_and_log_err(cx);
4996 }
4997 });
4998 }
4999
5000 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5001 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5002
5003 let buffer = self.buffer.read(cx);
5004 let snapshot = buffer.snapshot(cx);
5005
5006 let mut edits = Vec::new();
5007 let mut rows = Vec::new();
5008 let mut rows_inserted = 0;
5009
5010 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5011 let cursor = selection.head();
5012 let row = cursor.row;
5013
5014 let point = Point::new(row + 1, 0);
5015 let start_of_line = snapshot.clip_point(point, Bias::Left);
5016
5017 let newline = "\n".to_string();
5018 edits.push((start_of_line..start_of_line, newline));
5019
5020 rows_inserted += 1;
5021 rows.push(row + rows_inserted);
5022 }
5023
5024 self.transact(window, cx, |editor, window, cx| {
5025 editor.edit(edits, cx);
5026
5027 editor.change_selections(Default::default(), window, cx, |s| {
5028 let mut index = 0;
5029 s.move_cursors_with(|map, _, _| {
5030 let row = rows[index];
5031 index += 1;
5032
5033 let point = Point::new(row, 0);
5034 let boundary = map.next_line_boundary(point).1;
5035 let clipped = map.clip_point(boundary, Bias::Left);
5036
5037 (clipped, SelectionGoal::None)
5038 });
5039 });
5040
5041 let mut indent_edits = Vec::new();
5042 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5043 for row in rows {
5044 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5045 for (row, indent) in indents {
5046 if indent.len == 0 {
5047 continue;
5048 }
5049
5050 let text = match indent.kind {
5051 IndentKind::Space => " ".repeat(indent.len as usize),
5052 IndentKind::Tab => "\t".repeat(indent.len as usize),
5053 };
5054 let point = Point::new(row.0, 0);
5055 indent_edits.push((point..point, text));
5056 }
5057 }
5058 editor.edit(indent_edits, cx);
5059 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5060 format.detach_and_log_err(cx);
5061 }
5062 });
5063 }
5064
5065 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5066 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5067 original_indent_columns: Vec::new(),
5068 });
5069 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5070 }
5071
5072 fn insert_with_autoindent_mode(
5073 &mut self,
5074 text: &str,
5075 autoindent_mode: Option<AutoindentMode>,
5076 window: &mut Window,
5077 cx: &mut Context<Self>,
5078 ) {
5079 if self.read_only(cx) {
5080 return;
5081 }
5082
5083 let text: Arc<str> = text.into();
5084 self.transact(window, cx, |this, window, cx| {
5085 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5086 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5087 let anchors = {
5088 let snapshot = buffer.read(cx);
5089 old_selections
5090 .iter()
5091 .map(|s| {
5092 let anchor = snapshot.anchor_after(s.head());
5093 s.map(|_| anchor)
5094 })
5095 .collect::<Vec<_>>()
5096 };
5097 buffer.edit(
5098 old_selections
5099 .iter()
5100 .map(|s| (s.start..s.end, text.clone())),
5101 autoindent_mode,
5102 cx,
5103 );
5104 anchors
5105 });
5106
5107 this.change_selections(Default::default(), window, cx, |s| {
5108 s.select_anchors(selection_anchors);
5109 });
5110
5111 cx.notify();
5112 });
5113 }
5114
5115 fn trigger_completion_on_input(
5116 &mut self,
5117 text: &str,
5118 trigger_in_words: bool,
5119 window: &mut Window,
5120 cx: &mut Context<Self>,
5121 ) {
5122 let completions_source = self
5123 .context_menu
5124 .borrow()
5125 .as_ref()
5126 .and_then(|menu| match menu {
5127 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5128 CodeContextMenu::CodeActions(_) => None,
5129 });
5130
5131 match completions_source {
5132 Some(CompletionsMenuSource::Words { .. }) => {
5133 self.open_or_update_completions_menu(
5134 Some(CompletionsMenuSource::Words {
5135 ignore_threshold: false,
5136 }),
5137 None,
5138 trigger_in_words,
5139 window,
5140 cx,
5141 );
5142 }
5143 _ => self.open_or_update_completions_menu(
5144 None,
5145 Some(text.to_owned()).filter(|x| !x.is_empty()),
5146 true,
5147 window,
5148 cx,
5149 ),
5150 }
5151 }
5152
5153 /// If any empty selections is touching the start of its innermost containing autoclose
5154 /// region, expand it to select the brackets.
5155 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5156 let selections = self
5157 .selections
5158 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5159 let buffer = self.buffer.read(cx).read(cx);
5160 let new_selections = self
5161 .selections_with_autoclose_regions(selections, &buffer)
5162 .map(|(mut selection, region)| {
5163 if !selection.is_empty() {
5164 return selection;
5165 }
5166
5167 if let Some(region) = region {
5168 let mut range = region.range.to_offset(&buffer);
5169 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5170 range.start -= region.pair.start.len();
5171 if buffer.contains_str_at(range.start, ®ion.pair.start)
5172 && buffer.contains_str_at(range.end, ®ion.pair.end)
5173 {
5174 range.end += region.pair.end.len();
5175 selection.start = range.start;
5176 selection.end = range.end;
5177
5178 return selection;
5179 }
5180 }
5181 }
5182
5183 let always_treat_brackets_as_autoclosed = buffer
5184 .language_settings_at(selection.start, cx)
5185 .always_treat_brackets_as_autoclosed;
5186
5187 if !always_treat_brackets_as_autoclosed {
5188 return selection;
5189 }
5190
5191 if let Some(scope) = buffer.language_scope_at(selection.start) {
5192 for (pair, enabled) in scope.brackets() {
5193 if !enabled || !pair.close {
5194 continue;
5195 }
5196
5197 if buffer.contains_str_at(selection.start, &pair.end) {
5198 let pair_start_len = pair.start.len();
5199 if buffer.contains_str_at(
5200 selection.start.saturating_sub_usize(pair_start_len),
5201 &pair.start,
5202 ) {
5203 selection.start -= pair_start_len;
5204 selection.end += pair.end.len();
5205
5206 return selection;
5207 }
5208 }
5209 }
5210 }
5211
5212 selection
5213 })
5214 .collect();
5215
5216 drop(buffer);
5217 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5218 selections.select(new_selections)
5219 });
5220 }
5221
5222 /// Iterate the given selections, and for each one, find the smallest surrounding
5223 /// autoclose region. This uses the ordering of the selections and the autoclose
5224 /// regions to avoid repeated comparisons.
5225 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5226 &'a self,
5227 selections: impl IntoIterator<Item = Selection<D>>,
5228 buffer: &'a MultiBufferSnapshot,
5229 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5230 let mut i = 0;
5231 let mut regions = self.autoclose_regions.as_slice();
5232 selections.into_iter().map(move |selection| {
5233 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5234
5235 let mut enclosing = None;
5236 while let Some(pair_state) = regions.get(i) {
5237 if pair_state.range.end.to_offset(buffer) < range.start {
5238 regions = ®ions[i + 1..];
5239 i = 0;
5240 } else if pair_state.range.start.to_offset(buffer) > range.end {
5241 break;
5242 } else {
5243 if pair_state.selection_id == selection.id {
5244 enclosing = Some(pair_state);
5245 }
5246 i += 1;
5247 }
5248 }
5249
5250 (selection, enclosing)
5251 })
5252 }
5253
5254 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5255 fn invalidate_autoclose_regions(
5256 &mut self,
5257 mut selections: &[Selection<Anchor>],
5258 buffer: &MultiBufferSnapshot,
5259 ) {
5260 self.autoclose_regions.retain(|state| {
5261 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5262 return false;
5263 }
5264
5265 let mut i = 0;
5266 while let Some(selection) = selections.get(i) {
5267 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5268 selections = &selections[1..];
5269 continue;
5270 }
5271 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5272 break;
5273 }
5274 if selection.id == state.selection_id {
5275 return true;
5276 } else {
5277 i += 1;
5278 }
5279 }
5280 false
5281 });
5282 }
5283
5284 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5285 let offset = position.to_offset(buffer);
5286 let (word_range, kind) =
5287 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5288 if offset > word_range.start && kind == Some(CharKind::Word) {
5289 Some(
5290 buffer
5291 .text_for_range(word_range.start..offset)
5292 .collect::<String>(),
5293 )
5294 } else {
5295 None
5296 }
5297 }
5298
5299 pub fn visible_excerpts(
5300 &self,
5301 lsp_related_only: bool,
5302 cx: &mut Context<Editor>,
5303 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5304 let project = self.project().cloned();
5305 let multi_buffer = self.buffer().read(cx);
5306 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5307 let multi_buffer_visible_start = self
5308 .scroll_manager
5309 .anchor()
5310 .anchor
5311 .to_point(&multi_buffer_snapshot);
5312 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5313 multi_buffer_visible_start
5314 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5315 Bias::Left,
5316 );
5317 multi_buffer_snapshot
5318 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5319 .into_iter()
5320 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5321 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5322 if !lsp_related_only {
5323 return Some((
5324 excerpt_id,
5325 (
5326 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5327 buffer.version().clone(),
5328 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5329 ),
5330 ));
5331 }
5332
5333 let project = project.as_ref()?.read(cx);
5334 let buffer_file = project::File::from_dyn(buffer.file())?;
5335 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5336 let worktree_entry = buffer_worktree
5337 .read(cx)
5338 .entry_for_id(buffer_file.project_entry_id()?)?;
5339 if worktree_entry.is_ignored {
5340 None
5341 } else {
5342 Some((
5343 excerpt_id,
5344 (
5345 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5346 buffer.version().clone(),
5347 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5348 ),
5349 ))
5350 }
5351 })
5352 .collect()
5353 }
5354
5355 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5356 TextLayoutDetails {
5357 text_system: window.text_system().clone(),
5358 editor_style: self.style.clone().unwrap(),
5359 rem_size: window.rem_size(),
5360 scroll_anchor: self.scroll_manager.anchor(),
5361 visible_rows: self.visible_line_count(),
5362 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5363 }
5364 }
5365
5366 fn trigger_on_type_formatting(
5367 &self,
5368 input: String,
5369 window: &mut Window,
5370 cx: &mut Context<Self>,
5371 ) -> Option<Task<Result<()>>> {
5372 if input.chars().count() != 1 {
5373 return None;
5374 }
5375
5376 let project = self.project()?;
5377 let position = self.selections.newest_anchor().head();
5378 let (buffer, buffer_position) = self
5379 .buffer
5380 .read(cx)
5381 .text_anchor_for_position(position, cx)?;
5382
5383 let settings = language_settings::language_settings(
5384 buffer
5385 .read(cx)
5386 .language_at(buffer_position)
5387 .map(|l| l.name()),
5388 buffer.read(cx).file(),
5389 cx,
5390 );
5391 if !settings.use_on_type_format {
5392 return None;
5393 }
5394
5395 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5396 // hence we do LSP request & edit on host side only — add formats to host's history.
5397 let push_to_lsp_host_history = true;
5398 // If this is not the host, append its history with new edits.
5399 let push_to_client_history = project.read(cx).is_via_collab();
5400
5401 let on_type_formatting = project.update(cx, |project, cx| {
5402 project.on_type_format(
5403 buffer.clone(),
5404 buffer_position,
5405 input,
5406 push_to_lsp_host_history,
5407 cx,
5408 )
5409 });
5410 Some(cx.spawn_in(window, async move |editor, cx| {
5411 if let Some(transaction) = on_type_formatting.await? {
5412 if push_to_client_history {
5413 buffer
5414 .update(cx, |buffer, _| {
5415 buffer.push_transaction(transaction, Instant::now());
5416 buffer.finalize_last_transaction();
5417 })
5418 .ok();
5419 }
5420 editor.update(cx, |editor, cx| {
5421 editor.refresh_document_highlights(cx);
5422 })?;
5423 }
5424 Ok(())
5425 }))
5426 }
5427
5428 pub fn show_word_completions(
5429 &mut self,
5430 _: &ShowWordCompletions,
5431 window: &mut Window,
5432 cx: &mut Context<Self>,
5433 ) {
5434 self.open_or_update_completions_menu(
5435 Some(CompletionsMenuSource::Words {
5436 ignore_threshold: true,
5437 }),
5438 None,
5439 false,
5440 window,
5441 cx,
5442 );
5443 }
5444
5445 pub fn show_completions(
5446 &mut self,
5447 _: &ShowCompletions,
5448 window: &mut Window,
5449 cx: &mut Context<Self>,
5450 ) {
5451 self.open_or_update_completions_menu(None, None, false, window, cx);
5452 }
5453
5454 fn open_or_update_completions_menu(
5455 &mut self,
5456 requested_source: Option<CompletionsMenuSource>,
5457 trigger: Option<String>,
5458 trigger_in_words: bool,
5459 window: &mut Window,
5460 cx: &mut Context<Self>,
5461 ) {
5462 if self.pending_rename.is_some() {
5463 return;
5464 }
5465
5466 let completions_source = self
5467 .context_menu
5468 .borrow()
5469 .as_ref()
5470 .and_then(|menu| match menu {
5471 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5472 CodeContextMenu::CodeActions(_) => None,
5473 });
5474
5475 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5476
5477 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5478 // inserted and selected. To handle that case, the start of the selection is used so that
5479 // the menu starts with all choices.
5480 let position = self
5481 .selections
5482 .newest_anchor()
5483 .start
5484 .bias_right(&multibuffer_snapshot);
5485 if position.diff_base_anchor.is_some() {
5486 return;
5487 }
5488 let buffer_position = multibuffer_snapshot.anchor_before(position);
5489 let Some(buffer) = buffer_position
5490 .text_anchor
5491 .buffer_id
5492 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5493 else {
5494 return;
5495 };
5496 let buffer_snapshot = buffer.read(cx).snapshot();
5497
5498 let menu_is_open = matches!(
5499 self.context_menu.borrow().as_ref(),
5500 Some(CodeContextMenu::Completions(_))
5501 );
5502
5503 let language = buffer_snapshot
5504 .language_at(buffer_position.text_anchor)
5505 .map(|language| language.name());
5506
5507 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5508 let completion_settings = language_settings.completions.clone();
5509
5510 let show_completions_on_input = self
5511 .show_completions_on_input_override
5512 .unwrap_or(language_settings.show_completions_on_input);
5513 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5514 return;
5515 }
5516
5517 let query: Option<Arc<String>> =
5518 Self::completion_query(&multibuffer_snapshot, buffer_position)
5519 .map(|query| query.into());
5520
5521 drop(multibuffer_snapshot);
5522
5523 // Hide the current completions menu when query is empty. Without this, cached
5524 // completions from before the trigger char may be reused (#32774).
5525 if query.is_none() && menu_is_open {
5526 self.hide_context_menu(window, cx);
5527 }
5528
5529 let mut ignore_word_threshold = false;
5530 let provider = match requested_source {
5531 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5532 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5533 ignore_word_threshold = ignore_threshold;
5534 None
5535 }
5536 Some(CompletionsMenuSource::SnippetChoices)
5537 | Some(CompletionsMenuSource::SnippetsOnly) => {
5538 log::error!("bug: SnippetChoices requested_source is not handled");
5539 None
5540 }
5541 };
5542
5543 let sort_completions = provider
5544 .as_ref()
5545 .is_some_and(|provider| provider.sort_completions());
5546
5547 let filter_completions = provider
5548 .as_ref()
5549 .is_none_or(|provider| provider.filter_completions());
5550
5551 let was_snippets_only = matches!(
5552 completions_source,
5553 Some(CompletionsMenuSource::SnippetsOnly)
5554 );
5555
5556 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5557 if filter_completions {
5558 menu.filter(
5559 query.clone().unwrap_or_default(),
5560 buffer_position.text_anchor,
5561 &buffer,
5562 provider.clone(),
5563 window,
5564 cx,
5565 );
5566 }
5567 // When `is_incomplete` is false, no need to re-query completions when the current query
5568 // is a suffix of the initial query.
5569 let was_complete = !menu.is_incomplete;
5570 if was_complete && !was_snippets_only {
5571 // If the new query is a suffix of the old query (typing more characters) and
5572 // the previous result was complete, the existing completions can be filtered.
5573 //
5574 // Note that snippet completions are always complete.
5575 let query_matches = match (&menu.initial_query, &query) {
5576 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5577 (None, _) => true,
5578 _ => false,
5579 };
5580 if query_matches {
5581 let position_matches = if menu.initial_position == position {
5582 true
5583 } else {
5584 let snapshot = self.buffer.read(cx).read(cx);
5585 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5586 };
5587 if position_matches {
5588 return;
5589 }
5590 }
5591 }
5592 };
5593
5594 let Anchor {
5595 excerpt_id: buffer_excerpt_id,
5596 text_anchor: buffer_position,
5597 ..
5598 } = buffer_position;
5599
5600 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5601 buffer_snapshot.surrounding_word(buffer_position, None)
5602 {
5603 let word_to_exclude = buffer_snapshot
5604 .text_for_range(word_range.clone())
5605 .collect::<String>();
5606 (
5607 buffer_snapshot.anchor_before(word_range.start)
5608 ..buffer_snapshot.anchor_after(buffer_position),
5609 Some(word_to_exclude),
5610 )
5611 } else {
5612 (buffer_position..buffer_position, None)
5613 };
5614
5615 let show_completion_documentation = buffer_snapshot
5616 .settings_at(buffer_position, cx)
5617 .show_completion_documentation;
5618
5619 // The document can be large, so stay in reasonable bounds when searching for words,
5620 // otherwise completion pop-up might be slow to appear.
5621 const WORD_LOOKUP_ROWS: u32 = 5_000;
5622 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5623 let min_word_search = buffer_snapshot.clip_point(
5624 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5625 Bias::Left,
5626 );
5627 let max_word_search = buffer_snapshot.clip_point(
5628 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5629 Bias::Right,
5630 );
5631 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5632 ..buffer_snapshot.point_to_offset(max_word_search);
5633
5634 let skip_digits = query
5635 .as_ref()
5636 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5637
5638 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5639 trigger.as_ref().is_none_or(|trigger| {
5640 provider.is_completion_trigger(
5641 &buffer,
5642 position.text_anchor,
5643 trigger,
5644 trigger_in_words,
5645 cx,
5646 )
5647 })
5648 });
5649
5650 let provider_responses = if let Some(provider) = &provider
5651 && load_provider_completions
5652 {
5653 let trigger_character =
5654 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5655 let completion_context = CompletionContext {
5656 trigger_kind: match &trigger_character {
5657 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5658 None => CompletionTriggerKind::INVOKED,
5659 },
5660 trigger_character,
5661 };
5662
5663 provider.completions(
5664 buffer_excerpt_id,
5665 &buffer,
5666 buffer_position,
5667 completion_context,
5668 window,
5669 cx,
5670 )
5671 } else {
5672 Task::ready(Ok(Vec::new()))
5673 };
5674
5675 let load_word_completions = if !self.word_completions_enabled {
5676 false
5677 } else if requested_source
5678 == Some(CompletionsMenuSource::Words {
5679 ignore_threshold: true,
5680 })
5681 {
5682 true
5683 } else {
5684 load_provider_completions
5685 && completion_settings.words != WordsCompletionMode::Disabled
5686 && (ignore_word_threshold || {
5687 let words_min_length = completion_settings.words_min_length;
5688 // check whether word has at least `words_min_length` characters
5689 let query_chars = query.iter().flat_map(|q| q.chars());
5690 query_chars.take(words_min_length).count() == words_min_length
5691 })
5692 };
5693
5694 let mut words = if load_word_completions {
5695 cx.background_spawn({
5696 let buffer_snapshot = buffer_snapshot.clone();
5697 async move {
5698 buffer_snapshot.words_in_range(WordsQuery {
5699 fuzzy_contents: None,
5700 range: word_search_range,
5701 skip_digits,
5702 })
5703 }
5704 })
5705 } else {
5706 Task::ready(BTreeMap::default())
5707 };
5708
5709 let snippets = if let Some(provider) = &provider
5710 && provider.show_snippets()
5711 && let Some(project) = self.project()
5712 {
5713 let char_classifier = buffer_snapshot
5714 .char_classifier_at(buffer_position)
5715 .scope_context(Some(CharScopeContext::Completion));
5716 project.update(cx, |project, cx| {
5717 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5718 })
5719 } else {
5720 Task::ready(Ok(CompletionResponse {
5721 completions: Vec::new(),
5722 display_options: Default::default(),
5723 is_incomplete: false,
5724 }))
5725 };
5726
5727 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5728
5729 let id = post_inc(&mut self.next_completion_id);
5730 let task = cx.spawn_in(window, async move |editor, cx| {
5731 let Ok(()) = editor.update(cx, |this, _| {
5732 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5733 }) else {
5734 return;
5735 };
5736
5737 // TODO: Ideally completions from different sources would be selectively re-queried, so
5738 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5739 let mut completions = Vec::new();
5740 let mut is_incomplete = false;
5741 let mut display_options: Option<CompletionDisplayOptions> = None;
5742 if let Some(provider_responses) = provider_responses.await.log_err()
5743 && !provider_responses.is_empty()
5744 {
5745 for response in provider_responses {
5746 completions.extend(response.completions);
5747 is_incomplete = is_incomplete || response.is_incomplete;
5748 match display_options.as_mut() {
5749 None => {
5750 display_options = Some(response.display_options);
5751 }
5752 Some(options) => options.merge(&response.display_options),
5753 }
5754 }
5755 if completion_settings.words == WordsCompletionMode::Fallback {
5756 words = Task::ready(BTreeMap::default());
5757 }
5758 }
5759 let display_options = display_options.unwrap_or_default();
5760
5761 let mut words = words.await;
5762 if let Some(word_to_exclude) = &word_to_exclude {
5763 words.remove(word_to_exclude);
5764 }
5765 for lsp_completion in &completions {
5766 words.remove(&lsp_completion.new_text);
5767 }
5768 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5769 replace_range: word_replace_range.clone(),
5770 new_text: word.clone(),
5771 label: CodeLabel::plain(word, None),
5772 match_start: None,
5773 snippet_deduplication_key: None,
5774 icon_path: None,
5775 documentation: None,
5776 source: CompletionSource::BufferWord {
5777 word_range,
5778 resolved: false,
5779 },
5780 insert_text_mode: Some(InsertTextMode::AS_IS),
5781 confirm: None,
5782 }));
5783
5784 completions.extend(
5785 snippets
5786 .await
5787 .into_iter()
5788 .flat_map(|response| response.completions),
5789 );
5790
5791 let menu = if completions.is_empty() {
5792 None
5793 } else {
5794 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5795 let languages = editor
5796 .workspace
5797 .as_ref()
5798 .and_then(|(workspace, _)| workspace.upgrade())
5799 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5800 let menu = CompletionsMenu::new(
5801 id,
5802 requested_source.unwrap_or(if load_provider_completions {
5803 CompletionsMenuSource::Normal
5804 } else {
5805 CompletionsMenuSource::SnippetsOnly
5806 }),
5807 sort_completions,
5808 show_completion_documentation,
5809 position,
5810 query.clone(),
5811 is_incomplete,
5812 buffer.clone(),
5813 completions.into(),
5814 editor
5815 .context_menu()
5816 .borrow_mut()
5817 .as_ref()
5818 .map(|menu| menu.primary_scroll_handle()),
5819 display_options,
5820 snippet_sort_order,
5821 languages,
5822 language,
5823 cx,
5824 );
5825
5826 let query = if filter_completions { query } else { None };
5827 let matches_task = menu.do_async_filtering(
5828 query.unwrap_or_default(),
5829 buffer_position,
5830 &buffer,
5831 cx,
5832 );
5833 (menu, matches_task)
5834 }) else {
5835 return;
5836 };
5837
5838 let matches = matches_task.await;
5839
5840 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5841 // Newer menu already set, so exit.
5842 if let Some(CodeContextMenu::Completions(prev_menu)) =
5843 editor.context_menu.borrow().as_ref()
5844 && prev_menu.id > id
5845 {
5846 return;
5847 };
5848
5849 // Only valid to take prev_menu because either the new menu is immediately set
5850 // below, or the menu is hidden.
5851 if let Some(CodeContextMenu::Completions(prev_menu)) =
5852 editor.context_menu.borrow_mut().take()
5853 {
5854 let position_matches =
5855 if prev_menu.initial_position == menu.initial_position {
5856 true
5857 } else {
5858 let snapshot = editor.buffer.read(cx).read(cx);
5859 prev_menu.initial_position.to_offset(&snapshot)
5860 == menu.initial_position.to_offset(&snapshot)
5861 };
5862 if position_matches {
5863 // Preserve markdown cache before `set_filter_results` because it will
5864 // try to populate the documentation cache.
5865 menu.preserve_markdown_cache(prev_menu);
5866 }
5867 };
5868
5869 menu.set_filter_results(matches, provider, window, cx);
5870 }) else {
5871 return;
5872 };
5873
5874 menu.visible().then_some(menu)
5875 };
5876
5877 editor
5878 .update_in(cx, |editor, window, cx| {
5879 if editor.focus_handle.is_focused(window)
5880 && let Some(menu) = menu
5881 {
5882 *editor.context_menu.borrow_mut() =
5883 Some(CodeContextMenu::Completions(menu));
5884
5885 crate::hover_popover::hide_hover(editor, cx);
5886 if editor.show_edit_predictions_in_menu() {
5887 editor.update_visible_edit_prediction(window, cx);
5888 } else {
5889 editor.discard_edit_prediction(false, cx);
5890 }
5891
5892 cx.notify();
5893 return;
5894 }
5895
5896 if editor.completion_tasks.len() <= 1 {
5897 // If there are no more completion tasks and the last menu was empty, we should hide it.
5898 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5899 // If it was already hidden and we don't show edit predictions in the menu,
5900 // we should also show the edit prediction when available.
5901 if was_hidden && editor.show_edit_predictions_in_menu() {
5902 editor.update_visible_edit_prediction(window, cx);
5903 }
5904 }
5905 })
5906 .ok();
5907 });
5908
5909 self.completion_tasks.push((id, task));
5910 }
5911
5912 #[cfg(feature = "test-support")]
5913 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5914 let menu = self.context_menu.borrow();
5915 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5916 let completions = menu.completions.borrow();
5917 Some(completions.to_vec())
5918 } else {
5919 None
5920 }
5921 }
5922
5923 pub fn with_completions_menu_matching_id<R>(
5924 &self,
5925 id: CompletionId,
5926 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5927 ) -> R {
5928 let mut context_menu = self.context_menu.borrow_mut();
5929 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5930 return f(None);
5931 };
5932 if completions_menu.id != id {
5933 return f(None);
5934 }
5935 f(Some(completions_menu))
5936 }
5937
5938 pub fn confirm_completion(
5939 &mut self,
5940 action: &ConfirmCompletion,
5941 window: &mut Window,
5942 cx: &mut Context<Self>,
5943 ) -> Option<Task<Result<()>>> {
5944 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5945 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5946 }
5947
5948 pub fn confirm_completion_insert(
5949 &mut self,
5950 _: &ConfirmCompletionInsert,
5951 window: &mut Window,
5952 cx: &mut Context<Self>,
5953 ) -> Option<Task<Result<()>>> {
5954 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5955 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5956 }
5957
5958 pub fn confirm_completion_replace(
5959 &mut self,
5960 _: &ConfirmCompletionReplace,
5961 window: &mut Window,
5962 cx: &mut Context<Self>,
5963 ) -> Option<Task<Result<()>>> {
5964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5965 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5966 }
5967
5968 pub fn compose_completion(
5969 &mut self,
5970 action: &ComposeCompletion,
5971 window: &mut Window,
5972 cx: &mut Context<Self>,
5973 ) -> Option<Task<Result<()>>> {
5974 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5975 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5976 }
5977
5978 fn do_completion(
5979 &mut self,
5980 item_ix: Option<usize>,
5981 intent: CompletionIntent,
5982 window: &mut Window,
5983 cx: &mut Context<Editor>,
5984 ) -> Option<Task<Result<()>>> {
5985 use language::ToOffset as _;
5986
5987 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5988 else {
5989 return None;
5990 };
5991
5992 let candidate_id = {
5993 let entries = completions_menu.entries.borrow();
5994 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5995 if self.show_edit_predictions_in_menu() {
5996 self.discard_edit_prediction(true, cx);
5997 }
5998 mat.candidate_id
5999 };
6000
6001 let completion = completions_menu
6002 .completions
6003 .borrow()
6004 .get(candidate_id)?
6005 .clone();
6006 cx.stop_propagation();
6007
6008 let buffer_handle = completions_menu.buffer.clone();
6009
6010 let CompletionEdit {
6011 new_text,
6012 snippet,
6013 replace_range,
6014 } = process_completion_for_edit(
6015 &completion,
6016 intent,
6017 &buffer_handle,
6018 &completions_menu.initial_position.text_anchor,
6019 cx,
6020 );
6021
6022 let buffer = buffer_handle.read(cx);
6023 let snapshot = self.buffer.read(cx).snapshot(cx);
6024 let newest_anchor = self.selections.newest_anchor();
6025 let replace_range_multibuffer = {
6026 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6027 excerpt.map_range_from_buffer(replace_range.clone())
6028 };
6029 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6030 return None;
6031 }
6032
6033 let old_text = buffer
6034 .text_for_range(replace_range.clone())
6035 .collect::<String>();
6036 let lookbehind = newest_anchor
6037 .start
6038 .text_anchor
6039 .to_offset(buffer)
6040 .saturating_sub(replace_range.start.0);
6041 let lookahead = replace_range
6042 .end
6043 .0
6044 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6045 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6046 let suffix = &old_text[lookbehind.min(old_text.len())..];
6047
6048 let selections = self
6049 .selections
6050 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6051 let mut ranges = Vec::new();
6052 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6053
6054 for selection in &selections {
6055 let range = if selection.id == newest_anchor.id {
6056 replace_range_multibuffer.clone()
6057 } else {
6058 let mut range = selection.range();
6059
6060 // if prefix is present, don't duplicate it
6061 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6062 range.start = range.start.saturating_sub_usize(lookbehind);
6063
6064 // if suffix is also present, mimic the newest cursor and replace it
6065 if selection.id != newest_anchor.id
6066 && snapshot.contains_str_at(range.end, suffix)
6067 {
6068 range.end += lookahead;
6069 }
6070 }
6071 range
6072 };
6073
6074 ranges.push(range.clone());
6075
6076 if !self.linked_edit_ranges.is_empty() {
6077 let start_anchor = snapshot.anchor_before(range.start);
6078 let end_anchor = snapshot.anchor_after(range.end);
6079 if let Some(ranges) = self
6080 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6081 {
6082 for (buffer, edits) in ranges {
6083 linked_edits
6084 .entry(buffer.clone())
6085 .or_default()
6086 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6087 }
6088 }
6089 }
6090 }
6091
6092 let common_prefix_len = old_text
6093 .chars()
6094 .zip(new_text.chars())
6095 .take_while(|(a, b)| a == b)
6096 .map(|(a, _)| a.len_utf8())
6097 .sum::<usize>();
6098
6099 cx.emit(EditorEvent::InputHandled {
6100 utf16_range_to_replace: None,
6101 text: new_text[common_prefix_len..].into(),
6102 });
6103
6104 self.transact(window, cx, |editor, window, cx| {
6105 if let Some(mut snippet) = snippet {
6106 snippet.text = new_text.to_string();
6107 editor
6108 .insert_snippet(&ranges, snippet, window, cx)
6109 .log_err();
6110 } else {
6111 editor.buffer.update(cx, |multi_buffer, cx| {
6112 let auto_indent = match completion.insert_text_mode {
6113 Some(InsertTextMode::AS_IS) => None,
6114 _ => editor.autoindent_mode.clone(),
6115 };
6116 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6117 multi_buffer.edit(edits, auto_indent, cx);
6118 });
6119 }
6120 for (buffer, edits) in linked_edits {
6121 buffer.update(cx, |buffer, cx| {
6122 let snapshot = buffer.snapshot();
6123 let edits = edits
6124 .into_iter()
6125 .map(|(range, text)| {
6126 use text::ToPoint as TP;
6127 let end_point = TP::to_point(&range.end, &snapshot);
6128 let start_point = TP::to_point(&range.start, &snapshot);
6129 (start_point..end_point, text)
6130 })
6131 .sorted_by_key(|(range, _)| range.start);
6132 buffer.edit(edits, None, cx);
6133 })
6134 }
6135
6136 editor.refresh_edit_prediction(true, false, window, cx);
6137 });
6138 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6139
6140 let show_new_completions_on_confirm = completion
6141 .confirm
6142 .as_ref()
6143 .is_some_and(|confirm| confirm(intent, window, cx));
6144 if show_new_completions_on_confirm {
6145 self.open_or_update_completions_menu(None, None, false, window, cx);
6146 }
6147
6148 let provider = self.completion_provider.as_ref()?;
6149
6150 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6151 let command = lsp_store.as_ref().and_then(|lsp_store| {
6152 let CompletionSource::Lsp {
6153 lsp_completion,
6154 server_id,
6155 ..
6156 } = &completion.source
6157 else {
6158 return None;
6159 };
6160 let lsp_command = lsp_completion.command.as_ref()?;
6161 let available_commands = lsp_store
6162 .read(cx)
6163 .lsp_server_capabilities
6164 .get(server_id)
6165 .and_then(|server_capabilities| {
6166 server_capabilities
6167 .execute_command_provider
6168 .as_ref()
6169 .map(|options| options.commands.as_slice())
6170 })?;
6171 if available_commands.contains(&lsp_command.command) {
6172 Some(CodeAction {
6173 server_id: *server_id,
6174 range: language::Anchor::MIN..language::Anchor::MIN,
6175 lsp_action: LspAction::Command(lsp_command.clone()),
6176 resolved: false,
6177 })
6178 } else {
6179 None
6180 }
6181 });
6182
6183 drop(completion);
6184 let apply_edits = provider.apply_additional_edits_for_completion(
6185 buffer_handle.clone(),
6186 completions_menu.completions.clone(),
6187 candidate_id,
6188 true,
6189 cx,
6190 );
6191
6192 let editor_settings = EditorSettings::get_global(cx);
6193 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6194 // After the code completion is finished, users often want to know what signatures are needed.
6195 // so we should automatically call signature_help
6196 self.show_signature_help(&ShowSignatureHelp, window, cx);
6197 }
6198
6199 Some(cx.spawn_in(window, async move |editor, cx| {
6200 apply_edits.await?;
6201
6202 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6203 let title = command.lsp_action.title().to_owned();
6204 let project_transaction = lsp_store
6205 .update(cx, |lsp_store, cx| {
6206 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6207 })?
6208 .await
6209 .context("applying post-completion command")?;
6210 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6211 Self::open_project_transaction(
6212 &editor,
6213 workspace.downgrade(),
6214 project_transaction,
6215 title,
6216 cx,
6217 )
6218 .await?;
6219 }
6220 }
6221
6222 Ok(())
6223 }))
6224 }
6225
6226 pub fn toggle_code_actions(
6227 &mut self,
6228 action: &ToggleCodeActions,
6229 window: &mut Window,
6230 cx: &mut Context<Self>,
6231 ) {
6232 let quick_launch = action.quick_launch;
6233 let mut context_menu = self.context_menu.borrow_mut();
6234 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6235 if code_actions.deployed_from == action.deployed_from {
6236 // Toggle if we're selecting the same one
6237 *context_menu = None;
6238 cx.notify();
6239 return;
6240 } else {
6241 // Otherwise, clear it and start a new one
6242 *context_menu = None;
6243 cx.notify();
6244 }
6245 }
6246 drop(context_menu);
6247 let snapshot = self.snapshot(window, cx);
6248 let deployed_from = action.deployed_from.clone();
6249 let action = action.clone();
6250 self.completion_tasks.clear();
6251 self.discard_edit_prediction(false, cx);
6252
6253 let multibuffer_point = match &action.deployed_from {
6254 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6255 DisplayPoint::new(*row, 0).to_point(&snapshot)
6256 }
6257 _ => self
6258 .selections
6259 .newest::<Point>(&snapshot.display_snapshot)
6260 .head(),
6261 };
6262 let Some((buffer, buffer_row)) = snapshot
6263 .buffer_snapshot()
6264 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6265 .and_then(|(buffer_snapshot, range)| {
6266 self.buffer()
6267 .read(cx)
6268 .buffer(buffer_snapshot.remote_id())
6269 .map(|buffer| (buffer, range.start.row))
6270 })
6271 else {
6272 return;
6273 };
6274 let buffer_id = buffer.read(cx).remote_id();
6275 let tasks = self
6276 .tasks
6277 .get(&(buffer_id, buffer_row))
6278 .map(|t| Arc::new(t.to_owned()));
6279
6280 if !self.focus_handle.is_focused(window) {
6281 return;
6282 }
6283 let project = self.project.clone();
6284
6285 let code_actions_task = match deployed_from {
6286 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6287 _ => self.code_actions(buffer_row, window, cx),
6288 };
6289
6290 let runnable_task = match deployed_from {
6291 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6292 _ => {
6293 let mut task_context_task = Task::ready(None);
6294 if let Some(tasks) = &tasks
6295 && let Some(project) = project
6296 {
6297 task_context_task =
6298 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6299 }
6300
6301 cx.spawn_in(window, {
6302 let buffer = buffer.clone();
6303 async move |editor, cx| {
6304 let task_context = task_context_task.await;
6305
6306 let resolved_tasks =
6307 tasks
6308 .zip(task_context.clone())
6309 .map(|(tasks, task_context)| ResolvedTasks {
6310 templates: tasks.resolve(&task_context).collect(),
6311 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6312 multibuffer_point.row,
6313 tasks.column,
6314 )),
6315 });
6316 let debug_scenarios = editor
6317 .update(cx, |editor, cx| {
6318 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6319 })?
6320 .await;
6321 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6322 }
6323 })
6324 }
6325 };
6326
6327 cx.spawn_in(window, async move |editor, cx| {
6328 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6329 let code_actions = code_actions_task.await;
6330 let spawn_straight_away = quick_launch
6331 && resolved_tasks
6332 .as_ref()
6333 .is_some_and(|tasks| tasks.templates.len() == 1)
6334 && code_actions
6335 .as_ref()
6336 .is_none_or(|actions| actions.is_empty())
6337 && debug_scenarios.is_empty();
6338
6339 editor.update_in(cx, |editor, window, cx| {
6340 crate::hover_popover::hide_hover(editor, cx);
6341 let actions = CodeActionContents::new(
6342 resolved_tasks,
6343 code_actions,
6344 debug_scenarios,
6345 task_context.unwrap_or_default(),
6346 );
6347
6348 // Don't show the menu if there are no actions available
6349 if actions.is_empty() {
6350 cx.notify();
6351 return Task::ready(Ok(()));
6352 }
6353
6354 *editor.context_menu.borrow_mut() =
6355 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6356 buffer,
6357 actions,
6358 selected_item: Default::default(),
6359 scroll_handle: UniformListScrollHandle::default(),
6360 deployed_from,
6361 }));
6362 cx.notify();
6363 if spawn_straight_away
6364 && let Some(task) = editor.confirm_code_action(
6365 &ConfirmCodeAction { item_ix: Some(0) },
6366 window,
6367 cx,
6368 )
6369 {
6370 return task;
6371 }
6372
6373 Task::ready(Ok(()))
6374 })
6375 })
6376 .detach_and_log_err(cx);
6377 }
6378
6379 fn debug_scenarios(
6380 &mut self,
6381 resolved_tasks: &Option<ResolvedTasks>,
6382 buffer: &Entity<Buffer>,
6383 cx: &mut App,
6384 ) -> Task<Vec<task::DebugScenario>> {
6385 maybe!({
6386 let project = self.project()?;
6387 let dap_store = project.read(cx).dap_store();
6388 let mut scenarios = vec![];
6389 let resolved_tasks = resolved_tasks.as_ref()?;
6390 let buffer = buffer.read(cx);
6391 let language = buffer.language()?;
6392 let file = buffer.file();
6393 let debug_adapter = language_settings(language.name().into(), file, cx)
6394 .debuggers
6395 .first()
6396 .map(SharedString::from)
6397 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6398
6399 dap_store.update(cx, |dap_store, cx| {
6400 for (_, task) in &resolved_tasks.templates {
6401 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6402 task.original_task().clone(),
6403 debug_adapter.clone().into(),
6404 task.display_label().to_owned().into(),
6405 cx,
6406 );
6407 scenarios.push(maybe_scenario);
6408 }
6409 });
6410 Some(cx.background_spawn(async move {
6411 futures::future::join_all(scenarios)
6412 .await
6413 .into_iter()
6414 .flatten()
6415 .collect::<Vec<_>>()
6416 }))
6417 })
6418 .unwrap_or_else(|| Task::ready(vec![]))
6419 }
6420
6421 fn code_actions(
6422 &mut self,
6423 buffer_row: u32,
6424 window: &mut Window,
6425 cx: &mut Context<Self>,
6426 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6427 let mut task = self.code_actions_task.take();
6428 cx.spawn_in(window, async move |editor, cx| {
6429 while let Some(prev_task) = task {
6430 prev_task.await.log_err();
6431 task = editor
6432 .update(cx, |this, _| this.code_actions_task.take())
6433 .ok()?;
6434 }
6435
6436 editor
6437 .update(cx, |editor, cx| {
6438 editor
6439 .available_code_actions
6440 .clone()
6441 .and_then(|(location, code_actions)| {
6442 let snapshot = location.buffer.read(cx).snapshot();
6443 let point_range = location.range.to_point(&snapshot);
6444 let point_range = point_range.start.row..=point_range.end.row;
6445 if point_range.contains(&buffer_row) {
6446 Some(code_actions)
6447 } else {
6448 None
6449 }
6450 })
6451 })
6452 .ok()
6453 .flatten()
6454 })
6455 }
6456
6457 pub fn confirm_code_action(
6458 &mut self,
6459 action: &ConfirmCodeAction,
6460 window: &mut Window,
6461 cx: &mut Context<Self>,
6462 ) -> Option<Task<Result<()>>> {
6463 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6464
6465 let actions_menu =
6466 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6467 menu
6468 } else {
6469 return None;
6470 };
6471
6472 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6473 let action = actions_menu.actions.get(action_ix)?;
6474 let title = action.label();
6475 let buffer = actions_menu.buffer;
6476 let workspace = self.workspace()?;
6477
6478 match action {
6479 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6480 workspace.update(cx, |workspace, cx| {
6481 workspace.schedule_resolved_task(
6482 task_source_kind,
6483 resolved_task,
6484 false,
6485 window,
6486 cx,
6487 );
6488
6489 Some(Task::ready(Ok(())))
6490 })
6491 }
6492 CodeActionsItem::CodeAction {
6493 excerpt_id,
6494 action,
6495 provider,
6496 } => {
6497 let apply_code_action =
6498 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6499 let workspace = workspace.downgrade();
6500 Some(cx.spawn_in(window, async move |editor, cx| {
6501 let project_transaction = apply_code_action.await?;
6502 Self::open_project_transaction(
6503 &editor,
6504 workspace,
6505 project_transaction,
6506 title,
6507 cx,
6508 )
6509 .await
6510 }))
6511 }
6512 CodeActionsItem::DebugScenario(scenario) => {
6513 let context = actions_menu.actions.context;
6514
6515 workspace.update(cx, |workspace, cx| {
6516 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6517 workspace.start_debug_session(
6518 scenario,
6519 context,
6520 Some(buffer),
6521 None,
6522 window,
6523 cx,
6524 );
6525 });
6526 Some(Task::ready(Ok(())))
6527 }
6528 }
6529 }
6530
6531 fn open_transaction_for_hidden_buffers(
6532 workspace: Entity<Workspace>,
6533 transaction: ProjectTransaction,
6534 title: String,
6535 window: &mut Window,
6536 cx: &mut Context<Self>,
6537 ) {
6538 if transaction.0.is_empty() {
6539 return;
6540 }
6541
6542 let edited_buffers_already_open = {
6543 let other_editors: Vec<Entity<Editor>> = workspace
6544 .read(cx)
6545 .panes()
6546 .iter()
6547 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6548 .filter(|editor| editor.entity_id() != cx.entity_id())
6549 .collect();
6550
6551 transaction.0.keys().all(|buffer| {
6552 other_editors.iter().any(|editor| {
6553 let multi_buffer = editor.read(cx).buffer();
6554 multi_buffer.read(cx).is_singleton()
6555 && multi_buffer
6556 .read(cx)
6557 .as_singleton()
6558 .map_or(false, |singleton| {
6559 singleton.entity_id() == buffer.entity_id()
6560 })
6561 })
6562 })
6563 };
6564 if !edited_buffers_already_open {
6565 let workspace = workspace.downgrade();
6566 cx.defer_in(window, move |_, window, cx| {
6567 cx.spawn_in(window, async move |editor, cx| {
6568 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6569 .await
6570 .ok()
6571 })
6572 .detach();
6573 });
6574 }
6575 }
6576
6577 pub async fn open_project_transaction(
6578 editor: &WeakEntity<Editor>,
6579 workspace: WeakEntity<Workspace>,
6580 transaction: ProjectTransaction,
6581 title: String,
6582 cx: &mut AsyncWindowContext,
6583 ) -> Result<()> {
6584 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6585 cx.update(|_, cx| {
6586 entries.sort_unstable_by_key(|(buffer, _)| {
6587 buffer.read(cx).file().map(|f| f.path().clone())
6588 });
6589 })?;
6590 if entries.is_empty() {
6591 return Ok(());
6592 }
6593
6594 // If the project transaction's edits are all contained within this editor, then
6595 // avoid opening a new editor to display them.
6596
6597 if let [(buffer, transaction)] = &*entries {
6598 let excerpt = editor.update(cx, |editor, cx| {
6599 editor
6600 .buffer()
6601 .read(cx)
6602 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6603 })?;
6604 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6605 && excerpted_buffer == *buffer
6606 {
6607 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6608 let excerpt_range = excerpt_range.to_offset(buffer);
6609 buffer
6610 .edited_ranges_for_transaction::<usize>(transaction)
6611 .all(|range| {
6612 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6613 })
6614 })?;
6615
6616 if all_edits_within_excerpt {
6617 return Ok(());
6618 }
6619 }
6620 }
6621
6622 let mut ranges_to_highlight = Vec::new();
6623 let excerpt_buffer = cx.new(|cx| {
6624 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6625 for (buffer_handle, transaction) in &entries {
6626 let edited_ranges = buffer_handle
6627 .read(cx)
6628 .edited_ranges_for_transaction::<Point>(transaction)
6629 .collect::<Vec<_>>();
6630 let (ranges, _) = multibuffer.set_excerpts_for_path(
6631 PathKey::for_buffer(buffer_handle, cx),
6632 buffer_handle.clone(),
6633 edited_ranges,
6634 multibuffer_context_lines(cx),
6635 cx,
6636 );
6637
6638 ranges_to_highlight.extend(ranges);
6639 }
6640 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6641 multibuffer
6642 })?;
6643
6644 workspace.update_in(cx, |workspace, window, cx| {
6645 let project = workspace.project().clone();
6646 let editor =
6647 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6648 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6649 editor.update(cx, |editor, cx| {
6650 editor.highlight_background::<Self>(
6651 &ranges_to_highlight,
6652 |_, theme| theme.colors().editor_highlighted_line_background,
6653 cx,
6654 );
6655 });
6656 })?;
6657
6658 Ok(())
6659 }
6660
6661 pub fn clear_code_action_providers(&mut self) {
6662 self.code_action_providers.clear();
6663 self.available_code_actions.take();
6664 }
6665
6666 pub fn add_code_action_provider(
6667 &mut self,
6668 provider: Rc<dyn CodeActionProvider>,
6669 window: &mut Window,
6670 cx: &mut Context<Self>,
6671 ) {
6672 if self
6673 .code_action_providers
6674 .iter()
6675 .any(|existing_provider| existing_provider.id() == provider.id())
6676 {
6677 return;
6678 }
6679
6680 self.code_action_providers.push(provider);
6681 self.refresh_code_actions(window, cx);
6682 }
6683
6684 pub fn remove_code_action_provider(
6685 &mut self,
6686 id: Arc<str>,
6687 window: &mut Window,
6688 cx: &mut Context<Self>,
6689 ) {
6690 self.code_action_providers
6691 .retain(|provider| provider.id() != id);
6692 self.refresh_code_actions(window, cx);
6693 }
6694
6695 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6696 !self.code_action_providers.is_empty()
6697 && EditorSettings::get_global(cx).toolbar.code_actions
6698 }
6699
6700 pub fn has_available_code_actions(&self) -> bool {
6701 self.available_code_actions
6702 .as_ref()
6703 .is_some_and(|(_, actions)| !actions.is_empty())
6704 }
6705
6706 fn render_inline_code_actions(
6707 &self,
6708 icon_size: ui::IconSize,
6709 display_row: DisplayRow,
6710 is_active: bool,
6711 cx: &mut Context<Self>,
6712 ) -> AnyElement {
6713 let show_tooltip = !self.context_menu_visible();
6714 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6715 .icon_size(icon_size)
6716 .shape(ui::IconButtonShape::Square)
6717 .icon_color(ui::Color::Hidden)
6718 .toggle_state(is_active)
6719 .when(show_tooltip, |this| {
6720 this.tooltip({
6721 let focus_handle = self.focus_handle.clone();
6722 move |_window, cx| {
6723 Tooltip::for_action_in(
6724 "Toggle Code Actions",
6725 &ToggleCodeActions {
6726 deployed_from: None,
6727 quick_launch: false,
6728 },
6729 &focus_handle,
6730 cx,
6731 )
6732 }
6733 })
6734 })
6735 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6736 window.focus(&editor.focus_handle(cx), cx);
6737 editor.toggle_code_actions(
6738 &crate::actions::ToggleCodeActions {
6739 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6740 display_row,
6741 )),
6742 quick_launch: false,
6743 },
6744 window,
6745 cx,
6746 );
6747 }))
6748 .into_any_element()
6749 }
6750
6751 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6752 &self.context_menu
6753 }
6754
6755 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6756 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6757 cx.background_executor()
6758 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6759 .await;
6760
6761 let (start_buffer, start, _, end, newest_selection) = this
6762 .update(cx, |this, cx| {
6763 let newest_selection = this.selections.newest_anchor().clone();
6764 if newest_selection.head().diff_base_anchor.is_some() {
6765 return None;
6766 }
6767 let display_snapshot = this.display_snapshot(cx);
6768 let newest_selection_adjusted =
6769 this.selections.newest_adjusted(&display_snapshot);
6770 let buffer = this.buffer.read(cx);
6771
6772 let (start_buffer, start) =
6773 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6774 let (end_buffer, end) =
6775 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6776
6777 Some((start_buffer, start, end_buffer, end, newest_selection))
6778 })?
6779 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6780 .context(
6781 "Expected selection to lie in a single buffer when refreshing code actions",
6782 )?;
6783 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6784 let providers = this.code_action_providers.clone();
6785 let tasks = this
6786 .code_action_providers
6787 .iter()
6788 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6789 .collect::<Vec<_>>();
6790 (providers, tasks)
6791 })?;
6792
6793 let mut actions = Vec::new();
6794 for (provider, provider_actions) in
6795 providers.into_iter().zip(future::join_all(tasks).await)
6796 {
6797 if let Some(provider_actions) = provider_actions.log_err() {
6798 actions.extend(provider_actions.into_iter().map(|action| {
6799 AvailableCodeAction {
6800 excerpt_id: newest_selection.start.excerpt_id,
6801 action,
6802 provider: provider.clone(),
6803 }
6804 }));
6805 }
6806 }
6807
6808 this.update(cx, |this, cx| {
6809 this.available_code_actions = if actions.is_empty() {
6810 None
6811 } else {
6812 Some((
6813 Location {
6814 buffer: start_buffer,
6815 range: start..end,
6816 },
6817 actions.into(),
6818 ))
6819 };
6820 cx.notify();
6821 })
6822 }));
6823 }
6824
6825 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6826 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6827 self.show_git_blame_inline = false;
6828
6829 self.show_git_blame_inline_delay_task =
6830 Some(cx.spawn_in(window, async move |this, cx| {
6831 cx.background_executor().timer(delay).await;
6832
6833 this.update(cx, |this, cx| {
6834 this.show_git_blame_inline = true;
6835 cx.notify();
6836 })
6837 .log_err();
6838 }));
6839 }
6840 }
6841
6842 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6843 let snapshot = self.snapshot(window, cx);
6844 let cursor = self
6845 .selections
6846 .newest::<Point>(&snapshot.display_snapshot)
6847 .head();
6848 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6849 else {
6850 return;
6851 };
6852
6853 if self.blame.is_none() {
6854 self.start_git_blame(true, window, cx);
6855 }
6856 let Some(blame) = self.blame.as_ref() else {
6857 return;
6858 };
6859
6860 let row_info = RowInfo {
6861 buffer_id: Some(buffer.remote_id()),
6862 buffer_row: Some(point.row),
6863 ..Default::default()
6864 };
6865 let Some((buffer, blame_entry)) = blame
6866 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6867 .flatten()
6868 else {
6869 return;
6870 };
6871
6872 let anchor = self.selections.newest_anchor().head();
6873 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6874 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6875 self.show_blame_popover(
6876 buffer,
6877 &blame_entry,
6878 position + last_bounds.origin,
6879 true,
6880 cx,
6881 );
6882 };
6883 }
6884
6885 fn show_blame_popover(
6886 &mut self,
6887 buffer: BufferId,
6888 blame_entry: &BlameEntry,
6889 position: gpui::Point<Pixels>,
6890 ignore_timeout: bool,
6891 cx: &mut Context<Self>,
6892 ) {
6893 if let Some(state) = &mut self.inline_blame_popover {
6894 state.hide_task.take();
6895 } else {
6896 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6897 let blame_entry = blame_entry.clone();
6898 let show_task = cx.spawn(async move |editor, cx| {
6899 if !ignore_timeout {
6900 cx.background_executor()
6901 .timer(std::time::Duration::from_millis(blame_popover_delay))
6902 .await;
6903 }
6904 editor
6905 .update(cx, |editor, cx| {
6906 editor.inline_blame_popover_show_task.take();
6907 let Some(blame) = editor.blame.as_ref() else {
6908 return;
6909 };
6910 let blame = blame.read(cx);
6911 let details = blame.details_for_entry(buffer, &blame_entry);
6912 let markdown = cx.new(|cx| {
6913 Markdown::new(
6914 details
6915 .as_ref()
6916 .map(|message| message.message.clone())
6917 .unwrap_or_default(),
6918 None,
6919 None,
6920 cx,
6921 )
6922 });
6923 editor.inline_blame_popover = Some(InlineBlamePopover {
6924 position,
6925 hide_task: None,
6926 popover_bounds: None,
6927 popover_state: InlineBlamePopoverState {
6928 scroll_handle: ScrollHandle::new(),
6929 commit_message: details,
6930 markdown,
6931 },
6932 keyboard_grace: ignore_timeout,
6933 });
6934 cx.notify();
6935 })
6936 .ok();
6937 });
6938 self.inline_blame_popover_show_task = Some(show_task);
6939 }
6940 }
6941
6942 pub fn has_mouse_context_menu(&self) -> bool {
6943 self.mouse_context_menu.is_some()
6944 }
6945
6946 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6947 self.inline_blame_popover_show_task.take();
6948 if let Some(state) = &mut self.inline_blame_popover {
6949 let hide_task = cx.spawn(async move |editor, cx| {
6950 if !ignore_timeout {
6951 cx.background_executor()
6952 .timer(std::time::Duration::from_millis(100))
6953 .await;
6954 }
6955 editor
6956 .update(cx, |editor, cx| {
6957 editor.inline_blame_popover.take();
6958 cx.notify();
6959 })
6960 .ok();
6961 });
6962 state.hide_task = Some(hide_task);
6963 true
6964 } else {
6965 false
6966 }
6967 }
6968
6969 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6970 if self.pending_rename.is_some() {
6971 return None;
6972 }
6973
6974 let provider = self.semantics_provider.clone()?;
6975 let buffer = self.buffer.read(cx);
6976 let newest_selection = self.selections.newest_anchor().clone();
6977 let cursor_position = newest_selection.head();
6978 let (cursor_buffer, cursor_buffer_position) =
6979 buffer.text_anchor_for_position(cursor_position, cx)?;
6980 let (tail_buffer, tail_buffer_position) =
6981 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6982 if cursor_buffer != tail_buffer {
6983 return None;
6984 }
6985
6986 let snapshot = cursor_buffer.read(cx).snapshot();
6987 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6988 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6989 if start_word_range != end_word_range {
6990 self.document_highlights_task.take();
6991 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6992 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6993 return None;
6994 }
6995
6996 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6997 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6998 cx.background_executor()
6999 .timer(Duration::from_millis(debounce))
7000 .await;
7001
7002 let highlights = if let Some(highlights) = cx
7003 .update(|cx| {
7004 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7005 })
7006 .ok()
7007 .flatten()
7008 {
7009 highlights.await.log_err()
7010 } else {
7011 None
7012 };
7013
7014 if let Some(highlights) = highlights {
7015 this.update(cx, |this, cx| {
7016 if this.pending_rename.is_some() {
7017 return;
7018 }
7019
7020 let buffer = this.buffer.read(cx);
7021 if buffer
7022 .text_anchor_for_position(cursor_position, cx)
7023 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7024 {
7025 return;
7026 }
7027
7028 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7029 let mut write_ranges = Vec::new();
7030 let mut read_ranges = Vec::new();
7031 for highlight in highlights {
7032 let buffer_id = cursor_buffer.read(cx).remote_id();
7033 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7034 {
7035 let start = highlight
7036 .range
7037 .start
7038 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7039 let end = highlight
7040 .range
7041 .end
7042 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7043 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7044 continue;
7045 }
7046
7047 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7048 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7049 write_ranges.push(range);
7050 } else {
7051 read_ranges.push(range);
7052 }
7053 }
7054 }
7055
7056 this.highlight_background::<DocumentHighlightRead>(
7057 &read_ranges,
7058 |_, theme| theme.colors().editor_document_highlight_read_background,
7059 cx,
7060 );
7061 this.highlight_background::<DocumentHighlightWrite>(
7062 &write_ranges,
7063 |_, theme| theme.colors().editor_document_highlight_write_background,
7064 cx,
7065 );
7066 cx.notify();
7067 })
7068 .log_err();
7069 }
7070 }));
7071 None
7072 }
7073
7074 fn prepare_highlight_query_from_selection(
7075 &mut self,
7076 window: &Window,
7077 cx: &mut Context<Editor>,
7078 ) -> Option<(String, Range<Anchor>)> {
7079 if matches!(self.mode, EditorMode::SingleLine) {
7080 return None;
7081 }
7082 if !EditorSettings::get_global(cx).selection_highlight {
7083 return None;
7084 }
7085 if self.selections.count() != 1 || self.selections.line_mode() {
7086 return None;
7087 }
7088 let snapshot = self.snapshot(window, cx);
7089 let selection = self.selections.newest::<Point>(&snapshot);
7090 // If the selection spans multiple rows OR it is empty
7091 if selection.start.row != selection.end.row
7092 || selection.start.column == selection.end.column
7093 {
7094 return None;
7095 }
7096 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7097 let query = snapshot
7098 .buffer_snapshot()
7099 .text_for_range(selection_anchor_range.clone())
7100 .collect::<String>();
7101 if query.trim().is_empty() {
7102 return None;
7103 }
7104 Some((query, selection_anchor_range))
7105 }
7106
7107 #[ztracing::instrument(skip_all)]
7108 fn update_selection_occurrence_highlights(
7109 &mut self,
7110 query_text: String,
7111 query_range: Range<Anchor>,
7112 multi_buffer_range_to_query: Range<Point>,
7113 use_debounce: bool,
7114 window: &mut Window,
7115 cx: &mut Context<Editor>,
7116 ) -> Task<()> {
7117 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7118 cx.spawn_in(window, async move |editor, cx| {
7119 if use_debounce {
7120 cx.background_executor()
7121 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7122 .await;
7123 }
7124 let match_task = cx.background_spawn(async move {
7125 let buffer_ranges = multi_buffer_snapshot
7126 .range_to_buffer_ranges(multi_buffer_range_to_query)
7127 .into_iter()
7128 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7129 let mut match_ranges = Vec::new();
7130 let Ok(regex) = project::search::SearchQuery::text(
7131 query_text.clone(),
7132 false,
7133 false,
7134 false,
7135 Default::default(),
7136 Default::default(),
7137 false,
7138 None,
7139 ) else {
7140 return Vec::default();
7141 };
7142 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7143 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7144 match_ranges.extend(
7145 regex
7146 .search(
7147 buffer_snapshot,
7148 Some(search_range.start.0..search_range.end.0),
7149 )
7150 .await
7151 .into_iter()
7152 .filter_map(|match_range| {
7153 let match_start = buffer_snapshot
7154 .anchor_after(search_range.start + match_range.start);
7155 let match_end = buffer_snapshot
7156 .anchor_before(search_range.start + match_range.end);
7157 let match_anchor_range =
7158 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7159 (match_anchor_range != query_range).then_some(match_anchor_range)
7160 }),
7161 );
7162 }
7163 match_ranges
7164 });
7165 let match_ranges = match_task.await;
7166 editor
7167 .update_in(cx, |editor, _, cx| {
7168 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7169 if !match_ranges.is_empty() {
7170 editor.highlight_background::<SelectedTextHighlight>(
7171 &match_ranges,
7172 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7173 cx,
7174 )
7175 }
7176 })
7177 .log_err();
7178 })
7179 }
7180
7181 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7182 struct NewlineFold;
7183 let type_id = std::any::TypeId::of::<NewlineFold>();
7184 if !self.mode.is_single_line() {
7185 return;
7186 }
7187 let snapshot = self.snapshot(window, cx);
7188 if snapshot.buffer_snapshot().max_point().row == 0 {
7189 return;
7190 }
7191 let task = cx.background_spawn(async move {
7192 let new_newlines = snapshot
7193 .buffer_chars_at(MultiBufferOffset(0))
7194 .filter_map(|(c, i)| {
7195 if c == '\n' {
7196 Some(
7197 snapshot.buffer_snapshot().anchor_after(i)
7198 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7199 )
7200 } else {
7201 None
7202 }
7203 })
7204 .collect::<Vec<_>>();
7205 let existing_newlines = snapshot
7206 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7207 .filter_map(|fold| {
7208 if fold.placeholder.type_tag == Some(type_id) {
7209 Some(fold.range.start..fold.range.end)
7210 } else {
7211 None
7212 }
7213 })
7214 .collect::<Vec<_>>();
7215
7216 (new_newlines, existing_newlines)
7217 });
7218 self.folding_newlines = cx.spawn(async move |this, cx| {
7219 let (new_newlines, existing_newlines) = task.await;
7220 if new_newlines == existing_newlines {
7221 return;
7222 }
7223 let placeholder = FoldPlaceholder {
7224 render: Arc::new(move |_, _, cx| {
7225 div()
7226 .bg(cx.theme().status().hint_background)
7227 .border_b_1()
7228 .size_full()
7229 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7230 .border_color(cx.theme().status().hint)
7231 .child("\\n")
7232 .into_any()
7233 }),
7234 constrain_width: false,
7235 merge_adjacent: false,
7236 type_tag: Some(type_id),
7237 };
7238 let creases = new_newlines
7239 .into_iter()
7240 .map(|range| Crease::simple(range, placeholder.clone()))
7241 .collect();
7242 this.update(cx, |this, cx| {
7243 this.display_map.update(cx, |display_map, cx| {
7244 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7245 display_map.fold(creases, cx);
7246 });
7247 })
7248 .ok();
7249 });
7250 }
7251
7252 #[ztracing::instrument(skip_all)]
7253 fn refresh_selected_text_highlights(
7254 &mut self,
7255 on_buffer_edit: bool,
7256 window: &mut Window,
7257 cx: &mut Context<Editor>,
7258 ) {
7259 let Some((query_text, query_range)) =
7260 self.prepare_highlight_query_from_selection(window, cx)
7261 else {
7262 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7263 self.quick_selection_highlight_task.take();
7264 self.debounced_selection_highlight_task.take();
7265 return;
7266 };
7267 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7268 if on_buffer_edit
7269 || self
7270 .quick_selection_highlight_task
7271 .as_ref()
7272 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7273 {
7274 let multi_buffer_visible_start = self
7275 .scroll_manager
7276 .anchor()
7277 .anchor
7278 .to_point(&multi_buffer_snapshot);
7279 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7280 multi_buffer_visible_start
7281 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7282 Bias::Left,
7283 );
7284 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7285 self.quick_selection_highlight_task = Some((
7286 query_range.clone(),
7287 self.update_selection_occurrence_highlights(
7288 query_text.clone(),
7289 query_range.clone(),
7290 multi_buffer_visible_range,
7291 false,
7292 window,
7293 cx,
7294 ),
7295 ));
7296 }
7297 if on_buffer_edit
7298 || self
7299 .debounced_selection_highlight_task
7300 .as_ref()
7301 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7302 {
7303 let multi_buffer_start = multi_buffer_snapshot
7304 .anchor_before(MultiBufferOffset(0))
7305 .to_point(&multi_buffer_snapshot);
7306 let multi_buffer_end = multi_buffer_snapshot
7307 .anchor_after(multi_buffer_snapshot.len())
7308 .to_point(&multi_buffer_snapshot);
7309 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7310 self.debounced_selection_highlight_task = Some((
7311 query_range.clone(),
7312 self.update_selection_occurrence_highlights(
7313 query_text,
7314 query_range,
7315 multi_buffer_full_range,
7316 true,
7317 window,
7318 cx,
7319 ),
7320 ));
7321 }
7322 }
7323
7324 pub fn refresh_edit_prediction(
7325 &mut self,
7326 debounce: bool,
7327 user_requested: bool,
7328 window: &mut Window,
7329 cx: &mut Context<Self>,
7330 ) -> Option<()> {
7331 if DisableAiSettings::get_global(cx).disable_ai {
7332 return None;
7333 }
7334
7335 let provider = self.edit_prediction_provider()?;
7336 let cursor = self.selections.newest_anchor().head();
7337 let (buffer, cursor_buffer_position) =
7338 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7339
7340 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7341 self.discard_edit_prediction(false, cx);
7342 return None;
7343 }
7344
7345 self.update_visible_edit_prediction(window, cx);
7346
7347 if !user_requested
7348 && (!self.should_show_edit_predictions()
7349 || !self.is_focused(window)
7350 || buffer.read(cx).is_empty())
7351 {
7352 self.discard_edit_prediction(false, cx);
7353 return None;
7354 }
7355
7356 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7357 Some(())
7358 }
7359
7360 fn show_edit_predictions_in_menu(&self) -> bool {
7361 match self.edit_prediction_settings {
7362 EditPredictionSettings::Disabled => false,
7363 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7364 }
7365 }
7366
7367 pub fn edit_predictions_enabled(&self) -> bool {
7368 match self.edit_prediction_settings {
7369 EditPredictionSettings::Disabled => false,
7370 EditPredictionSettings::Enabled { .. } => true,
7371 }
7372 }
7373
7374 fn edit_prediction_requires_modifier(&self) -> bool {
7375 match self.edit_prediction_settings {
7376 EditPredictionSettings::Disabled => false,
7377 EditPredictionSettings::Enabled {
7378 preview_requires_modifier,
7379 ..
7380 } => preview_requires_modifier,
7381 }
7382 }
7383
7384 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7385 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7386 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7387 self.discard_edit_prediction(false, cx);
7388 } else {
7389 let selection = self.selections.newest_anchor();
7390 let cursor = selection.head();
7391
7392 if let Some((buffer, cursor_buffer_position)) =
7393 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7394 {
7395 self.edit_prediction_settings =
7396 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7397 }
7398 }
7399 }
7400
7401 fn edit_prediction_settings_at_position(
7402 &self,
7403 buffer: &Entity<Buffer>,
7404 buffer_position: language::Anchor,
7405 cx: &App,
7406 ) -> EditPredictionSettings {
7407 if !self.mode.is_full()
7408 || !self.show_edit_predictions_override.unwrap_or(true)
7409 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7410 {
7411 return EditPredictionSettings::Disabled;
7412 }
7413
7414 let buffer = buffer.read(cx);
7415
7416 let file = buffer.file();
7417
7418 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7419 return EditPredictionSettings::Disabled;
7420 };
7421
7422 let by_provider = matches!(
7423 self.menu_edit_predictions_policy,
7424 MenuEditPredictionsPolicy::ByProvider
7425 );
7426
7427 let show_in_menu = by_provider
7428 && self
7429 .edit_prediction_provider
7430 .as_ref()
7431 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7432
7433 let preview_requires_modifier =
7434 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7435
7436 EditPredictionSettings::Enabled {
7437 show_in_menu,
7438 preview_requires_modifier,
7439 }
7440 }
7441
7442 fn should_show_edit_predictions(&self) -> bool {
7443 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7444 }
7445
7446 pub fn edit_prediction_preview_is_active(&self) -> bool {
7447 matches!(
7448 self.edit_prediction_preview,
7449 EditPredictionPreview::Active { .. }
7450 )
7451 }
7452
7453 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7454 let cursor = self.selections.newest_anchor().head();
7455 if let Some((buffer, cursor_position)) =
7456 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7457 {
7458 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7459 } else {
7460 false
7461 }
7462 }
7463
7464 pub fn supports_minimap(&self, cx: &App) -> bool {
7465 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7466 }
7467
7468 fn edit_predictions_enabled_in_buffer(
7469 &self,
7470 buffer: &Entity<Buffer>,
7471 buffer_position: language::Anchor,
7472 cx: &App,
7473 ) -> bool {
7474 maybe!({
7475 if self.read_only(cx) {
7476 return Some(false);
7477 }
7478 let provider = self.edit_prediction_provider()?;
7479 if !provider.is_enabled(buffer, buffer_position, cx) {
7480 return Some(false);
7481 }
7482 let buffer = buffer.read(cx);
7483 let Some(file) = buffer.file() else {
7484 return Some(true);
7485 };
7486 let settings = all_language_settings(Some(file), cx);
7487 Some(settings.edit_predictions_enabled_for_file(file, cx))
7488 })
7489 .unwrap_or(false)
7490 }
7491
7492 pub fn show_edit_prediction(
7493 &mut self,
7494 _: &ShowEditPrediction,
7495 window: &mut Window,
7496 cx: &mut Context<Self>,
7497 ) {
7498 if !self.has_active_edit_prediction() {
7499 self.refresh_edit_prediction(false, true, window, cx);
7500 return;
7501 }
7502
7503 self.update_visible_edit_prediction(window, cx);
7504 }
7505
7506 pub fn display_cursor_names(
7507 &mut self,
7508 _: &DisplayCursorNames,
7509 window: &mut Window,
7510 cx: &mut Context<Self>,
7511 ) {
7512 self.show_cursor_names(window, cx);
7513 }
7514
7515 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7516 self.show_cursor_names = true;
7517 cx.notify();
7518 cx.spawn_in(window, async move |this, cx| {
7519 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7520 this.update(cx, |this, cx| {
7521 this.show_cursor_names = false;
7522 cx.notify()
7523 })
7524 .ok()
7525 })
7526 .detach();
7527 }
7528
7529 pub fn accept_partial_edit_prediction(
7530 &mut self,
7531 granularity: EditPredictionGranularity,
7532 window: &mut Window,
7533 cx: &mut Context<Self>,
7534 ) {
7535 if self.show_edit_predictions_in_menu() {
7536 self.hide_context_menu(window, cx);
7537 }
7538
7539 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7540 return;
7541 };
7542
7543 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7544 return;
7545 }
7546
7547 match &active_edit_prediction.completion {
7548 EditPrediction::MoveWithin { target, .. } => {
7549 let target = *target;
7550
7551 if matches!(granularity, EditPredictionGranularity::Full) {
7552 if let Some(position_map) = &self.last_position_map {
7553 let target_row = target.to_display_point(&position_map.snapshot).row();
7554 let is_visible = position_map.visible_row_range.contains(&target_row);
7555
7556 if is_visible || !self.edit_prediction_requires_modifier() {
7557 self.unfold_ranges(&[target..target], true, false, cx);
7558 self.change_selections(
7559 SelectionEffects::scroll(Autoscroll::newest()),
7560 window,
7561 cx,
7562 |selections| {
7563 selections.select_anchor_ranges([target..target]);
7564 },
7565 );
7566 self.clear_row_highlights::<EditPredictionPreview>();
7567 self.edit_prediction_preview
7568 .set_previous_scroll_position(None);
7569 } else {
7570 // Highlight and request scroll
7571 self.edit_prediction_preview
7572 .set_previous_scroll_position(Some(
7573 position_map.snapshot.scroll_anchor,
7574 ));
7575 self.highlight_rows::<EditPredictionPreview>(
7576 target..target,
7577 cx.theme().colors().editor_highlighted_line_background,
7578 RowHighlightOptions {
7579 autoscroll: true,
7580 ..Default::default()
7581 },
7582 cx,
7583 );
7584 self.request_autoscroll(Autoscroll::fit(), cx);
7585 }
7586 }
7587 } else {
7588 self.change_selections(
7589 SelectionEffects::scroll(Autoscroll::newest()),
7590 window,
7591 cx,
7592 |selections| {
7593 selections.select_anchor_ranges([target..target]);
7594 },
7595 );
7596 }
7597 }
7598 EditPrediction::MoveOutside { snapshot, target } => {
7599 if let Some(workspace) = self.workspace() {
7600 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7601 .detach_and_log_err(cx);
7602 }
7603 }
7604 EditPrediction::Edit { edits, .. } => {
7605 self.report_edit_prediction_event(
7606 active_edit_prediction.completion_id.clone(),
7607 true,
7608 cx,
7609 );
7610
7611 match granularity {
7612 EditPredictionGranularity::Full => {
7613 if let Some(provider) = self.edit_prediction_provider() {
7614 provider.accept(cx);
7615 }
7616
7617 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7618 let snapshot = self.buffer.read(cx).snapshot(cx);
7619 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7620
7621 self.buffer.update(cx, |buffer, cx| {
7622 buffer.edit(edits.iter().cloned(), None, cx)
7623 });
7624
7625 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7626 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7627 });
7628
7629 let selections = self.selections.disjoint_anchors_arc();
7630 if let Some(transaction_id_now) =
7631 self.buffer.read(cx).last_transaction_id(cx)
7632 {
7633 if transaction_id_prev != Some(transaction_id_now) {
7634 self.selection_history
7635 .insert_transaction(transaction_id_now, selections);
7636 }
7637 }
7638
7639 self.update_visible_edit_prediction(window, cx);
7640 if self.active_edit_prediction.is_none() {
7641 self.refresh_edit_prediction(true, true, window, cx);
7642 }
7643 cx.notify();
7644 }
7645 _ => {
7646 let snapshot = self.buffer.read(cx).snapshot(cx);
7647 let cursor_offset = self
7648 .selections
7649 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7650 .head();
7651
7652 let insertion = edits.iter().find_map(|(range, text)| {
7653 let range = range.to_offset(&snapshot);
7654 if range.is_empty() && range.start == cursor_offset {
7655 Some(text)
7656 } else {
7657 None
7658 }
7659 });
7660
7661 if let Some(text) = insertion {
7662 let text_to_insert = match granularity {
7663 EditPredictionGranularity::Word => {
7664 let mut partial = text
7665 .chars()
7666 .by_ref()
7667 .take_while(|c| c.is_alphabetic())
7668 .collect::<String>();
7669 if partial.is_empty() {
7670 partial = text
7671 .chars()
7672 .by_ref()
7673 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7674 .collect::<String>();
7675 }
7676 partial
7677 }
7678 EditPredictionGranularity::Line => {
7679 if let Some(line) = text.split_inclusive('\n').next() {
7680 line.to_string()
7681 } else {
7682 text.to_string()
7683 }
7684 }
7685 EditPredictionGranularity::Full => unreachable!(),
7686 };
7687
7688 cx.emit(EditorEvent::InputHandled {
7689 utf16_range_to_replace: None,
7690 text: text_to_insert.clone().into(),
7691 });
7692
7693 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7694 self.refresh_edit_prediction(true, true, window, cx);
7695 cx.notify();
7696 } else {
7697 self.accept_partial_edit_prediction(
7698 EditPredictionGranularity::Full,
7699 window,
7700 cx,
7701 );
7702 }
7703 }
7704 }
7705 }
7706 }
7707
7708 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7709 }
7710
7711 pub fn accept_next_word_edit_prediction(
7712 &mut self,
7713 _: &AcceptNextWordEditPrediction,
7714 window: &mut Window,
7715 cx: &mut Context<Self>,
7716 ) {
7717 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7718 }
7719
7720 pub fn accept_next_line_edit_prediction(
7721 &mut self,
7722 _: &AcceptNextLineEditPrediction,
7723 window: &mut Window,
7724 cx: &mut Context<Self>,
7725 ) {
7726 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7727 }
7728
7729 pub fn accept_edit_prediction(
7730 &mut self,
7731 _: &AcceptEditPrediction,
7732 window: &mut Window,
7733 cx: &mut Context<Self>,
7734 ) {
7735 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7736 }
7737
7738 fn discard_edit_prediction(
7739 &mut self,
7740 should_report_edit_prediction_event: bool,
7741 cx: &mut Context<Self>,
7742 ) -> bool {
7743 if should_report_edit_prediction_event {
7744 let completion_id = self
7745 .active_edit_prediction
7746 .as_ref()
7747 .and_then(|active_completion| active_completion.completion_id.clone());
7748
7749 self.report_edit_prediction_event(completion_id, false, cx);
7750 }
7751
7752 if let Some(provider) = self.edit_prediction_provider() {
7753 provider.discard(cx);
7754 }
7755
7756 self.take_active_edit_prediction(cx)
7757 }
7758
7759 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7760 let Some(provider) = self.edit_prediction_provider() else {
7761 return;
7762 };
7763
7764 let Some((_, buffer, _)) = self
7765 .buffer
7766 .read(cx)
7767 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7768 else {
7769 return;
7770 };
7771
7772 let extension = buffer
7773 .read(cx)
7774 .file()
7775 .and_then(|file| Some(file.path().extension()?.to_string()));
7776
7777 let event_type = match accepted {
7778 true => "Edit Prediction Accepted",
7779 false => "Edit Prediction Discarded",
7780 };
7781 telemetry::event!(
7782 event_type,
7783 provider = provider.name(),
7784 prediction_id = id,
7785 suggestion_accepted = accepted,
7786 file_extension = extension,
7787 );
7788 }
7789
7790 fn open_editor_at_anchor(
7791 snapshot: &language::BufferSnapshot,
7792 target: language::Anchor,
7793 workspace: &Entity<Workspace>,
7794 window: &mut Window,
7795 cx: &mut App,
7796 ) -> Task<Result<()>> {
7797 workspace.update(cx, |workspace, cx| {
7798 let path = snapshot.file().map(|file| file.full_path(cx));
7799 let Some(path) =
7800 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7801 else {
7802 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7803 };
7804 let target = text::ToPoint::to_point(&target, snapshot);
7805 let item = workspace.open_path(path, None, true, window, cx);
7806 window.spawn(cx, async move |cx| {
7807 let Some(editor) = item.await?.downcast::<Editor>() else {
7808 return Ok(());
7809 };
7810 editor
7811 .update_in(cx, |editor, window, cx| {
7812 editor.go_to_singleton_buffer_point(target, window, cx);
7813 })
7814 .ok();
7815 anyhow::Ok(())
7816 })
7817 })
7818 }
7819
7820 pub fn has_active_edit_prediction(&self) -> bool {
7821 self.active_edit_prediction.is_some()
7822 }
7823
7824 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7825 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7826 return false;
7827 };
7828
7829 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7830 self.clear_highlights::<EditPredictionHighlight>(cx);
7831 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7832 true
7833 }
7834
7835 /// Returns true when we're displaying the edit prediction popover below the cursor
7836 /// like we are not previewing and the LSP autocomplete menu is visible
7837 /// or we are in `when_holding_modifier` mode.
7838 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7839 if self.edit_prediction_preview_is_active()
7840 || !self.show_edit_predictions_in_menu()
7841 || !self.edit_predictions_enabled()
7842 {
7843 return false;
7844 }
7845
7846 if self.has_visible_completions_menu() {
7847 return true;
7848 }
7849
7850 has_completion && self.edit_prediction_requires_modifier()
7851 }
7852
7853 fn handle_modifiers_changed(
7854 &mut self,
7855 modifiers: Modifiers,
7856 position_map: &PositionMap,
7857 window: &mut Window,
7858 cx: &mut Context<Self>,
7859 ) {
7860 // Ensure that the edit prediction preview is updated, even when not
7861 // enabled, if there's an active edit prediction preview.
7862 if self.show_edit_predictions_in_menu()
7863 || matches!(
7864 self.edit_prediction_preview,
7865 EditPredictionPreview::Active { .. }
7866 )
7867 {
7868 self.update_edit_prediction_preview(&modifiers, window, cx);
7869 }
7870
7871 self.update_selection_mode(&modifiers, position_map, window, cx);
7872
7873 let mouse_position = window.mouse_position();
7874 if !position_map.text_hitbox.is_hovered(window) {
7875 return;
7876 }
7877
7878 self.update_hovered_link(
7879 position_map.point_for_position(mouse_position),
7880 &position_map.snapshot,
7881 modifiers,
7882 window,
7883 cx,
7884 )
7885 }
7886
7887 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7888 match EditorSettings::get_global(cx).multi_cursor_modifier {
7889 MultiCursorModifier::Alt => modifiers.secondary(),
7890 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7891 }
7892 }
7893
7894 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7895 match EditorSettings::get_global(cx).multi_cursor_modifier {
7896 MultiCursorModifier::Alt => modifiers.alt,
7897 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7898 }
7899 }
7900
7901 fn columnar_selection_mode(
7902 modifiers: &Modifiers,
7903 cx: &mut Context<Self>,
7904 ) -> Option<ColumnarMode> {
7905 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7906 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7907 Some(ColumnarMode::FromMouse)
7908 } else if Self::is_alt_pressed(modifiers, cx) {
7909 Some(ColumnarMode::FromSelection)
7910 } else {
7911 None
7912 }
7913 } else {
7914 None
7915 }
7916 }
7917
7918 fn update_selection_mode(
7919 &mut self,
7920 modifiers: &Modifiers,
7921 position_map: &PositionMap,
7922 window: &mut Window,
7923 cx: &mut Context<Self>,
7924 ) {
7925 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7926 return;
7927 };
7928 if self.selections.pending_anchor().is_none() {
7929 return;
7930 }
7931
7932 let mouse_position = window.mouse_position();
7933 let point_for_position = position_map.point_for_position(mouse_position);
7934 let position = point_for_position.previous_valid;
7935
7936 self.select(
7937 SelectPhase::BeginColumnar {
7938 position,
7939 reset: false,
7940 mode,
7941 goal_column: point_for_position.exact_unclipped.column(),
7942 },
7943 window,
7944 cx,
7945 );
7946 }
7947
7948 fn update_edit_prediction_preview(
7949 &mut self,
7950 modifiers: &Modifiers,
7951 window: &mut Window,
7952 cx: &mut Context<Self>,
7953 ) {
7954 let mut modifiers_held = false;
7955
7956 // Check bindings for all granularities.
7957 // If the user holds the key for Word, Line, or Full, we want to show the preview.
7958 let granularities = [
7959 EditPredictionGranularity::Full,
7960 EditPredictionGranularity::Line,
7961 EditPredictionGranularity::Word,
7962 ];
7963
7964 for granularity in granularities {
7965 if let Some(keystroke) = self
7966 .accept_edit_prediction_keybind(granularity, window, cx)
7967 .keystroke()
7968 {
7969 modifiers_held = modifiers_held
7970 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
7971 }
7972 }
7973
7974 if modifiers_held {
7975 if matches!(
7976 self.edit_prediction_preview,
7977 EditPredictionPreview::Inactive { .. }
7978 ) {
7979 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7980 provider.provider.did_show(cx)
7981 }
7982
7983 self.edit_prediction_preview = EditPredictionPreview::Active {
7984 previous_scroll_position: None,
7985 since: Instant::now(),
7986 };
7987
7988 self.update_visible_edit_prediction(window, cx);
7989 cx.notify();
7990 }
7991 } else if let EditPredictionPreview::Active {
7992 previous_scroll_position,
7993 since,
7994 } = self.edit_prediction_preview
7995 {
7996 if let (Some(previous_scroll_position), Some(position_map)) =
7997 (previous_scroll_position, self.last_position_map.as_ref())
7998 {
7999 self.set_scroll_position(
8000 previous_scroll_position
8001 .scroll_position(&position_map.snapshot.display_snapshot),
8002 window,
8003 cx,
8004 );
8005 }
8006
8007 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8008 released_too_fast: since.elapsed() < Duration::from_millis(200),
8009 };
8010 self.clear_row_highlights::<EditPredictionPreview>();
8011 self.update_visible_edit_prediction(window, cx);
8012 cx.notify();
8013 }
8014 }
8015
8016 fn update_visible_edit_prediction(
8017 &mut self,
8018 _window: &mut Window,
8019 cx: &mut Context<Self>,
8020 ) -> Option<()> {
8021 if DisableAiSettings::get_global(cx).disable_ai {
8022 return None;
8023 }
8024
8025 if self.ime_transaction.is_some() {
8026 self.discard_edit_prediction(false, cx);
8027 return None;
8028 }
8029
8030 let selection = self.selections.newest_anchor();
8031 let cursor = selection.head();
8032 let multibuffer = self.buffer.read(cx).snapshot(cx);
8033 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8034 let excerpt_id = cursor.excerpt_id;
8035
8036 let show_in_menu = self.show_edit_predictions_in_menu();
8037 let completions_menu_has_precedence = !show_in_menu
8038 && (self.context_menu.borrow().is_some()
8039 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8040
8041 if completions_menu_has_precedence
8042 || !offset_selection.is_empty()
8043 || self
8044 .active_edit_prediction
8045 .as_ref()
8046 .is_some_and(|completion| {
8047 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8048 return false;
8049 };
8050 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8051 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8052 !invalidation_range.contains(&offset_selection.head())
8053 })
8054 {
8055 self.discard_edit_prediction(false, cx);
8056 return None;
8057 }
8058
8059 self.take_active_edit_prediction(cx);
8060 let Some(provider) = self.edit_prediction_provider() else {
8061 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8062 return None;
8063 };
8064
8065 let (buffer, cursor_buffer_position) =
8066 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8067
8068 self.edit_prediction_settings =
8069 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8070
8071 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8072
8073 if self.edit_prediction_indent_conflict {
8074 let cursor_point = cursor.to_point(&multibuffer);
8075 let mut suggested_indent = None;
8076 multibuffer.suggested_indents_callback(
8077 cursor_point.row..cursor_point.row + 1,
8078 |_, indent| {
8079 suggested_indent = Some(indent);
8080 ControlFlow::Break(())
8081 },
8082 cx,
8083 );
8084
8085 if let Some(indent) = suggested_indent
8086 && indent.len == cursor_point.column
8087 {
8088 self.edit_prediction_indent_conflict = false;
8089 }
8090 }
8091
8092 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8093
8094 let (completion_id, edits, edit_preview) = match edit_prediction {
8095 edit_prediction_types::EditPrediction::Local {
8096 id,
8097 edits,
8098 edit_preview,
8099 } => (id, edits, edit_preview),
8100 edit_prediction_types::EditPrediction::Jump {
8101 id,
8102 snapshot,
8103 target,
8104 } => {
8105 self.stale_edit_prediction_in_menu = None;
8106 self.active_edit_prediction = Some(EditPredictionState {
8107 inlay_ids: vec![],
8108 completion: EditPrediction::MoveOutside { snapshot, target },
8109 completion_id: id,
8110 invalidation_range: None,
8111 });
8112 cx.notify();
8113 return Some(());
8114 }
8115 };
8116
8117 let edits = edits
8118 .into_iter()
8119 .flat_map(|(range, new_text)| {
8120 Some((
8121 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8122 new_text,
8123 ))
8124 })
8125 .collect::<Vec<_>>();
8126 if edits.is_empty() {
8127 return None;
8128 }
8129
8130 let first_edit_start = edits.first().unwrap().0.start;
8131 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8132 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8133
8134 let last_edit_end = edits.last().unwrap().0.end;
8135 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8136 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8137
8138 let cursor_row = cursor.to_point(&multibuffer).row;
8139
8140 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8141
8142 let mut inlay_ids = Vec::new();
8143 let invalidation_row_range;
8144 let move_invalidation_row_range = if cursor_row < edit_start_row {
8145 Some(cursor_row..edit_end_row)
8146 } else if cursor_row > edit_end_row {
8147 Some(edit_start_row..cursor_row)
8148 } else {
8149 None
8150 };
8151 let supports_jump = self
8152 .edit_prediction_provider
8153 .as_ref()
8154 .map(|provider| provider.provider.supports_jump_to_edit())
8155 .unwrap_or(true);
8156
8157 let is_move = supports_jump
8158 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8159 let completion = if is_move {
8160 invalidation_row_range =
8161 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8162 let target = first_edit_start;
8163 EditPrediction::MoveWithin { target, snapshot }
8164 } else {
8165 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8166 && !self.edit_predictions_hidden_for_vim_mode;
8167
8168 if show_completions_in_buffer {
8169 if let Some(provider) = &self.edit_prediction_provider {
8170 provider.provider.did_show(cx);
8171 }
8172 if edits
8173 .iter()
8174 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8175 {
8176 let mut inlays = Vec::new();
8177 for (range, new_text) in &edits {
8178 let inlay = Inlay::edit_prediction(
8179 post_inc(&mut self.next_inlay_id),
8180 range.start,
8181 new_text.as_ref(),
8182 );
8183 inlay_ids.push(inlay.id);
8184 inlays.push(inlay);
8185 }
8186
8187 self.splice_inlays(&[], inlays, cx);
8188 } else {
8189 let background_color = cx.theme().status().deleted_background;
8190 self.highlight_text::<EditPredictionHighlight>(
8191 edits.iter().map(|(range, _)| range.clone()).collect(),
8192 HighlightStyle {
8193 background_color: Some(background_color),
8194 ..Default::default()
8195 },
8196 cx,
8197 );
8198 }
8199 }
8200
8201 invalidation_row_range = edit_start_row..edit_end_row;
8202
8203 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8204 if provider.show_tab_accept_marker() {
8205 EditDisplayMode::TabAccept
8206 } else {
8207 EditDisplayMode::Inline
8208 }
8209 } else {
8210 EditDisplayMode::DiffPopover
8211 };
8212
8213 EditPrediction::Edit {
8214 edits,
8215 edit_preview,
8216 display_mode,
8217 snapshot,
8218 }
8219 };
8220
8221 let invalidation_range = multibuffer
8222 .anchor_before(Point::new(invalidation_row_range.start, 0))
8223 ..multibuffer.anchor_after(Point::new(
8224 invalidation_row_range.end,
8225 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8226 ));
8227
8228 self.stale_edit_prediction_in_menu = None;
8229 self.active_edit_prediction = Some(EditPredictionState {
8230 inlay_ids,
8231 completion,
8232 completion_id,
8233 invalidation_range: Some(invalidation_range),
8234 });
8235
8236 cx.notify();
8237
8238 Some(())
8239 }
8240
8241 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8242 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8243 }
8244
8245 fn clear_tasks(&mut self) {
8246 self.tasks.clear()
8247 }
8248
8249 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8250 if self.tasks.insert(key, value).is_some() {
8251 // This case should hopefully be rare, but just in case...
8252 log::error!(
8253 "multiple different run targets found on a single line, only the last target will be rendered"
8254 )
8255 }
8256 }
8257
8258 /// Get all display points of breakpoints that will be rendered within editor
8259 ///
8260 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8261 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8262 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8263 fn active_breakpoints(
8264 &self,
8265 range: Range<DisplayRow>,
8266 window: &mut Window,
8267 cx: &mut Context<Self>,
8268 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8269 let mut breakpoint_display_points = HashMap::default();
8270
8271 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8272 return breakpoint_display_points;
8273 };
8274
8275 let snapshot = self.snapshot(window, cx);
8276
8277 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8278 let Some(project) = self.project() else {
8279 return breakpoint_display_points;
8280 };
8281
8282 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8283 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8284
8285 for (buffer_snapshot, range, excerpt_id) in
8286 multi_buffer_snapshot.range_to_buffer_ranges(range)
8287 {
8288 let Some(buffer) = project
8289 .read(cx)
8290 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8291 else {
8292 continue;
8293 };
8294 let breakpoints = breakpoint_store.read(cx).breakpoints(
8295 &buffer,
8296 Some(
8297 buffer_snapshot.anchor_before(range.start)
8298 ..buffer_snapshot.anchor_after(range.end),
8299 ),
8300 buffer_snapshot,
8301 cx,
8302 );
8303 for (breakpoint, state) in breakpoints {
8304 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8305 let position = multi_buffer_anchor
8306 .to_point(&multi_buffer_snapshot)
8307 .to_display_point(&snapshot);
8308
8309 breakpoint_display_points.insert(
8310 position.row(),
8311 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8312 );
8313 }
8314 }
8315
8316 breakpoint_display_points
8317 }
8318
8319 fn breakpoint_context_menu(
8320 &self,
8321 anchor: Anchor,
8322 window: &mut Window,
8323 cx: &mut Context<Self>,
8324 ) -> Entity<ui::ContextMenu> {
8325 let weak_editor = cx.weak_entity();
8326 let focus_handle = self.focus_handle(cx);
8327
8328 let row = self
8329 .buffer
8330 .read(cx)
8331 .snapshot(cx)
8332 .summary_for_anchor::<Point>(&anchor)
8333 .row;
8334
8335 let breakpoint = self
8336 .breakpoint_at_row(row, window, cx)
8337 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8338
8339 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8340 "Edit Log Breakpoint"
8341 } else {
8342 "Set Log Breakpoint"
8343 };
8344
8345 let condition_breakpoint_msg = if breakpoint
8346 .as_ref()
8347 .is_some_and(|bp| bp.1.condition.is_some())
8348 {
8349 "Edit Condition Breakpoint"
8350 } else {
8351 "Set Condition Breakpoint"
8352 };
8353
8354 let hit_condition_breakpoint_msg = if breakpoint
8355 .as_ref()
8356 .is_some_and(|bp| bp.1.hit_condition.is_some())
8357 {
8358 "Edit Hit Condition Breakpoint"
8359 } else {
8360 "Set Hit Condition Breakpoint"
8361 };
8362
8363 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8364 "Unset Breakpoint"
8365 } else {
8366 "Set Breakpoint"
8367 };
8368
8369 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8370
8371 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8372 BreakpointState::Enabled => Some("Disable"),
8373 BreakpointState::Disabled => Some("Enable"),
8374 });
8375
8376 let (anchor, breakpoint) =
8377 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8378
8379 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8380 menu.on_blur_subscription(Subscription::new(|| {}))
8381 .context(focus_handle)
8382 .when(run_to_cursor, |this| {
8383 let weak_editor = weak_editor.clone();
8384 this.entry("Run to cursor", None, move |window, cx| {
8385 weak_editor
8386 .update(cx, |editor, cx| {
8387 editor.change_selections(
8388 SelectionEffects::no_scroll(),
8389 window,
8390 cx,
8391 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8392 );
8393 })
8394 .ok();
8395
8396 window.dispatch_action(Box::new(RunToCursor), cx);
8397 })
8398 .separator()
8399 })
8400 .when_some(toggle_state_msg, |this, msg| {
8401 this.entry(msg, None, {
8402 let weak_editor = weak_editor.clone();
8403 let breakpoint = breakpoint.clone();
8404 move |_window, cx| {
8405 weak_editor
8406 .update(cx, |this, cx| {
8407 this.edit_breakpoint_at_anchor(
8408 anchor,
8409 breakpoint.as_ref().clone(),
8410 BreakpointEditAction::InvertState,
8411 cx,
8412 );
8413 })
8414 .log_err();
8415 }
8416 })
8417 })
8418 .entry(set_breakpoint_msg, None, {
8419 let weak_editor = weak_editor.clone();
8420 let breakpoint = breakpoint.clone();
8421 move |_window, cx| {
8422 weak_editor
8423 .update(cx, |this, cx| {
8424 this.edit_breakpoint_at_anchor(
8425 anchor,
8426 breakpoint.as_ref().clone(),
8427 BreakpointEditAction::Toggle,
8428 cx,
8429 );
8430 })
8431 .log_err();
8432 }
8433 })
8434 .entry(log_breakpoint_msg, None, {
8435 let breakpoint = breakpoint.clone();
8436 let weak_editor = weak_editor.clone();
8437 move |window, cx| {
8438 weak_editor
8439 .update(cx, |this, cx| {
8440 this.add_edit_breakpoint_block(
8441 anchor,
8442 breakpoint.as_ref(),
8443 BreakpointPromptEditAction::Log,
8444 window,
8445 cx,
8446 );
8447 })
8448 .log_err();
8449 }
8450 })
8451 .entry(condition_breakpoint_msg, None, {
8452 let breakpoint = breakpoint.clone();
8453 let weak_editor = weak_editor.clone();
8454 move |window, cx| {
8455 weak_editor
8456 .update(cx, |this, cx| {
8457 this.add_edit_breakpoint_block(
8458 anchor,
8459 breakpoint.as_ref(),
8460 BreakpointPromptEditAction::Condition,
8461 window,
8462 cx,
8463 );
8464 })
8465 .log_err();
8466 }
8467 })
8468 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8469 weak_editor
8470 .update(cx, |this, cx| {
8471 this.add_edit_breakpoint_block(
8472 anchor,
8473 breakpoint.as_ref(),
8474 BreakpointPromptEditAction::HitCondition,
8475 window,
8476 cx,
8477 );
8478 })
8479 .log_err();
8480 })
8481 })
8482 }
8483
8484 fn render_breakpoint(
8485 &self,
8486 position: Anchor,
8487 row: DisplayRow,
8488 breakpoint: &Breakpoint,
8489 state: Option<BreakpointSessionState>,
8490 cx: &mut Context<Self>,
8491 ) -> IconButton {
8492 let is_rejected = state.is_some_and(|s| !s.verified);
8493 // Is it a breakpoint that shows up when hovering over gutter?
8494 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8495 (false, false),
8496 |PhantomBreakpointIndicator {
8497 is_active,
8498 display_row,
8499 collides_with_existing_breakpoint,
8500 }| {
8501 (
8502 is_active && display_row == row,
8503 collides_with_existing_breakpoint,
8504 )
8505 },
8506 );
8507
8508 let (color, icon) = {
8509 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8510 (false, false) => ui::IconName::DebugBreakpoint,
8511 (true, false) => ui::IconName::DebugLogBreakpoint,
8512 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8513 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8514 };
8515
8516 let color = cx.theme().colors();
8517
8518 let color = if is_phantom {
8519 if collides_with_existing {
8520 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8521 } else {
8522 Color::Hint
8523 }
8524 } else if is_rejected {
8525 Color::Disabled
8526 } else {
8527 Color::Debugger
8528 };
8529
8530 (color, icon)
8531 };
8532
8533 let breakpoint = Arc::from(breakpoint.clone());
8534
8535 let alt_as_text = gpui::Keystroke {
8536 modifiers: Modifiers::secondary_key(),
8537 ..Default::default()
8538 };
8539 let primary_action_text = if breakpoint.is_disabled() {
8540 "Enable breakpoint"
8541 } else if is_phantom && !collides_with_existing {
8542 "Set breakpoint"
8543 } else {
8544 "Unset breakpoint"
8545 };
8546 let focus_handle = self.focus_handle.clone();
8547
8548 let meta = if is_rejected {
8549 SharedString::from("No executable code is associated with this line.")
8550 } else if collides_with_existing && !breakpoint.is_disabled() {
8551 SharedString::from(format!(
8552 "{alt_as_text}-click to disable,\nright-click for more options."
8553 ))
8554 } else {
8555 SharedString::from("Right-click for more options.")
8556 };
8557 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8558 .icon_size(IconSize::XSmall)
8559 .size(ui::ButtonSize::None)
8560 .when(is_rejected, |this| {
8561 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8562 })
8563 .icon_color(color)
8564 .style(ButtonStyle::Transparent)
8565 .on_click(cx.listener({
8566 move |editor, event: &ClickEvent, window, cx| {
8567 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8568 BreakpointEditAction::InvertState
8569 } else {
8570 BreakpointEditAction::Toggle
8571 };
8572
8573 window.focus(&editor.focus_handle(cx), cx);
8574 editor.edit_breakpoint_at_anchor(
8575 position,
8576 breakpoint.as_ref().clone(),
8577 edit_action,
8578 cx,
8579 );
8580 }
8581 }))
8582 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8583 editor.set_breakpoint_context_menu(
8584 row,
8585 Some(position),
8586 event.position(),
8587 window,
8588 cx,
8589 );
8590 }))
8591 .tooltip(move |_window, cx| {
8592 Tooltip::with_meta_in(
8593 primary_action_text,
8594 Some(&ToggleBreakpoint),
8595 meta.clone(),
8596 &focus_handle,
8597 cx,
8598 )
8599 })
8600 }
8601
8602 fn build_tasks_context(
8603 project: &Entity<Project>,
8604 buffer: &Entity<Buffer>,
8605 buffer_row: u32,
8606 tasks: &Arc<RunnableTasks>,
8607 cx: &mut Context<Self>,
8608 ) -> Task<Option<task::TaskContext>> {
8609 let position = Point::new(buffer_row, tasks.column);
8610 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8611 let location = Location {
8612 buffer: buffer.clone(),
8613 range: range_start..range_start,
8614 };
8615 // Fill in the environmental variables from the tree-sitter captures
8616 let mut captured_task_variables = TaskVariables::default();
8617 for (capture_name, value) in tasks.extra_variables.clone() {
8618 captured_task_variables.insert(
8619 task::VariableName::Custom(capture_name.into()),
8620 value.clone(),
8621 );
8622 }
8623 project.update(cx, |project, cx| {
8624 project.task_store().update(cx, |task_store, cx| {
8625 task_store.task_context_for_location(captured_task_variables, location, cx)
8626 })
8627 })
8628 }
8629
8630 pub fn spawn_nearest_task(
8631 &mut self,
8632 action: &SpawnNearestTask,
8633 window: &mut Window,
8634 cx: &mut Context<Self>,
8635 ) {
8636 let Some((workspace, _)) = self.workspace.clone() else {
8637 return;
8638 };
8639 let Some(project) = self.project.clone() else {
8640 return;
8641 };
8642
8643 // Try to find a closest, enclosing node using tree-sitter that has a task
8644 let Some((buffer, buffer_row, tasks)) = self
8645 .find_enclosing_node_task(cx)
8646 // Or find the task that's closest in row-distance.
8647 .or_else(|| self.find_closest_task(cx))
8648 else {
8649 return;
8650 };
8651
8652 let reveal_strategy = action.reveal;
8653 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8654 cx.spawn_in(window, async move |_, cx| {
8655 let context = task_context.await?;
8656 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8657
8658 let resolved = &mut resolved_task.resolved;
8659 resolved.reveal = reveal_strategy;
8660
8661 workspace
8662 .update_in(cx, |workspace, window, cx| {
8663 workspace.schedule_resolved_task(
8664 task_source_kind,
8665 resolved_task,
8666 false,
8667 window,
8668 cx,
8669 );
8670 })
8671 .ok()
8672 })
8673 .detach();
8674 }
8675
8676 fn find_closest_task(
8677 &mut self,
8678 cx: &mut Context<Self>,
8679 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8680 let cursor_row = self
8681 .selections
8682 .newest_adjusted(&self.display_snapshot(cx))
8683 .head()
8684 .row;
8685
8686 let ((buffer_id, row), tasks) = self
8687 .tasks
8688 .iter()
8689 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8690
8691 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8692 let tasks = Arc::new(tasks.to_owned());
8693 Some((buffer, *row, tasks))
8694 }
8695
8696 fn find_enclosing_node_task(
8697 &mut self,
8698 cx: &mut Context<Self>,
8699 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8700 let snapshot = self.buffer.read(cx).snapshot(cx);
8701 let offset = self
8702 .selections
8703 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8704 .head();
8705 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8706 let offset = excerpt.map_offset_to_buffer(offset);
8707 let buffer_id = excerpt.buffer().remote_id();
8708
8709 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8710 let mut cursor = layer.node().walk();
8711
8712 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8713 if cursor.node().end_byte() == offset.0 {
8714 cursor.goto_next_sibling();
8715 }
8716 }
8717
8718 // Ascend to the smallest ancestor that contains the range and has a task.
8719 loop {
8720 let node = cursor.node();
8721 let node_range = node.byte_range();
8722 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8723
8724 // Check if this node contains our offset
8725 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8726 // If it contains offset, check for task
8727 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8728 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8729 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8730 }
8731 }
8732
8733 if !cursor.goto_parent() {
8734 break;
8735 }
8736 }
8737 None
8738 }
8739
8740 fn render_run_indicator(
8741 &self,
8742 _style: &EditorStyle,
8743 is_active: bool,
8744 row: DisplayRow,
8745 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8746 cx: &mut Context<Self>,
8747 ) -> IconButton {
8748 let color = Color::Muted;
8749 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8750
8751 IconButton::new(
8752 ("run_indicator", row.0 as usize),
8753 ui::IconName::PlayOutlined,
8754 )
8755 .shape(ui::IconButtonShape::Square)
8756 .icon_size(IconSize::XSmall)
8757 .icon_color(color)
8758 .toggle_state(is_active)
8759 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8760 let quick_launch = match e {
8761 ClickEvent::Keyboard(_) => true,
8762 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8763 };
8764
8765 window.focus(&editor.focus_handle(cx), cx);
8766 editor.toggle_code_actions(
8767 &ToggleCodeActions {
8768 deployed_from: Some(CodeActionSource::RunMenu(row)),
8769 quick_launch,
8770 },
8771 window,
8772 cx,
8773 );
8774 }))
8775 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8776 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8777 }))
8778 }
8779
8780 pub fn context_menu_visible(&self) -> bool {
8781 !self.edit_prediction_preview_is_active()
8782 && self
8783 .context_menu
8784 .borrow()
8785 .as_ref()
8786 .is_some_and(|menu| menu.visible())
8787 }
8788
8789 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8790 self.context_menu
8791 .borrow()
8792 .as_ref()
8793 .map(|menu| menu.origin())
8794 }
8795
8796 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8797 self.context_menu_options = Some(options);
8798 }
8799
8800 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8801 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8802
8803 fn render_edit_prediction_popover(
8804 &mut self,
8805 text_bounds: &Bounds<Pixels>,
8806 content_origin: gpui::Point<Pixels>,
8807 right_margin: Pixels,
8808 editor_snapshot: &EditorSnapshot,
8809 visible_row_range: Range<DisplayRow>,
8810 scroll_top: ScrollOffset,
8811 scroll_bottom: ScrollOffset,
8812 line_layouts: &[LineWithInvisibles],
8813 line_height: Pixels,
8814 scroll_position: gpui::Point<ScrollOffset>,
8815 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8816 newest_selection_head: Option<DisplayPoint>,
8817 editor_width: Pixels,
8818 style: &EditorStyle,
8819 window: &mut Window,
8820 cx: &mut App,
8821 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8822 if self.mode().is_minimap() {
8823 return None;
8824 }
8825 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8826
8827 if self.edit_prediction_visible_in_cursor_popover(true) {
8828 return None;
8829 }
8830
8831 match &active_edit_prediction.completion {
8832 EditPrediction::MoveWithin { target, .. } => {
8833 let target_display_point = target.to_display_point(editor_snapshot);
8834
8835 if self.edit_prediction_requires_modifier() {
8836 if !self.edit_prediction_preview_is_active() {
8837 return None;
8838 }
8839
8840 self.render_edit_prediction_modifier_jump_popover(
8841 text_bounds,
8842 content_origin,
8843 visible_row_range,
8844 line_layouts,
8845 line_height,
8846 scroll_pixel_position,
8847 newest_selection_head,
8848 target_display_point,
8849 window,
8850 cx,
8851 )
8852 } else {
8853 self.render_edit_prediction_eager_jump_popover(
8854 text_bounds,
8855 content_origin,
8856 editor_snapshot,
8857 visible_row_range,
8858 scroll_top,
8859 scroll_bottom,
8860 line_height,
8861 scroll_pixel_position,
8862 target_display_point,
8863 editor_width,
8864 window,
8865 cx,
8866 )
8867 }
8868 }
8869 EditPrediction::Edit {
8870 display_mode: EditDisplayMode::Inline,
8871 ..
8872 } => None,
8873 EditPrediction::Edit {
8874 display_mode: EditDisplayMode::TabAccept,
8875 edits,
8876 ..
8877 } => {
8878 let range = &edits.first()?.0;
8879 let target_display_point = range.end.to_display_point(editor_snapshot);
8880
8881 self.render_edit_prediction_end_of_line_popover(
8882 "Accept",
8883 editor_snapshot,
8884 visible_row_range,
8885 target_display_point,
8886 line_height,
8887 scroll_pixel_position,
8888 content_origin,
8889 editor_width,
8890 window,
8891 cx,
8892 )
8893 }
8894 EditPrediction::Edit {
8895 edits,
8896 edit_preview,
8897 display_mode: EditDisplayMode::DiffPopover,
8898 snapshot,
8899 } => self.render_edit_prediction_diff_popover(
8900 text_bounds,
8901 content_origin,
8902 right_margin,
8903 editor_snapshot,
8904 visible_row_range,
8905 line_layouts,
8906 line_height,
8907 scroll_position,
8908 scroll_pixel_position,
8909 newest_selection_head,
8910 editor_width,
8911 style,
8912 edits,
8913 edit_preview,
8914 snapshot,
8915 window,
8916 cx,
8917 ),
8918 EditPrediction::MoveOutside { snapshot, .. } => {
8919 let mut element = self
8920 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8921 .into_any();
8922
8923 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8924 let origin_x = text_bounds.size.width - size.width - px(30.);
8925 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8926 element.prepaint_at(origin, window, cx);
8927
8928 Some((element, origin))
8929 }
8930 }
8931 }
8932
8933 fn render_edit_prediction_modifier_jump_popover(
8934 &mut self,
8935 text_bounds: &Bounds<Pixels>,
8936 content_origin: gpui::Point<Pixels>,
8937 visible_row_range: Range<DisplayRow>,
8938 line_layouts: &[LineWithInvisibles],
8939 line_height: Pixels,
8940 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8941 newest_selection_head: Option<DisplayPoint>,
8942 target_display_point: DisplayPoint,
8943 window: &mut Window,
8944 cx: &mut App,
8945 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8946 let scrolled_content_origin =
8947 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8948
8949 const SCROLL_PADDING_Y: Pixels = px(12.);
8950
8951 if target_display_point.row() < visible_row_range.start {
8952 return self.render_edit_prediction_scroll_popover(
8953 |_| SCROLL_PADDING_Y,
8954 IconName::ArrowUp,
8955 visible_row_range,
8956 line_layouts,
8957 newest_selection_head,
8958 scrolled_content_origin,
8959 window,
8960 cx,
8961 );
8962 } else if target_display_point.row() >= visible_row_range.end {
8963 return self.render_edit_prediction_scroll_popover(
8964 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8965 IconName::ArrowDown,
8966 visible_row_range,
8967 line_layouts,
8968 newest_selection_head,
8969 scrolled_content_origin,
8970 window,
8971 cx,
8972 );
8973 }
8974
8975 const POLE_WIDTH: Pixels = px(2.);
8976
8977 let line_layout =
8978 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8979 let target_column = target_display_point.column() as usize;
8980
8981 let target_x = line_layout.x_for_index(target_column);
8982 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8983 - scroll_pixel_position.y;
8984
8985 let flag_on_right = target_x < text_bounds.size.width / 2.;
8986
8987 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8988 border_color.l += 0.001;
8989
8990 let mut element = v_flex()
8991 .items_end()
8992 .when(flag_on_right, |el| el.items_start())
8993 .child(if flag_on_right {
8994 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8995 .rounded_bl(px(0.))
8996 .rounded_tl(px(0.))
8997 .border_l_2()
8998 .border_color(border_color)
8999 } else {
9000 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9001 .rounded_br(px(0.))
9002 .rounded_tr(px(0.))
9003 .border_r_2()
9004 .border_color(border_color)
9005 })
9006 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9007 .into_any();
9008
9009 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9010
9011 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9012 - point(
9013 if flag_on_right {
9014 POLE_WIDTH
9015 } else {
9016 size.width - POLE_WIDTH
9017 },
9018 size.height - line_height,
9019 );
9020
9021 origin.x = origin.x.max(content_origin.x);
9022
9023 element.prepaint_at(origin, window, cx);
9024
9025 Some((element, origin))
9026 }
9027
9028 fn render_edit_prediction_scroll_popover(
9029 &mut self,
9030 to_y: impl Fn(Size<Pixels>) -> Pixels,
9031 scroll_icon: IconName,
9032 visible_row_range: Range<DisplayRow>,
9033 line_layouts: &[LineWithInvisibles],
9034 newest_selection_head: Option<DisplayPoint>,
9035 scrolled_content_origin: gpui::Point<Pixels>,
9036 window: &mut Window,
9037 cx: &mut App,
9038 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9039 let mut element = self
9040 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9041 .into_any();
9042
9043 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9044
9045 let cursor = newest_selection_head?;
9046 let cursor_row_layout =
9047 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9048 let cursor_column = cursor.column() as usize;
9049
9050 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9051
9052 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9053
9054 element.prepaint_at(origin, window, cx);
9055 Some((element, origin))
9056 }
9057
9058 fn render_edit_prediction_eager_jump_popover(
9059 &mut self,
9060 text_bounds: &Bounds<Pixels>,
9061 content_origin: gpui::Point<Pixels>,
9062 editor_snapshot: &EditorSnapshot,
9063 visible_row_range: Range<DisplayRow>,
9064 scroll_top: ScrollOffset,
9065 scroll_bottom: ScrollOffset,
9066 line_height: Pixels,
9067 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9068 target_display_point: DisplayPoint,
9069 editor_width: Pixels,
9070 window: &mut Window,
9071 cx: &mut App,
9072 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9073 if target_display_point.row().as_f64() < scroll_top {
9074 let mut element = self
9075 .render_edit_prediction_line_popover(
9076 "Jump to Edit",
9077 Some(IconName::ArrowUp),
9078 window,
9079 cx,
9080 )
9081 .into_any();
9082
9083 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9084 let offset = point(
9085 (text_bounds.size.width - size.width) / 2.,
9086 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9087 );
9088
9089 let origin = text_bounds.origin + offset;
9090 element.prepaint_at(origin, window, cx);
9091 Some((element, origin))
9092 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9093 let mut element = self
9094 .render_edit_prediction_line_popover(
9095 "Jump to Edit",
9096 Some(IconName::ArrowDown),
9097 window,
9098 cx,
9099 )
9100 .into_any();
9101
9102 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9103 let offset = point(
9104 (text_bounds.size.width - size.width) / 2.,
9105 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9106 );
9107
9108 let origin = text_bounds.origin + offset;
9109 element.prepaint_at(origin, window, cx);
9110 Some((element, origin))
9111 } else {
9112 self.render_edit_prediction_end_of_line_popover(
9113 "Jump to Edit",
9114 editor_snapshot,
9115 visible_row_range,
9116 target_display_point,
9117 line_height,
9118 scroll_pixel_position,
9119 content_origin,
9120 editor_width,
9121 window,
9122 cx,
9123 )
9124 }
9125 }
9126
9127 fn render_edit_prediction_end_of_line_popover(
9128 self: &mut Editor,
9129 label: &'static str,
9130 editor_snapshot: &EditorSnapshot,
9131 visible_row_range: Range<DisplayRow>,
9132 target_display_point: DisplayPoint,
9133 line_height: Pixels,
9134 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9135 content_origin: gpui::Point<Pixels>,
9136 editor_width: Pixels,
9137 window: &mut Window,
9138 cx: &mut App,
9139 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9140 let target_line_end = DisplayPoint::new(
9141 target_display_point.row(),
9142 editor_snapshot.line_len(target_display_point.row()),
9143 );
9144
9145 let mut element = self
9146 .render_edit_prediction_line_popover(label, None, window, cx)
9147 .into_any();
9148
9149 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9150
9151 let line_origin =
9152 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9153
9154 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9155 let mut origin = start_point
9156 + line_origin
9157 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9158 origin.x = origin.x.max(content_origin.x);
9159
9160 let max_x = content_origin.x + editor_width - size.width;
9161
9162 if origin.x > max_x {
9163 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9164
9165 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9166 origin.y += offset;
9167 IconName::ArrowUp
9168 } else {
9169 origin.y -= offset;
9170 IconName::ArrowDown
9171 };
9172
9173 element = self
9174 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9175 .into_any();
9176
9177 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9178
9179 origin.x = content_origin.x + editor_width - size.width - px(2.);
9180 }
9181
9182 element.prepaint_at(origin, window, cx);
9183 Some((element, origin))
9184 }
9185
9186 fn render_edit_prediction_diff_popover(
9187 self: &Editor,
9188 text_bounds: &Bounds<Pixels>,
9189 content_origin: gpui::Point<Pixels>,
9190 right_margin: Pixels,
9191 editor_snapshot: &EditorSnapshot,
9192 visible_row_range: Range<DisplayRow>,
9193 line_layouts: &[LineWithInvisibles],
9194 line_height: Pixels,
9195 scroll_position: gpui::Point<ScrollOffset>,
9196 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9197 newest_selection_head: Option<DisplayPoint>,
9198 editor_width: Pixels,
9199 style: &EditorStyle,
9200 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9201 edit_preview: &Option<language::EditPreview>,
9202 snapshot: &language::BufferSnapshot,
9203 window: &mut Window,
9204 cx: &mut App,
9205 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9206 let edit_start = edits
9207 .first()
9208 .unwrap()
9209 .0
9210 .start
9211 .to_display_point(editor_snapshot);
9212 let edit_end = edits
9213 .last()
9214 .unwrap()
9215 .0
9216 .end
9217 .to_display_point(editor_snapshot);
9218
9219 let is_visible = visible_row_range.contains(&edit_start.row())
9220 || visible_row_range.contains(&edit_end.row());
9221 if !is_visible {
9222 return None;
9223 }
9224
9225 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9226 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9227 } else {
9228 // Fallback for providers without edit_preview
9229 crate::edit_prediction_fallback_text(edits, cx)
9230 };
9231
9232 let styled_text = highlighted_edits.to_styled_text(&style.text);
9233 let line_count = highlighted_edits.text.lines().count();
9234
9235 const BORDER_WIDTH: Pixels = px(1.);
9236
9237 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9238 let has_keybind = keybind.is_some();
9239
9240 let mut element = h_flex()
9241 .items_start()
9242 .child(
9243 h_flex()
9244 .bg(cx.theme().colors().editor_background)
9245 .border(BORDER_WIDTH)
9246 .shadow_xs()
9247 .border_color(cx.theme().colors().border)
9248 .rounded_l_lg()
9249 .when(line_count > 1, |el| el.rounded_br_lg())
9250 .pr_1()
9251 .child(styled_text),
9252 )
9253 .child(
9254 h_flex()
9255 .h(line_height + BORDER_WIDTH * 2.)
9256 .px_1p5()
9257 .gap_1()
9258 // Workaround: For some reason, there's a gap if we don't do this
9259 .ml(-BORDER_WIDTH)
9260 .shadow(vec![gpui::BoxShadow {
9261 color: gpui::black().opacity(0.05),
9262 offset: point(px(1.), px(1.)),
9263 blur_radius: px(2.),
9264 spread_radius: px(0.),
9265 }])
9266 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9267 .border(BORDER_WIDTH)
9268 .border_color(cx.theme().colors().border)
9269 .rounded_r_lg()
9270 .id("edit_prediction_diff_popover_keybind")
9271 .when(!has_keybind, |el| {
9272 let status_colors = cx.theme().status();
9273
9274 el.bg(status_colors.error_background)
9275 .border_color(status_colors.error.opacity(0.6))
9276 .child(Icon::new(IconName::Info).color(Color::Error))
9277 .cursor_default()
9278 .hoverable_tooltip(move |_window, cx| {
9279 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9280 })
9281 })
9282 .children(keybind),
9283 )
9284 .into_any();
9285
9286 let longest_row =
9287 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9288 let longest_line_width = if visible_row_range.contains(&longest_row) {
9289 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9290 } else {
9291 layout_line(
9292 longest_row,
9293 editor_snapshot,
9294 style,
9295 editor_width,
9296 |_| false,
9297 window,
9298 cx,
9299 )
9300 .width
9301 };
9302
9303 let viewport_bounds =
9304 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9305 right: -right_margin,
9306 ..Default::default()
9307 });
9308
9309 let x_after_longest = Pixels::from(
9310 ScrollPixelOffset::from(
9311 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9312 ) - scroll_pixel_position.x,
9313 );
9314
9315 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9316
9317 // Fully visible if it can be displayed within the window (allow overlapping other
9318 // panes). However, this is only allowed if the popover starts within text_bounds.
9319 let can_position_to_the_right = x_after_longest < text_bounds.right()
9320 && x_after_longest + element_bounds.width < viewport_bounds.right();
9321
9322 let mut origin = if can_position_to_the_right {
9323 point(
9324 x_after_longest,
9325 text_bounds.origin.y
9326 + Pixels::from(
9327 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9328 - scroll_pixel_position.y,
9329 ),
9330 )
9331 } else {
9332 let cursor_row = newest_selection_head.map(|head| head.row());
9333 let above_edit = edit_start
9334 .row()
9335 .0
9336 .checked_sub(line_count as u32)
9337 .map(DisplayRow);
9338 let below_edit = Some(edit_end.row() + 1);
9339 let above_cursor =
9340 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9341 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9342
9343 // Place the edit popover adjacent to the edit if there is a location
9344 // available that is onscreen and does not obscure the cursor. Otherwise,
9345 // place it adjacent to the cursor.
9346 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9347 .into_iter()
9348 .flatten()
9349 .find(|&start_row| {
9350 let end_row = start_row + line_count as u32;
9351 visible_row_range.contains(&start_row)
9352 && visible_row_range.contains(&end_row)
9353 && cursor_row
9354 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9355 })?;
9356
9357 content_origin
9358 + point(
9359 Pixels::from(-scroll_pixel_position.x),
9360 Pixels::from(
9361 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9362 ),
9363 )
9364 };
9365
9366 origin.x -= BORDER_WIDTH;
9367
9368 window.defer_draw(element, origin, 1);
9369
9370 // Do not return an element, since it will already be drawn due to defer_draw.
9371 None
9372 }
9373
9374 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9375 px(30.)
9376 }
9377
9378 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9379 if self.read_only(cx) {
9380 cx.theme().players().read_only()
9381 } else {
9382 self.style.as_ref().unwrap().local_player
9383 }
9384 }
9385
9386 fn render_edit_prediction_accept_keybind(
9387 &self,
9388 window: &mut Window,
9389 cx: &mut App,
9390 ) -> Option<AnyElement> {
9391 let accept_binding =
9392 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9393 let accept_keystroke = accept_binding.keystroke()?;
9394
9395 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9396
9397 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9398 Color::Accent
9399 } else {
9400 Color::Muted
9401 };
9402
9403 h_flex()
9404 .px_0p5()
9405 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9406 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9407 .text_size(TextSize::XSmall.rems(cx))
9408 .child(h_flex().children(ui::render_modifiers(
9409 accept_keystroke.modifiers(),
9410 PlatformStyle::platform(),
9411 Some(modifiers_color),
9412 Some(IconSize::XSmall.rems().into()),
9413 true,
9414 )))
9415 .when(is_platform_style_mac, |parent| {
9416 parent.child(accept_keystroke.key().to_string())
9417 })
9418 .when(!is_platform_style_mac, |parent| {
9419 parent.child(
9420 Key::new(
9421 util::capitalize(accept_keystroke.key()),
9422 Some(Color::Default),
9423 )
9424 .size(Some(IconSize::XSmall.rems().into())),
9425 )
9426 })
9427 .into_any()
9428 .into()
9429 }
9430
9431 fn render_edit_prediction_line_popover(
9432 &self,
9433 label: impl Into<SharedString>,
9434 icon: Option<IconName>,
9435 window: &mut Window,
9436 cx: &mut App,
9437 ) -> Stateful<Div> {
9438 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9439
9440 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9441 let has_keybind = keybind.is_some();
9442
9443 h_flex()
9444 .id("ep-line-popover")
9445 .py_0p5()
9446 .pl_1()
9447 .pr(padding_right)
9448 .gap_1()
9449 .rounded_md()
9450 .border_1()
9451 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9452 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9453 .shadow_xs()
9454 .when(!has_keybind, |el| {
9455 let status_colors = cx.theme().status();
9456
9457 el.bg(status_colors.error_background)
9458 .border_color(status_colors.error.opacity(0.6))
9459 .pl_2()
9460 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9461 .cursor_default()
9462 .hoverable_tooltip(move |_window, cx| {
9463 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9464 })
9465 })
9466 .children(keybind)
9467 .child(
9468 Label::new(label)
9469 .size(LabelSize::Small)
9470 .when(!has_keybind, |el| {
9471 el.color(cx.theme().status().error.into()).strikethrough()
9472 }),
9473 )
9474 .when(!has_keybind, |el| {
9475 el.child(
9476 h_flex().ml_1().child(
9477 Icon::new(IconName::Info)
9478 .size(IconSize::Small)
9479 .color(cx.theme().status().error.into()),
9480 ),
9481 )
9482 })
9483 .when_some(icon, |element, icon| {
9484 element.child(
9485 div()
9486 .mt(px(1.5))
9487 .child(Icon::new(icon).size(IconSize::Small)),
9488 )
9489 })
9490 }
9491
9492 fn render_edit_prediction_jump_outside_popover(
9493 &self,
9494 snapshot: &BufferSnapshot,
9495 window: &mut Window,
9496 cx: &mut App,
9497 ) -> Stateful<Div> {
9498 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9499 let has_keybind = keybind.is_some();
9500
9501 let file_name = snapshot
9502 .file()
9503 .map(|file| SharedString::new(file.file_name(cx)))
9504 .unwrap_or(SharedString::new_static("untitled"));
9505
9506 h_flex()
9507 .id("ep-jump-outside-popover")
9508 .py_1()
9509 .px_2()
9510 .gap_1()
9511 .rounded_md()
9512 .border_1()
9513 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9514 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9515 .shadow_xs()
9516 .when(!has_keybind, |el| {
9517 let status_colors = cx.theme().status();
9518
9519 el.bg(status_colors.error_background)
9520 .border_color(status_colors.error.opacity(0.6))
9521 .pl_2()
9522 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9523 .cursor_default()
9524 .hoverable_tooltip(move |_window, cx| {
9525 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9526 })
9527 })
9528 .children(keybind)
9529 .child(
9530 Label::new(file_name)
9531 .size(LabelSize::Small)
9532 .buffer_font(cx)
9533 .when(!has_keybind, |el| {
9534 el.color(cx.theme().status().error.into()).strikethrough()
9535 }),
9536 )
9537 .when(!has_keybind, |el| {
9538 el.child(
9539 h_flex().ml_1().child(
9540 Icon::new(IconName::Info)
9541 .size(IconSize::Small)
9542 .color(cx.theme().status().error.into()),
9543 ),
9544 )
9545 })
9546 .child(
9547 div()
9548 .mt(px(1.5))
9549 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9550 )
9551 }
9552
9553 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9554 let accent_color = cx.theme().colors().text_accent;
9555 let editor_bg_color = cx.theme().colors().editor_background;
9556 editor_bg_color.blend(accent_color.opacity(0.1))
9557 }
9558
9559 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9560 let accent_color = cx.theme().colors().text_accent;
9561 let editor_bg_color = cx.theme().colors().editor_background;
9562 editor_bg_color.blend(accent_color.opacity(0.6))
9563 }
9564 fn get_prediction_provider_icon_name(
9565 provider: &Option<RegisteredEditPredictionDelegate>,
9566 ) -> IconName {
9567 match provider {
9568 Some(provider) => match provider.provider.name() {
9569 "copilot" => IconName::Copilot,
9570 "supermaven" => IconName::Supermaven,
9571 _ => IconName::ZedPredict,
9572 },
9573 None => IconName::ZedPredict,
9574 }
9575 }
9576
9577 fn render_edit_prediction_cursor_popover(
9578 &self,
9579 min_width: Pixels,
9580 max_width: Pixels,
9581 cursor_point: Point,
9582 style: &EditorStyle,
9583 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9584 _window: &Window,
9585 cx: &mut Context<Editor>,
9586 ) -> Option<AnyElement> {
9587 let provider = self.edit_prediction_provider.as_ref()?;
9588 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9589
9590 let is_refreshing = provider.provider.is_refreshing(cx);
9591
9592 fn pending_completion_container(icon: IconName) -> Div {
9593 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9594 }
9595
9596 let completion = match &self.active_edit_prediction {
9597 Some(prediction) => {
9598 if !self.has_visible_completions_menu() {
9599 const RADIUS: Pixels = px(6.);
9600 const BORDER_WIDTH: Pixels = px(1.);
9601
9602 return Some(
9603 h_flex()
9604 .elevation_2(cx)
9605 .border(BORDER_WIDTH)
9606 .border_color(cx.theme().colors().border)
9607 .when(accept_keystroke.is_none(), |el| {
9608 el.border_color(cx.theme().status().error)
9609 })
9610 .rounded(RADIUS)
9611 .rounded_tl(px(0.))
9612 .overflow_hidden()
9613 .child(div().px_1p5().child(match &prediction.completion {
9614 EditPrediction::MoveWithin { target, snapshot } => {
9615 use text::ToPoint as _;
9616 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9617 {
9618 Icon::new(IconName::ZedPredictDown)
9619 } else {
9620 Icon::new(IconName::ZedPredictUp)
9621 }
9622 }
9623 EditPrediction::MoveOutside { .. } => {
9624 // TODO [zeta2] custom icon for external jump?
9625 Icon::new(provider_icon)
9626 }
9627 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9628 }))
9629 .child(
9630 h_flex()
9631 .gap_1()
9632 .py_1()
9633 .px_2()
9634 .rounded_r(RADIUS - BORDER_WIDTH)
9635 .border_l_1()
9636 .border_color(cx.theme().colors().border)
9637 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9638 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9639 el.child(
9640 Label::new("Hold")
9641 .size(LabelSize::Small)
9642 .when(accept_keystroke.is_none(), |el| {
9643 el.strikethrough()
9644 })
9645 .line_height_style(LineHeightStyle::UiLabel),
9646 )
9647 })
9648 .id("edit_prediction_cursor_popover_keybind")
9649 .when(accept_keystroke.is_none(), |el| {
9650 let status_colors = cx.theme().status();
9651
9652 el.bg(status_colors.error_background)
9653 .border_color(status_colors.error.opacity(0.6))
9654 .child(Icon::new(IconName::Info).color(Color::Error))
9655 .cursor_default()
9656 .hoverable_tooltip(move |_window, cx| {
9657 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9658 .into()
9659 })
9660 })
9661 .when_some(
9662 accept_keystroke.as_ref(),
9663 |el, accept_keystroke| {
9664 el.child(h_flex().children(ui::render_modifiers(
9665 accept_keystroke.modifiers(),
9666 PlatformStyle::platform(),
9667 Some(Color::Default),
9668 Some(IconSize::XSmall.rems().into()),
9669 false,
9670 )))
9671 },
9672 ),
9673 )
9674 .into_any(),
9675 );
9676 }
9677
9678 self.render_edit_prediction_cursor_popover_preview(
9679 prediction,
9680 cursor_point,
9681 style,
9682 cx,
9683 )?
9684 }
9685
9686 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9687 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9688 stale_completion,
9689 cursor_point,
9690 style,
9691 cx,
9692 )?,
9693
9694 None => pending_completion_container(provider_icon)
9695 .child(Label::new("...").size(LabelSize::Small)),
9696 },
9697
9698 None => pending_completion_container(provider_icon)
9699 .child(Label::new("...").size(LabelSize::Small)),
9700 };
9701
9702 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9703 completion
9704 .with_animation(
9705 "loading-completion",
9706 Animation::new(Duration::from_secs(2))
9707 .repeat()
9708 .with_easing(pulsating_between(0.4, 0.8)),
9709 |label, delta| label.opacity(delta),
9710 )
9711 .into_any_element()
9712 } else {
9713 completion.into_any_element()
9714 };
9715
9716 let has_completion = self.active_edit_prediction.is_some();
9717
9718 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9719 Some(
9720 h_flex()
9721 .min_w(min_width)
9722 .max_w(max_width)
9723 .flex_1()
9724 .elevation_2(cx)
9725 .border_color(cx.theme().colors().border)
9726 .child(
9727 div()
9728 .flex_1()
9729 .py_1()
9730 .px_2()
9731 .overflow_hidden()
9732 .child(completion),
9733 )
9734 .when_some(accept_keystroke, |el, accept_keystroke| {
9735 if !accept_keystroke.modifiers().modified() {
9736 return el;
9737 }
9738
9739 el.child(
9740 h_flex()
9741 .h_full()
9742 .border_l_1()
9743 .rounded_r_lg()
9744 .border_color(cx.theme().colors().border)
9745 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9746 .gap_1()
9747 .py_1()
9748 .px_2()
9749 .child(
9750 h_flex()
9751 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9752 .when(is_platform_style_mac, |parent| parent.gap_1())
9753 .child(h_flex().children(ui::render_modifiers(
9754 accept_keystroke.modifiers(),
9755 PlatformStyle::platform(),
9756 Some(if !has_completion {
9757 Color::Muted
9758 } else {
9759 Color::Default
9760 }),
9761 None,
9762 false,
9763 ))),
9764 )
9765 .child(Label::new("Preview").into_any_element())
9766 .opacity(if has_completion { 1.0 } else { 0.4 }),
9767 )
9768 })
9769 .into_any(),
9770 )
9771 }
9772
9773 fn render_edit_prediction_cursor_popover_preview(
9774 &self,
9775 completion: &EditPredictionState,
9776 cursor_point: Point,
9777 style: &EditorStyle,
9778 cx: &mut Context<Editor>,
9779 ) -> Option<Div> {
9780 use text::ToPoint as _;
9781
9782 fn render_relative_row_jump(
9783 prefix: impl Into<String>,
9784 current_row: u32,
9785 target_row: u32,
9786 ) -> Div {
9787 let (row_diff, arrow) = if target_row < current_row {
9788 (current_row - target_row, IconName::ArrowUp)
9789 } else {
9790 (target_row - current_row, IconName::ArrowDown)
9791 };
9792
9793 h_flex()
9794 .child(
9795 Label::new(format!("{}{}", prefix.into(), row_diff))
9796 .color(Color::Muted)
9797 .size(LabelSize::Small),
9798 )
9799 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9800 }
9801
9802 let supports_jump = self
9803 .edit_prediction_provider
9804 .as_ref()
9805 .map(|provider| provider.provider.supports_jump_to_edit())
9806 .unwrap_or(true);
9807
9808 match &completion.completion {
9809 EditPrediction::MoveWithin {
9810 target, snapshot, ..
9811 } => {
9812 if !supports_jump {
9813 return None;
9814 }
9815
9816 Some(
9817 h_flex()
9818 .px_2()
9819 .gap_2()
9820 .flex_1()
9821 .child(
9822 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9823 Icon::new(IconName::ZedPredictDown)
9824 } else {
9825 Icon::new(IconName::ZedPredictUp)
9826 },
9827 )
9828 .child(Label::new("Jump to Edit")),
9829 )
9830 }
9831 EditPrediction::MoveOutside { snapshot, .. } => {
9832 let file_name = snapshot
9833 .file()
9834 .map(|file| file.file_name(cx))
9835 .unwrap_or("untitled");
9836 Some(
9837 h_flex()
9838 .px_2()
9839 .gap_2()
9840 .flex_1()
9841 .child(Icon::new(IconName::ZedPredict))
9842 .child(Label::new(format!("Jump to {file_name}"))),
9843 )
9844 }
9845 EditPrediction::Edit {
9846 edits,
9847 edit_preview,
9848 snapshot,
9849 display_mode: _,
9850 } => {
9851 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9852
9853 let (highlighted_edits, has_more_lines) =
9854 if let Some(edit_preview) = edit_preview.as_ref() {
9855 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9856 .first_line_preview()
9857 } else {
9858 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9859 };
9860
9861 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9862 .with_default_highlights(&style.text, highlighted_edits.highlights);
9863
9864 let preview = h_flex()
9865 .gap_1()
9866 .min_w_16()
9867 .child(styled_text)
9868 .when(has_more_lines, |parent| parent.child("…"));
9869
9870 let left = if supports_jump && first_edit_row != cursor_point.row {
9871 render_relative_row_jump("", cursor_point.row, first_edit_row)
9872 .into_any_element()
9873 } else {
9874 let icon_name =
9875 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9876 Icon::new(icon_name).into_any_element()
9877 };
9878
9879 Some(
9880 h_flex()
9881 .h_full()
9882 .flex_1()
9883 .gap_2()
9884 .pr_1()
9885 .overflow_x_hidden()
9886 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9887 .child(left)
9888 .child(preview),
9889 )
9890 }
9891 }
9892 }
9893
9894 pub fn render_context_menu(
9895 &mut self,
9896 max_height_in_lines: u32,
9897 window: &mut Window,
9898 cx: &mut Context<Editor>,
9899 ) -> Option<AnyElement> {
9900 let menu = self.context_menu.borrow();
9901 let menu = menu.as_ref()?;
9902 if !menu.visible() {
9903 return None;
9904 };
9905 self.style
9906 .as_ref()
9907 .map(|style| menu.render(style, max_height_in_lines, window, cx))
9908 }
9909
9910 fn render_context_menu_aside(
9911 &mut self,
9912 max_size: Size<Pixels>,
9913 window: &mut Window,
9914 cx: &mut Context<Editor>,
9915 ) -> Option<AnyElement> {
9916 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9917 if menu.visible() {
9918 menu.render_aside(max_size, window, cx)
9919 } else {
9920 None
9921 }
9922 })
9923 }
9924
9925 fn hide_context_menu(
9926 &mut self,
9927 window: &mut Window,
9928 cx: &mut Context<Self>,
9929 ) -> Option<CodeContextMenu> {
9930 cx.notify();
9931 self.completion_tasks.clear();
9932 let context_menu = self.context_menu.borrow_mut().take();
9933 self.stale_edit_prediction_in_menu.take();
9934 self.update_visible_edit_prediction(window, cx);
9935 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9936 && let Some(completion_provider) = &self.completion_provider
9937 {
9938 completion_provider.selection_changed(None, window, cx);
9939 }
9940 context_menu
9941 }
9942
9943 fn show_snippet_choices(
9944 &mut self,
9945 choices: &Vec<String>,
9946 selection: Range<Anchor>,
9947 cx: &mut Context<Self>,
9948 ) {
9949 let Some((_, buffer, _)) = self
9950 .buffer()
9951 .read(cx)
9952 .excerpt_containing(selection.start, cx)
9953 else {
9954 return;
9955 };
9956 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9957 else {
9958 return;
9959 };
9960 if buffer != end_buffer {
9961 log::error!("expected anchor range to have matching buffer IDs");
9962 return;
9963 }
9964
9965 let id = post_inc(&mut self.next_completion_id);
9966 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9967 let mut context_menu = self.context_menu.borrow_mut();
9968 let old_menu = context_menu.take();
9969 *context_menu = Some(CodeContextMenu::Completions(
9970 CompletionsMenu::new_snippet_choices(
9971 id,
9972 true,
9973 choices,
9974 selection,
9975 buffer,
9976 old_menu.map(|menu| menu.primary_scroll_handle()),
9977 snippet_sort_order,
9978 ),
9979 ));
9980 }
9981
9982 pub fn insert_snippet(
9983 &mut self,
9984 insertion_ranges: &[Range<MultiBufferOffset>],
9985 snippet: Snippet,
9986 window: &mut Window,
9987 cx: &mut Context<Self>,
9988 ) -> Result<()> {
9989 struct Tabstop<T> {
9990 is_end_tabstop: bool,
9991 ranges: Vec<Range<T>>,
9992 choices: Option<Vec<String>>,
9993 }
9994
9995 let tabstops = self.buffer.update(cx, |buffer, cx| {
9996 let snippet_text: Arc<str> = snippet.text.clone().into();
9997 let edits = insertion_ranges
9998 .iter()
9999 .cloned()
10000 .map(|range| (range, snippet_text.clone()));
10001 let autoindent_mode = AutoindentMode::Block {
10002 original_indent_columns: Vec::new(),
10003 };
10004 buffer.edit(edits, Some(autoindent_mode), cx);
10005
10006 let snapshot = &*buffer.read(cx);
10007 let snippet = &snippet;
10008 snippet
10009 .tabstops
10010 .iter()
10011 .map(|tabstop| {
10012 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10013 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10014 });
10015 let mut tabstop_ranges = tabstop
10016 .ranges
10017 .iter()
10018 .flat_map(|tabstop_range| {
10019 let mut delta = 0_isize;
10020 insertion_ranges.iter().map(move |insertion_range| {
10021 let insertion_start = insertion_range.start + delta;
10022 delta += snippet.text.len() as isize
10023 - (insertion_range.end - insertion_range.start) as isize;
10024
10025 let start =
10026 (insertion_start + tabstop_range.start).min(snapshot.len());
10027 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10028 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10029 })
10030 })
10031 .collect::<Vec<_>>();
10032 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10033
10034 Tabstop {
10035 is_end_tabstop,
10036 ranges: tabstop_ranges,
10037 choices: tabstop.choices.clone(),
10038 }
10039 })
10040 .collect::<Vec<_>>()
10041 });
10042 if let Some(tabstop) = tabstops.first() {
10043 self.change_selections(Default::default(), window, cx, |s| {
10044 // Reverse order so that the first range is the newest created selection.
10045 // Completions will use it and autoscroll will prioritize it.
10046 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10047 });
10048
10049 if let Some(choices) = &tabstop.choices
10050 && let Some(selection) = tabstop.ranges.first()
10051 {
10052 self.show_snippet_choices(choices, selection.clone(), cx)
10053 }
10054
10055 // If we're already at the last tabstop and it's at the end of the snippet,
10056 // we're done, we don't need to keep the state around.
10057 if !tabstop.is_end_tabstop {
10058 let choices = tabstops
10059 .iter()
10060 .map(|tabstop| tabstop.choices.clone())
10061 .collect();
10062
10063 let ranges = tabstops
10064 .into_iter()
10065 .map(|tabstop| tabstop.ranges)
10066 .collect::<Vec<_>>();
10067
10068 self.snippet_stack.push(SnippetState {
10069 active_index: 0,
10070 ranges,
10071 choices,
10072 });
10073 }
10074
10075 // Check whether the just-entered snippet ends with an auto-closable bracket.
10076 if self.autoclose_regions.is_empty() {
10077 let snapshot = self.buffer.read(cx).snapshot(cx);
10078 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10079 let selection_head = selection.head();
10080 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10081 continue;
10082 };
10083
10084 let mut bracket_pair = None;
10085 let max_lookup_length = scope
10086 .brackets()
10087 .map(|(pair, _)| {
10088 pair.start
10089 .as_str()
10090 .chars()
10091 .count()
10092 .max(pair.end.as_str().chars().count())
10093 })
10094 .max();
10095 if let Some(max_lookup_length) = max_lookup_length {
10096 let next_text = snapshot
10097 .chars_at(selection_head)
10098 .take(max_lookup_length)
10099 .collect::<String>();
10100 let prev_text = snapshot
10101 .reversed_chars_at(selection_head)
10102 .take(max_lookup_length)
10103 .collect::<String>();
10104
10105 for (pair, enabled) in scope.brackets() {
10106 if enabled
10107 && pair.close
10108 && prev_text.starts_with(pair.start.as_str())
10109 && next_text.starts_with(pair.end.as_str())
10110 {
10111 bracket_pair = Some(pair.clone());
10112 break;
10113 }
10114 }
10115 }
10116
10117 if let Some(pair) = bracket_pair {
10118 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10119 let autoclose_enabled =
10120 self.use_autoclose && snapshot_settings.use_autoclose;
10121 if autoclose_enabled {
10122 let start = snapshot.anchor_after(selection_head);
10123 let end = snapshot.anchor_after(selection_head);
10124 self.autoclose_regions.push(AutocloseRegion {
10125 selection_id: selection.id,
10126 range: start..end,
10127 pair,
10128 });
10129 }
10130 }
10131 }
10132 }
10133 }
10134 Ok(())
10135 }
10136
10137 pub fn move_to_next_snippet_tabstop(
10138 &mut self,
10139 window: &mut Window,
10140 cx: &mut Context<Self>,
10141 ) -> bool {
10142 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10143 }
10144
10145 pub fn move_to_prev_snippet_tabstop(
10146 &mut self,
10147 window: &mut Window,
10148 cx: &mut Context<Self>,
10149 ) -> bool {
10150 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10151 }
10152
10153 pub fn move_to_snippet_tabstop(
10154 &mut self,
10155 bias: Bias,
10156 window: &mut Window,
10157 cx: &mut Context<Self>,
10158 ) -> bool {
10159 if let Some(mut snippet) = self.snippet_stack.pop() {
10160 match bias {
10161 Bias::Left => {
10162 if snippet.active_index > 0 {
10163 snippet.active_index -= 1;
10164 } else {
10165 self.snippet_stack.push(snippet);
10166 return false;
10167 }
10168 }
10169 Bias::Right => {
10170 if snippet.active_index + 1 < snippet.ranges.len() {
10171 snippet.active_index += 1;
10172 } else {
10173 self.snippet_stack.push(snippet);
10174 return false;
10175 }
10176 }
10177 }
10178 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10179 self.change_selections(Default::default(), window, cx, |s| {
10180 // Reverse order so that the first range is the newest created selection.
10181 // Completions will use it and autoscroll will prioritize it.
10182 s.select_ranges(current_ranges.iter().rev().cloned())
10183 });
10184
10185 if let Some(choices) = &snippet.choices[snippet.active_index]
10186 && let Some(selection) = current_ranges.first()
10187 {
10188 self.show_snippet_choices(choices, selection.clone(), cx);
10189 }
10190
10191 // If snippet state is not at the last tabstop, push it back on the stack
10192 if snippet.active_index + 1 < snippet.ranges.len() {
10193 self.snippet_stack.push(snippet);
10194 }
10195 return true;
10196 }
10197 }
10198
10199 false
10200 }
10201
10202 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10203 self.transact(window, cx, |this, window, cx| {
10204 this.select_all(&SelectAll, window, cx);
10205 this.insert("", window, cx);
10206 });
10207 }
10208
10209 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10210 if self.read_only(cx) {
10211 return;
10212 }
10213 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10214 self.transact(window, cx, |this, window, cx| {
10215 this.select_autoclose_pair(window, cx);
10216
10217 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10218
10219 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10220 if !this.linked_edit_ranges.is_empty() {
10221 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10222 let snapshot = this.buffer.read(cx).snapshot(cx);
10223
10224 for selection in selections.iter() {
10225 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10226 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10227 if selection_start.buffer_id != selection_end.buffer_id {
10228 continue;
10229 }
10230 if let Some(ranges) =
10231 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10232 {
10233 for (buffer, entries) in ranges {
10234 linked_ranges.entry(buffer).or_default().extend(entries);
10235 }
10236 }
10237 }
10238 }
10239
10240 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10241 for selection in &mut selections {
10242 if selection.is_empty() {
10243 let old_head = selection.head();
10244 let mut new_head =
10245 movement::left(&display_map, old_head.to_display_point(&display_map))
10246 .to_point(&display_map);
10247 if let Some((buffer, line_buffer_range)) = display_map
10248 .buffer_snapshot()
10249 .buffer_line_for_row(MultiBufferRow(old_head.row))
10250 {
10251 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10252 let indent_len = match indent_size.kind {
10253 IndentKind::Space => {
10254 buffer.settings_at(line_buffer_range.start, cx).tab_size
10255 }
10256 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10257 };
10258 if old_head.column <= indent_size.len && old_head.column > 0 {
10259 let indent_len = indent_len.get();
10260 new_head = cmp::min(
10261 new_head,
10262 MultiBufferPoint::new(
10263 old_head.row,
10264 ((old_head.column - 1) / indent_len) * indent_len,
10265 ),
10266 );
10267 }
10268 }
10269
10270 selection.set_head(new_head, SelectionGoal::None);
10271 }
10272 }
10273
10274 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10275 this.insert("", window, cx);
10276 let empty_str: Arc<str> = Arc::from("");
10277 for (buffer, edits) in linked_ranges {
10278 let snapshot = buffer.read(cx).snapshot();
10279 use text::ToPoint as TP;
10280
10281 let edits = edits
10282 .into_iter()
10283 .map(|range| {
10284 let end_point = TP::to_point(&range.end, &snapshot);
10285 let mut start_point = TP::to_point(&range.start, &snapshot);
10286
10287 if end_point == start_point {
10288 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10289 .saturating_sub(1);
10290 start_point =
10291 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10292 };
10293
10294 (start_point..end_point, empty_str.clone())
10295 })
10296 .sorted_by_key(|(range, _)| range.start)
10297 .collect::<Vec<_>>();
10298 buffer.update(cx, |this, cx| {
10299 this.edit(edits, None, cx);
10300 })
10301 }
10302 this.refresh_edit_prediction(true, false, window, cx);
10303 refresh_linked_ranges(this, window, cx);
10304 });
10305 }
10306
10307 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10308 if self.read_only(cx) {
10309 return;
10310 }
10311 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10312 self.transact(window, cx, |this, window, cx| {
10313 this.change_selections(Default::default(), window, cx, |s| {
10314 s.move_with(|map, selection| {
10315 if selection.is_empty() {
10316 let cursor = movement::right(map, selection.head());
10317 selection.end = cursor;
10318 selection.reversed = true;
10319 selection.goal = SelectionGoal::None;
10320 }
10321 })
10322 });
10323 this.insert("", window, cx);
10324 this.refresh_edit_prediction(true, false, window, cx);
10325 });
10326 }
10327
10328 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10329 if self.mode.is_single_line() {
10330 cx.propagate();
10331 return;
10332 }
10333
10334 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10335 if self.move_to_prev_snippet_tabstop(window, cx) {
10336 return;
10337 }
10338 self.outdent(&Outdent, window, cx);
10339 }
10340
10341 pub fn next_snippet_tabstop(
10342 &mut self,
10343 _: &NextSnippetTabstop,
10344 window: &mut Window,
10345 cx: &mut Context<Self>,
10346 ) {
10347 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10348 cx.propagate();
10349 return;
10350 }
10351
10352 if self.move_to_next_snippet_tabstop(window, cx) {
10353 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10354 return;
10355 }
10356 cx.propagate();
10357 }
10358
10359 pub fn previous_snippet_tabstop(
10360 &mut self,
10361 _: &PreviousSnippetTabstop,
10362 window: &mut Window,
10363 cx: &mut Context<Self>,
10364 ) {
10365 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10366 cx.propagate();
10367 return;
10368 }
10369
10370 if self.move_to_prev_snippet_tabstop(window, cx) {
10371 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10372 return;
10373 }
10374 cx.propagate();
10375 }
10376
10377 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10378 if self.mode.is_single_line() {
10379 cx.propagate();
10380 return;
10381 }
10382
10383 if self.move_to_next_snippet_tabstop(window, cx) {
10384 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10385 return;
10386 }
10387 if self.read_only(cx) {
10388 return;
10389 }
10390 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10391 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10392 let buffer = self.buffer.read(cx);
10393 let snapshot = buffer.snapshot(cx);
10394 let rows_iter = selections.iter().map(|s| s.head().row);
10395 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10396
10397 let has_some_cursor_in_whitespace = selections
10398 .iter()
10399 .filter(|selection| selection.is_empty())
10400 .any(|selection| {
10401 let cursor = selection.head();
10402 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10403 cursor.column < current_indent.len
10404 });
10405
10406 let mut edits = Vec::new();
10407 let mut prev_edited_row = 0;
10408 let mut row_delta = 0;
10409 for selection in &mut selections {
10410 if selection.start.row != prev_edited_row {
10411 row_delta = 0;
10412 }
10413 prev_edited_row = selection.end.row;
10414
10415 // If the selection is non-empty, then increase the indentation of the selected lines.
10416 if !selection.is_empty() {
10417 row_delta =
10418 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10419 continue;
10420 }
10421
10422 let cursor = selection.head();
10423 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10424 if let Some(suggested_indent) =
10425 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10426 {
10427 // Don't do anything if already at suggested indent
10428 // and there is any other cursor which is not
10429 if has_some_cursor_in_whitespace
10430 && cursor.column == current_indent.len
10431 && current_indent.len == suggested_indent.len
10432 {
10433 continue;
10434 }
10435
10436 // Adjust line and move cursor to suggested indent
10437 // if cursor is not at suggested indent
10438 if cursor.column < suggested_indent.len
10439 && cursor.column <= current_indent.len
10440 && current_indent.len <= suggested_indent.len
10441 {
10442 selection.start = Point::new(cursor.row, suggested_indent.len);
10443 selection.end = selection.start;
10444 if row_delta == 0 {
10445 edits.extend(Buffer::edit_for_indent_size_adjustment(
10446 cursor.row,
10447 current_indent,
10448 suggested_indent,
10449 ));
10450 row_delta = suggested_indent.len - current_indent.len;
10451 }
10452 continue;
10453 }
10454
10455 // If current indent is more than suggested indent
10456 // only move cursor to current indent and skip indent
10457 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10458 selection.start = Point::new(cursor.row, current_indent.len);
10459 selection.end = selection.start;
10460 continue;
10461 }
10462 }
10463
10464 // Otherwise, insert a hard or soft tab.
10465 let settings = buffer.language_settings_at(cursor, cx);
10466 let tab_size = if settings.hard_tabs {
10467 IndentSize::tab()
10468 } else {
10469 let tab_size = settings.tab_size.get();
10470 let indent_remainder = snapshot
10471 .text_for_range(Point::new(cursor.row, 0)..cursor)
10472 .flat_map(str::chars)
10473 .fold(row_delta % tab_size, |counter: u32, c| {
10474 if c == '\t' {
10475 0
10476 } else {
10477 (counter + 1) % tab_size
10478 }
10479 });
10480
10481 let chars_to_next_tab_stop = tab_size - indent_remainder;
10482 IndentSize::spaces(chars_to_next_tab_stop)
10483 };
10484 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10485 selection.end = selection.start;
10486 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10487 row_delta += tab_size.len;
10488 }
10489
10490 self.transact(window, cx, |this, window, cx| {
10491 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10492 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10493 this.refresh_edit_prediction(true, false, window, cx);
10494 });
10495 }
10496
10497 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10498 if self.read_only(cx) {
10499 return;
10500 }
10501 if self.mode.is_single_line() {
10502 cx.propagate();
10503 return;
10504 }
10505
10506 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10507 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10508 let mut prev_edited_row = 0;
10509 let mut row_delta = 0;
10510 let mut edits = Vec::new();
10511 let buffer = self.buffer.read(cx);
10512 let snapshot = buffer.snapshot(cx);
10513 for selection in &mut selections {
10514 if selection.start.row != prev_edited_row {
10515 row_delta = 0;
10516 }
10517 prev_edited_row = selection.end.row;
10518
10519 row_delta =
10520 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10521 }
10522
10523 self.transact(window, cx, |this, window, cx| {
10524 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10525 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10526 });
10527 }
10528
10529 fn indent_selection(
10530 buffer: &MultiBuffer,
10531 snapshot: &MultiBufferSnapshot,
10532 selection: &mut Selection<Point>,
10533 edits: &mut Vec<(Range<Point>, String)>,
10534 delta_for_start_row: u32,
10535 cx: &App,
10536 ) -> u32 {
10537 let settings = buffer.language_settings_at(selection.start, cx);
10538 let tab_size = settings.tab_size.get();
10539 let indent_kind = if settings.hard_tabs {
10540 IndentKind::Tab
10541 } else {
10542 IndentKind::Space
10543 };
10544 let mut start_row = selection.start.row;
10545 let mut end_row = selection.end.row + 1;
10546
10547 // If a selection ends at the beginning of a line, don't indent
10548 // that last line.
10549 if selection.end.column == 0 && selection.end.row > selection.start.row {
10550 end_row -= 1;
10551 }
10552
10553 // Avoid re-indenting a row that has already been indented by a
10554 // previous selection, but still update this selection's column
10555 // to reflect that indentation.
10556 if delta_for_start_row > 0 {
10557 start_row += 1;
10558 selection.start.column += delta_for_start_row;
10559 if selection.end.row == selection.start.row {
10560 selection.end.column += delta_for_start_row;
10561 }
10562 }
10563
10564 let mut delta_for_end_row = 0;
10565 let has_multiple_rows = start_row + 1 != end_row;
10566 for row in start_row..end_row {
10567 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10568 let indent_delta = match (current_indent.kind, indent_kind) {
10569 (IndentKind::Space, IndentKind::Space) => {
10570 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10571 IndentSize::spaces(columns_to_next_tab_stop)
10572 }
10573 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10574 (_, IndentKind::Tab) => IndentSize::tab(),
10575 };
10576
10577 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10578 0
10579 } else {
10580 selection.start.column
10581 };
10582 let row_start = Point::new(row, start);
10583 edits.push((
10584 row_start..row_start,
10585 indent_delta.chars().collect::<String>(),
10586 ));
10587
10588 // Update this selection's endpoints to reflect the indentation.
10589 if row == selection.start.row {
10590 selection.start.column += indent_delta.len;
10591 }
10592 if row == selection.end.row {
10593 selection.end.column += indent_delta.len;
10594 delta_for_end_row = indent_delta.len;
10595 }
10596 }
10597
10598 if selection.start.row == selection.end.row {
10599 delta_for_start_row + delta_for_end_row
10600 } else {
10601 delta_for_end_row
10602 }
10603 }
10604
10605 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10606 if self.read_only(cx) {
10607 return;
10608 }
10609 if self.mode.is_single_line() {
10610 cx.propagate();
10611 return;
10612 }
10613
10614 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10615 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10616 let selections = self.selections.all::<Point>(&display_map);
10617 let mut deletion_ranges = Vec::new();
10618 let mut last_outdent = None;
10619 {
10620 let buffer = self.buffer.read(cx);
10621 let snapshot = buffer.snapshot(cx);
10622 for selection in &selections {
10623 let settings = buffer.language_settings_at(selection.start, cx);
10624 let tab_size = settings.tab_size.get();
10625 let mut rows = selection.spanned_rows(false, &display_map);
10626
10627 // Avoid re-outdenting a row that has already been outdented by a
10628 // previous selection.
10629 if let Some(last_row) = last_outdent
10630 && last_row == rows.start
10631 {
10632 rows.start = rows.start.next_row();
10633 }
10634 let has_multiple_rows = rows.len() > 1;
10635 for row in rows.iter_rows() {
10636 let indent_size = snapshot.indent_size_for_line(row);
10637 if indent_size.len > 0 {
10638 let deletion_len = match indent_size.kind {
10639 IndentKind::Space => {
10640 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10641 if columns_to_prev_tab_stop == 0 {
10642 tab_size
10643 } else {
10644 columns_to_prev_tab_stop
10645 }
10646 }
10647 IndentKind::Tab => 1,
10648 };
10649 let start = if has_multiple_rows
10650 || deletion_len > selection.start.column
10651 || indent_size.len < selection.start.column
10652 {
10653 0
10654 } else {
10655 selection.start.column - deletion_len
10656 };
10657 deletion_ranges.push(
10658 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10659 );
10660 last_outdent = Some(row);
10661 }
10662 }
10663 }
10664 }
10665
10666 self.transact(window, cx, |this, window, cx| {
10667 this.buffer.update(cx, |buffer, cx| {
10668 let empty_str: Arc<str> = Arc::default();
10669 buffer.edit(
10670 deletion_ranges
10671 .into_iter()
10672 .map(|range| (range, empty_str.clone())),
10673 None,
10674 cx,
10675 );
10676 });
10677 let selections = this
10678 .selections
10679 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10680 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10681 });
10682 }
10683
10684 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10685 if self.read_only(cx) {
10686 return;
10687 }
10688 if self.mode.is_single_line() {
10689 cx.propagate();
10690 return;
10691 }
10692
10693 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10694 let selections = self
10695 .selections
10696 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10697 .into_iter()
10698 .map(|s| s.range());
10699
10700 self.transact(window, cx, |this, window, cx| {
10701 this.buffer.update(cx, |buffer, cx| {
10702 buffer.autoindent_ranges(selections, cx);
10703 });
10704 let selections = this
10705 .selections
10706 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10707 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10708 });
10709 }
10710
10711 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10712 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10713 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10714 let selections = self.selections.all::<Point>(&display_map);
10715
10716 let mut new_cursors = Vec::new();
10717 let mut edit_ranges = Vec::new();
10718 let mut selections = selections.iter().peekable();
10719 while let Some(selection) = selections.next() {
10720 let mut rows = selection.spanned_rows(false, &display_map);
10721
10722 // Accumulate contiguous regions of rows that we want to delete.
10723 while let Some(next_selection) = selections.peek() {
10724 let next_rows = next_selection.spanned_rows(false, &display_map);
10725 if next_rows.start <= rows.end {
10726 rows.end = next_rows.end;
10727 selections.next().unwrap();
10728 } else {
10729 break;
10730 }
10731 }
10732
10733 let buffer = display_map.buffer_snapshot();
10734 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10735 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10736 // If there's a line after the range, delete the \n from the end of the row range
10737 (
10738 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10739 rows.end,
10740 )
10741 } else {
10742 // If there isn't a line after the range, delete the \n from the line before the
10743 // start of the row range
10744 edit_start = edit_start.saturating_sub_usize(1);
10745 (buffer.len(), rows.start.previous_row())
10746 };
10747
10748 let text_layout_details = self.text_layout_details(window);
10749 let x = display_map.x_for_display_point(
10750 selection.head().to_display_point(&display_map),
10751 &text_layout_details,
10752 );
10753 let row = Point::new(target_row.0, 0)
10754 .to_display_point(&display_map)
10755 .row();
10756 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10757
10758 new_cursors.push((
10759 selection.id,
10760 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10761 SelectionGoal::None,
10762 ));
10763 edit_ranges.push(edit_start..edit_end);
10764 }
10765
10766 self.transact(window, cx, |this, window, cx| {
10767 let buffer = this.buffer.update(cx, |buffer, cx| {
10768 let empty_str: Arc<str> = Arc::default();
10769 buffer.edit(
10770 edit_ranges
10771 .into_iter()
10772 .map(|range| (range, empty_str.clone())),
10773 None,
10774 cx,
10775 );
10776 buffer.snapshot(cx)
10777 });
10778 let new_selections = new_cursors
10779 .into_iter()
10780 .map(|(id, cursor, goal)| {
10781 let cursor = cursor.to_point(&buffer);
10782 Selection {
10783 id,
10784 start: cursor,
10785 end: cursor,
10786 reversed: false,
10787 goal,
10788 }
10789 })
10790 .collect();
10791
10792 this.change_selections(Default::default(), window, cx, |s| {
10793 s.select(new_selections);
10794 });
10795 });
10796 }
10797
10798 pub fn join_lines_impl(
10799 &mut self,
10800 insert_whitespace: bool,
10801 window: &mut Window,
10802 cx: &mut Context<Self>,
10803 ) {
10804 if self.read_only(cx) {
10805 return;
10806 }
10807 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10808 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10809 let start = MultiBufferRow(selection.start.row);
10810 // Treat single line selections as if they include the next line. Otherwise this action
10811 // would do nothing for single line selections individual cursors.
10812 let end = if selection.start.row == selection.end.row {
10813 MultiBufferRow(selection.start.row + 1)
10814 } else {
10815 MultiBufferRow(selection.end.row)
10816 };
10817
10818 if let Some(last_row_range) = row_ranges.last_mut()
10819 && start <= last_row_range.end
10820 {
10821 last_row_range.end = end;
10822 continue;
10823 }
10824 row_ranges.push(start..end);
10825 }
10826
10827 let snapshot = self.buffer.read(cx).snapshot(cx);
10828 let mut cursor_positions = Vec::new();
10829 for row_range in &row_ranges {
10830 let anchor = snapshot.anchor_before(Point::new(
10831 row_range.end.previous_row().0,
10832 snapshot.line_len(row_range.end.previous_row()),
10833 ));
10834 cursor_positions.push(anchor..anchor);
10835 }
10836
10837 self.transact(window, cx, |this, window, cx| {
10838 for row_range in row_ranges.into_iter().rev() {
10839 for row in row_range.iter_rows().rev() {
10840 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10841 let next_line_row = row.next_row();
10842 let indent = snapshot.indent_size_for_line(next_line_row);
10843 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10844
10845 let replace =
10846 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10847 " "
10848 } else {
10849 ""
10850 };
10851
10852 this.buffer.update(cx, |buffer, cx| {
10853 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10854 });
10855 }
10856 }
10857
10858 this.change_selections(Default::default(), window, cx, |s| {
10859 s.select_anchor_ranges(cursor_positions)
10860 });
10861 });
10862 }
10863
10864 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10865 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10866 self.join_lines_impl(true, window, cx);
10867 }
10868
10869 pub fn sort_lines_case_sensitive(
10870 &mut self,
10871 _: &SortLinesCaseSensitive,
10872 window: &mut Window,
10873 cx: &mut Context<Self>,
10874 ) {
10875 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10876 }
10877
10878 pub fn sort_lines_by_length(
10879 &mut self,
10880 _: &SortLinesByLength,
10881 window: &mut Window,
10882 cx: &mut Context<Self>,
10883 ) {
10884 self.manipulate_immutable_lines(window, cx, |lines| {
10885 lines.sort_by_key(|&line| line.chars().count())
10886 })
10887 }
10888
10889 pub fn sort_lines_case_insensitive(
10890 &mut self,
10891 _: &SortLinesCaseInsensitive,
10892 window: &mut Window,
10893 cx: &mut Context<Self>,
10894 ) {
10895 self.manipulate_immutable_lines(window, cx, |lines| {
10896 lines.sort_by_key(|line| line.to_lowercase())
10897 })
10898 }
10899
10900 pub fn unique_lines_case_insensitive(
10901 &mut self,
10902 _: &UniqueLinesCaseInsensitive,
10903 window: &mut Window,
10904 cx: &mut Context<Self>,
10905 ) {
10906 self.manipulate_immutable_lines(window, cx, |lines| {
10907 let mut seen = HashSet::default();
10908 lines.retain(|line| seen.insert(line.to_lowercase()));
10909 })
10910 }
10911
10912 pub fn unique_lines_case_sensitive(
10913 &mut self,
10914 _: &UniqueLinesCaseSensitive,
10915 window: &mut Window,
10916 cx: &mut Context<Self>,
10917 ) {
10918 self.manipulate_immutable_lines(window, cx, |lines| {
10919 let mut seen = HashSet::default();
10920 lines.retain(|line| seen.insert(*line));
10921 })
10922 }
10923
10924 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10925 let snapshot = self.buffer.read(cx).snapshot(cx);
10926 for selection in self.selections.disjoint_anchors_arc().iter() {
10927 if snapshot
10928 .language_at(selection.start)
10929 .and_then(|lang| lang.config().wrap_characters.as_ref())
10930 .is_some()
10931 {
10932 return true;
10933 }
10934 }
10935 false
10936 }
10937
10938 fn wrap_selections_in_tag(
10939 &mut self,
10940 _: &WrapSelectionsInTag,
10941 window: &mut Window,
10942 cx: &mut Context<Self>,
10943 ) {
10944 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10945
10946 let snapshot = self.buffer.read(cx).snapshot(cx);
10947
10948 let mut edits = Vec::new();
10949 let mut boundaries = Vec::new();
10950
10951 for selection in self
10952 .selections
10953 .all_adjusted(&self.display_snapshot(cx))
10954 .iter()
10955 {
10956 let Some(wrap_config) = snapshot
10957 .language_at(selection.start)
10958 .and_then(|lang| lang.config().wrap_characters.clone())
10959 else {
10960 continue;
10961 };
10962
10963 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10964 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10965
10966 let start_before = snapshot.anchor_before(selection.start);
10967 let end_after = snapshot.anchor_after(selection.end);
10968
10969 edits.push((start_before..start_before, open_tag));
10970 edits.push((end_after..end_after, close_tag));
10971
10972 boundaries.push((
10973 start_before,
10974 end_after,
10975 wrap_config.start_prefix.len(),
10976 wrap_config.end_suffix.len(),
10977 ));
10978 }
10979
10980 if edits.is_empty() {
10981 return;
10982 }
10983
10984 self.transact(window, cx, |this, window, cx| {
10985 let buffer = this.buffer.update(cx, |buffer, cx| {
10986 buffer.edit(edits, None, cx);
10987 buffer.snapshot(cx)
10988 });
10989
10990 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10991 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10992 boundaries.into_iter()
10993 {
10994 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10995 let close_offset = end_after
10996 .to_offset(&buffer)
10997 .saturating_sub_usize(end_suffix_len);
10998 new_selections.push(open_offset..open_offset);
10999 new_selections.push(close_offset..close_offset);
11000 }
11001
11002 this.change_selections(Default::default(), window, cx, |s| {
11003 s.select_ranges(new_selections);
11004 });
11005
11006 this.request_autoscroll(Autoscroll::fit(), cx);
11007 });
11008 }
11009
11010 pub fn toggle_read_only(&mut self, _: &ToggleReadOnly, _: &mut Window, cx: &mut Context<Self>) {
11011 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11012 buffer.update(cx, |buffer, cx| {
11013 buffer.set_capability(
11014 match buffer.capability() {
11015 Capability::ReadWrite => Capability::Read,
11016 Capability::Read => Capability::ReadWrite,
11017 Capability::ReadOnly => Capability::ReadOnly,
11018 },
11019 cx,
11020 );
11021 })
11022 }
11023 }
11024
11025 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11026 let Some(project) = self.project.clone() else {
11027 return;
11028 };
11029 self.reload(project, window, cx)
11030 .detach_and_notify_err(window, cx);
11031 }
11032
11033 pub fn restore_file(
11034 &mut self,
11035 _: &::git::RestoreFile,
11036 window: &mut Window,
11037 cx: &mut Context<Self>,
11038 ) {
11039 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11040 let mut buffer_ids = HashSet::default();
11041 let snapshot = self.buffer().read(cx).snapshot(cx);
11042 for selection in self
11043 .selections
11044 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11045 {
11046 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11047 }
11048
11049 let buffer = self.buffer().read(cx);
11050 let ranges = buffer_ids
11051 .into_iter()
11052 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11053 .collect::<Vec<_>>();
11054
11055 self.restore_hunks_in_ranges(ranges, window, cx);
11056 }
11057
11058 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11059 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11060 let selections = self
11061 .selections
11062 .all(&self.display_snapshot(cx))
11063 .into_iter()
11064 .map(|s| s.range())
11065 .collect();
11066 self.restore_hunks_in_ranges(selections, window, cx);
11067 }
11068
11069 pub fn restore_hunks_in_ranges(
11070 &mut self,
11071 ranges: Vec<Range<Point>>,
11072 window: &mut Window,
11073 cx: &mut Context<Editor>,
11074 ) {
11075 let mut revert_changes = HashMap::default();
11076 let chunk_by = self
11077 .snapshot(window, cx)
11078 .hunks_for_ranges(ranges)
11079 .into_iter()
11080 .chunk_by(|hunk| hunk.buffer_id);
11081 for (buffer_id, hunks) in &chunk_by {
11082 let hunks = hunks.collect::<Vec<_>>();
11083 for hunk in &hunks {
11084 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11085 }
11086 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11087 }
11088 drop(chunk_by);
11089 if !revert_changes.is_empty() {
11090 self.transact(window, cx, |editor, window, cx| {
11091 editor.restore(revert_changes, window, cx);
11092 });
11093 }
11094 }
11095
11096 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11097 if let Some(status) = self
11098 .addons
11099 .iter()
11100 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11101 {
11102 return Some(status);
11103 }
11104 self.project
11105 .as_ref()?
11106 .read(cx)
11107 .status_for_buffer_id(buffer_id, cx)
11108 }
11109
11110 pub fn open_active_item_in_terminal(
11111 &mut self,
11112 _: &OpenInTerminal,
11113 window: &mut Window,
11114 cx: &mut Context<Self>,
11115 ) {
11116 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11117 let project_path = buffer.read(cx).project_path(cx)?;
11118 let project = self.project()?.read(cx);
11119 let entry = project.entry_for_path(&project_path, cx)?;
11120 let parent = match &entry.canonical_path {
11121 Some(canonical_path) => canonical_path.to_path_buf(),
11122 None => project.absolute_path(&project_path, cx)?,
11123 }
11124 .parent()?
11125 .to_path_buf();
11126 Some(parent)
11127 }) {
11128 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11129 }
11130 }
11131
11132 fn set_breakpoint_context_menu(
11133 &mut self,
11134 display_row: DisplayRow,
11135 position: Option<Anchor>,
11136 clicked_point: gpui::Point<Pixels>,
11137 window: &mut Window,
11138 cx: &mut Context<Self>,
11139 ) {
11140 let source = self
11141 .buffer
11142 .read(cx)
11143 .snapshot(cx)
11144 .anchor_before(Point::new(display_row.0, 0u32));
11145
11146 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11147
11148 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11149 self,
11150 source,
11151 clicked_point,
11152 context_menu,
11153 window,
11154 cx,
11155 );
11156 }
11157
11158 fn add_edit_breakpoint_block(
11159 &mut self,
11160 anchor: Anchor,
11161 breakpoint: &Breakpoint,
11162 edit_action: BreakpointPromptEditAction,
11163 window: &mut Window,
11164 cx: &mut Context<Self>,
11165 ) {
11166 let weak_editor = cx.weak_entity();
11167 let bp_prompt = cx.new(|cx| {
11168 BreakpointPromptEditor::new(
11169 weak_editor,
11170 anchor,
11171 breakpoint.clone(),
11172 edit_action,
11173 window,
11174 cx,
11175 )
11176 });
11177
11178 let height = bp_prompt.update(cx, |this, cx| {
11179 this.prompt
11180 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11181 });
11182 let cloned_prompt = bp_prompt.clone();
11183 let blocks = vec![BlockProperties {
11184 style: BlockStyle::Sticky,
11185 placement: BlockPlacement::Above(anchor),
11186 height: Some(height),
11187 render: Arc::new(move |cx| {
11188 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11189 cloned_prompt.clone().into_any_element()
11190 }),
11191 priority: 0,
11192 }];
11193
11194 let focus_handle = bp_prompt.focus_handle(cx);
11195 window.focus(&focus_handle, cx);
11196
11197 let block_ids = self.insert_blocks(blocks, None, cx);
11198 bp_prompt.update(cx, |prompt, _| {
11199 prompt.add_block_ids(block_ids);
11200 });
11201 }
11202
11203 pub(crate) fn breakpoint_at_row(
11204 &self,
11205 row: u32,
11206 window: &mut Window,
11207 cx: &mut Context<Self>,
11208 ) -> Option<(Anchor, Breakpoint)> {
11209 let snapshot = self.snapshot(window, cx);
11210 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11211
11212 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11213 }
11214
11215 pub(crate) fn breakpoint_at_anchor(
11216 &self,
11217 breakpoint_position: Anchor,
11218 snapshot: &EditorSnapshot,
11219 cx: &mut Context<Self>,
11220 ) -> Option<(Anchor, Breakpoint)> {
11221 let buffer = self
11222 .buffer
11223 .read(cx)
11224 .buffer_for_anchor(breakpoint_position, cx)?;
11225
11226 let enclosing_excerpt = breakpoint_position.excerpt_id;
11227 let buffer_snapshot = buffer.read(cx).snapshot();
11228
11229 let row = buffer_snapshot
11230 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11231 .row;
11232
11233 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11234 let anchor_end = snapshot
11235 .buffer_snapshot()
11236 .anchor_after(Point::new(row, line_len));
11237
11238 self.breakpoint_store
11239 .as_ref()?
11240 .read_with(cx, |breakpoint_store, cx| {
11241 breakpoint_store
11242 .breakpoints(
11243 &buffer,
11244 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11245 &buffer_snapshot,
11246 cx,
11247 )
11248 .next()
11249 .and_then(|(bp, _)| {
11250 let breakpoint_row = buffer_snapshot
11251 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11252 .row;
11253
11254 if breakpoint_row == row {
11255 snapshot
11256 .buffer_snapshot()
11257 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11258 .map(|position| (position, bp.bp.clone()))
11259 } else {
11260 None
11261 }
11262 })
11263 })
11264 }
11265
11266 pub fn edit_log_breakpoint(
11267 &mut self,
11268 _: &EditLogBreakpoint,
11269 window: &mut Window,
11270 cx: &mut Context<Self>,
11271 ) {
11272 if self.breakpoint_store.is_none() {
11273 return;
11274 }
11275
11276 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11277 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11278 message: None,
11279 state: BreakpointState::Enabled,
11280 condition: None,
11281 hit_condition: None,
11282 });
11283
11284 self.add_edit_breakpoint_block(
11285 anchor,
11286 &breakpoint,
11287 BreakpointPromptEditAction::Log,
11288 window,
11289 cx,
11290 );
11291 }
11292 }
11293
11294 fn breakpoints_at_cursors(
11295 &self,
11296 window: &mut Window,
11297 cx: &mut Context<Self>,
11298 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11299 let snapshot = self.snapshot(window, cx);
11300 let cursors = self
11301 .selections
11302 .disjoint_anchors_arc()
11303 .iter()
11304 .map(|selection| {
11305 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11306
11307 let breakpoint_position = self
11308 .breakpoint_at_row(cursor_position.row, window, cx)
11309 .map(|bp| bp.0)
11310 .unwrap_or_else(|| {
11311 snapshot
11312 .display_snapshot
11313 .buffer_snapshot()
11314 .anchor_after(Point::new(cursor_position.row, 0))
11315 });
11316
11317 let breakpoint = self
11318 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11319 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11320
11321 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11322 })
11323 // 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.
11324 .collect::<HashMap<Anchor, _>>();
11325
11326 cursors.into_iter().collect()
11327 }
11328
11329 pub fn enable_breakpoint(
11330 &mut self,
11331 _: &crate::actions::EnableBreakpoint,
11332 window: &mut Window,
11333 cx: &mut Context<Self>,
11334 ) {
11335 if self.breakpoint_store.is_none() {
11336 return;
11337 }
11338
11339 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11340 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11341 continue;
11342 };
11343 self.edit_breakpoint_at_anchor(
11344 anchor,
11345 breakpoint,
11346 BreakpointEditAction::InvertState,
11347 cx,
11348 );
11349 }
11350 }
11351
11352 pub fn disable_breakpoint(
11353 &mut self,
11354 _: &crate::actions::DisableBreakpoint,
11355 window: &mut Window,
11356 cx: &mut Context<Self>,
11357 ) {
11358 if self.breakpoint_store.is_none() {
11359 return;
11360 }
11361
11362 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11363 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11364 continue;
11365 };
11366 self.edit_breakpoint_at_anchor(
11367 anchor,
11368 breakpoint,
11369 BreakpointEditAction::InvertState,
11370 cx,
11371 );
11372 }
11373 }
11374
11375 pub fn toggle_breakpoint(
11376 &mut self,
11377 _: &crate::actions::ToggleBreakpoint,
11378 window: &mut Window,
11379 cx: &mut Context<Self>,
11380 ) {
11381 if self.breakpoint_store.is_none() {
11382 return;
11383 }
11384
11385 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11386 if let Some(breakpoint) = breakpoint {
11387 self.edit_breakpoint_at_anchor(
11388 anchor,
11389 breakpoint,
11390 BreakpointEditAction::Toggle,
11391 cx,
11392 );
11393 } else {
11394 self.edit_breakpoint_at_anchor(
11395 anchor,
11396 Breakpoint::new_standard(),
11397 BreakpointEditAction::Toggle,
11398 cx,
11399 );
11400 }
11401 }
11402 }
11403
11404 pub fn edit_breakpoint_at_anchor(
11405 &mut self,
11406 breakpoint_position: Anchor,
11407 breakpoint: Breakpoint,
11408 edit_action: BreakpointEditAction,
11409 cx: &mut Context<Self>,
11410 ) {
11411 let Some(breakpoint_store) = &self.breakpoint_store else {
11412 return;
11413 };
11414
11415 let Some(buffer) = self
11416 .buffer
11417 .read(cx)
11418 .buffer_for_anchor(breakpoint_position, cx)
11419 else {
11420 return;
11421 };
11422
11423 breakpoint_store.update(cx, |breakpoint_store, cx| {
11424 breakpoint_store.toggle_breakpoint(
11425 buffer,
11426 BreakpointWithPosition {
11427 position: breakpoint_position.text_anchor,
11428 bp: breakpoint,
11429 },
11430 edit_action,
11431 cx,
11432 );
11433 });
11434
11435 cx.notify();
11436 }
11437
11438 #[cfg(any(test, feature = "test-support"))]
11439 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11440 self.breakpoint_store.clone()
11441 }
11442
11443 pub fn prepare_restore_change(
11444 &self,
11445 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11446 hunk: &MultiBufferDiffHunk,
11447 cx: &mut App,
11448 ) -> Option<()> {
11449 if hunk.is_created_file() {
11450 return None;
11451 }
11452 let buffer = self.buffer.read(cx);
11453 let diff = buffer.diff_for(hunk.buffer_id)?;
11454 let buffer = buffer.buffer(hunk.buffer_id)?;
11455 let buffer = buffer.read(cx);
11456 let original_text = diff
11457 .read(cx)
11458 .base_text()
11459 .as_rope()
11460 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11461 let buffer_snapshot = buffer.snapshot();
11462 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11463 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11464 probe
11465 .0
11466 .start
11467 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11468 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11469 }) {
11470 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11471 Some(())
11472 } else {
11473 None
11474 }
11475 }
11476
11477 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11478 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11479 }
11480
11481 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11482 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11483 }
11484
11485 pub fn rotate_selections_forward(
11486 &mut self,
11487 _: &RotateSelectionsForward,
11488 window: &mut Window,
11489 cx: &mut Context<Self>,
11490 ) {
11491 self.rotate_selections(window, cx, false)
11492 }
11493
11494 pub fn rotate_selections_backward(
11495 &mut self,
11496 _: &RotateSelectionsBackward,
11497 window: &mut Window,
11498 cx: &mut Context<Self>,
11499 ) {
11500 self.rotate_selections(window, cx, true)
11501 }
11502
11503 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11505 let display_snapshot = self.display_snapshot(cx);
11506 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11507
11508 if selections.len() < 2 {
11509 return;
11510 }
11511
11512 let (edits, new_selections) = {
11513 let buffer = self.buffer.read(cx).read(cx);
11514 let has_selections = selections.iter().any(|s| !s.is_empty());
11515 if has_selections {
11516 let mut selected_texts: Vec<String> = selections
11517 .iter()
11518 .map(|selection| {
11519 buffer
11520 .text_for_range(selection.start..selection.end)
11521 .collect()
11522 })
11523 .collect();
11524
11525 if reverse {
11526 selected_texts.rotate_left(1);
11527 } else {
11528 selected_texts.rotate_right(1);
11529 }
11530
11531 let mut offset_delta: i64 = 0;
11532 let mut new_selections = Vec::new();
11533 let edits: Vec<_> = selections
11534 .iter()
11535 .zip(selected_texts.iter())
11536 .map(|(selection, new_text)| {
11537 let old_len = (selection.end.0 - selection.start.0) as i64;
11538 let new_len = new_text.len() as i64;
11539 let adjusted_start =
11540 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11541 let adjusted_end =
11542 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11543
11544 new_selections.push(Selection {
11545 id: selection.id,
11546 start: adjusted_start,
11547 end: adjusted_end,
11548 reversed: selection.reversed,
11549 goal: selection.goal,
11550 });
11551
11552 offset_delta += new_len - old_len;
11553 (selection.start..selection.end, new_text.clone())
11554 })
11555 .collect();
11556 (edits, new_selections)
11557 } else {
11558 let mut all_rows: Vec<u32> = selections
11559 .iter()
11560 .map(|selection| buffer.offset_to_point(selection.start).row)
11561 .collect();
11562 all_rows.sort_unstable();
11563 all_rows.dedup();
11564
11565 if all_rows.len() < 2 {
11566 return;
11567 }
11568
11569 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11570 .iter()
11571 .map(|&row| {
11572 let start = Point::new(row, 0);
11573 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11574 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11575 })
11576 .collect();
11577
11578 let mut line_texts: Vec<String> = line_ranges
11579 .iter()
11580 .map(|range| buffer.text_for_range(range.clone()).collect())
11581 .collect();
11582
11583 if reverse {
11584 line_texts.rotate_left(1);
11585 } else {
11586 line_texts.rotate_right(1);
11587 }
11588
11589 let edits = line_ranges
11590 .iter()
11591 .zip(line_texts.iter())
11592 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11593 .collect();
11594
11595 let num_rows = all_rows.len();
11596 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11597 .iter()
11598 .enumerate()
11599 .map(|(i, &row)| (row, i))
11600 .collect();
11601
11602 // Compute new line start offsets after rotation (handles CRLF)
11603 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11604 let first_line_start = line_ranges[0].start.0;
11605 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11606 for text in line_texts.iter().take(num_rows - 1) {
11607 let prev_start = *new_line_starts.last().unwrap();
11608 new_line_starts.push(prev_start + text.len() + newline_len);
11609 }
11610
11611 let new_selections = selections
11612 .iter()
11613 .map(|selection| {
11614 let point = buffer.offset_to_point(selection.start);
11615 let old_index = row_to_index[&point.row];
11616 let new_index = if reverse {
11617 (old_index + num_rows - 1) % num_rows
11618 } else {
11619 (old_index + 1) % num_rows
11620 };
11621 let new_offset =
11622 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11623 Selection {
11624 id: selection.id,
11625 start: new_offset,
11626 end: new_offset,
11627 reversed: selection.reversed,
11628 goal: selection.goal,
11629 }
11630 })
11631 .collect();
11632
11633 (edits, new_selections)
11634 }
11635 };
11636
11637 self.transact(window, cx, |this, window, cx| {
11638 this.buffer.update(cx, |buffer, cx| {
11639 buffer.edit(edits, None, cx);
11640 });
11641 this.change_selections(Default::default(), window, cx, |s| {
11642 s.select(new_selections);
11643 });
11644 });
11645 }
11646
11647 fn manipulate_lines<M>(
11648 &mut self,
11649 window: &mut Window,
11650 cx: &mut Context<Self>,
11651 mut manipulate: M,
11652 ) where
11653 M: FnMut(&str) -> LineManipulationResult,
11654 {
11655 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11656
11657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11658 let buffer = self.buffer.read(cx).snapshot(cx);
11659
11660 let mut edits = Vec::new();
11661
11662 let selections = self.selections.all::<Point>(&display_map);
11663 let mut selections = selections.iter().peekable();
11664 let mut contiguous_row_selections = Vec::new();
11665 let mut new_selections = Vec::new();
11666 let mut added_lines = 0;
11667 let mut removed_lines = 0;
11668
11669 while let Some(selection) = selections.next() {
11670 let (start_row, end_row) = consume_contiguous_rows(
11671 &mut contiguous_row_selections,
11672 selection,
11673 &display_map,
11674 &mut selections,
11675 );
11676
11677 let start_point = Point::new(start_row.0, 0);
11678 let end_point = Point::new(
11679 end_row.previous_row().0,
11680 buffer.line_len(end_row.previous_row()),
11681 );
11682 let text = buffer
11683 .text_for_range(start_point..end_point)
11684 .collect::<String>();
11685
11686 let LineManipulationResult {
11687 new_text,
11688 line_count_before,
11689 line_count_after,
11690 } = manipulate(&text);
11691
11692 edits.push((start_point..end_point, new_text));
11693
11694 // Selections must change based on added and removed line count
11695 let start_row =
11696 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11697 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11698 new_selections.push(Selection {
11699 id: selection.id,
11700 start: start_row,
11701 end: end_row,
11702 goal: SelectionGoal::None,
11703 reversed: selection.reversed,
11704 });
11705
11706 if line_count_after > line_count_before {
11707 added_lines += line_count_after - line_count_before;
11708 } else if line_count_before > line_count_after {
11709 removed_lines += line_count_before - line_count_after;
11710 }
11711 }
11712
11713 self.transact(window, cx, |this, window, cx| {
11714 let buffer = this.buffer.update(cx, |buffer, cx| {
11715 buffer.edit(edits, None, cx);
11716 buffer.snapshot(cx)
11717 });
11718
11719 // Recalculate offsets on newly edited buffer
11720 let new_selections = new_selections
11721 .iter()
11722 .map(|s| {
11723 let start_point = Point::new(s.start.0, 0);
11724 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11725 Selection {
11726 id: s.id,
11727 start: buffer.point_to_offset(start_point),
11728 end: buffer.point_to_offset(end_point),
11729 goal: s.goal,
11730 reversed: s.reversed,
11731 }
11732 })
11733 .collect();
11734
11735 this.change_selections(Default::default(), window, cx, |s| {
11736 s.select(new_selections);
11737 });
11738
11739 this.request_autoscroll(Autoscroll::fit(), cx);
11740 });
11741 }
11742
11743 fn manipulate_immutable_lines<Fn>(
11744 &mut self,
11745 window: &mut Window,
11746 cx: &mut Context<Self>,
11747 mut callback: Fn,
11748 ) where
11749 Fn: FnMut(&mut Vec<&str>),
11750 {
11751 self.manipulate_lines(window, cx, |text| {
11752 let mut lines: Vec<&str> = text.split('\n').collect();
11753 let line_count_before = lines.len();
11754
11755 callback(&mut lines);
11756
11757 LineManipulationResult {
11758 new_text: lines.join("\n"),
11759 line_count_before,
11760 line_count_after: lines.len(),
11761 }
11762 });
11763 }
11764
11765 fn manipulate_mutable_lines<Fn>(
11766 &mut self,
11767 window: &mut Window,
11768 cx: &mut Context<Self>,
11769 mut callback: Fn,
11770 ) where
11771 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11772 {
11773 self.manipulate_lines(window, cx, |text| {
11774 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11775 let line_count_before = lines.len();
11776
11777 callback(&mut lines);
11778
11779 LineManipulationResult {
11780 new_text: lines.join("\n"),
11781 line_count_before,
11782 line_count_after: lines.len(),
11783 }
11784 });
11785 }
11786
11787 pub fn convert_indentation_to_spaces(
11788 &mut self,
11789 _: &ConvertIndentationToSpaces,
11790 window: &mut Window,
11791 cx: &mut Context<Self>,
11792 ) {
11793 let settings = self.buffer.read(cx).language_settings(cx);
11794 let tab_size = settings.tab_size.get() as usize;
11795
11796 self.manipulate_mutable_lines(window, cx, |lines| {
11797 // Allocates a reasonably sized scratch buffer once for the whole loop
11798 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11799 // Avoids recomputing spaces that could be inserted many times
11800 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11801 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11802 .collect();
11803
11804 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11805 let mut chars = line.as_ref().chars();
11806 let mut col = 0;
11807 let mut changed = false;
11808
11809 for ch in chars.by_ref() {
11810 match ch {
11811 ' ' => {
11812 reindented_line.push(' ');
11813 col += 1;
11814 }
11815 '\t' => {
11816 // \t are converted to spaces depending on the current column
11817 let spaces_len = tab_size - (col % tab_size);
11818 reindented_line.extend(&space_cache[spaces_len - 1]);
11819 col += spaces_len;
11820 changed = true;
11821 }
11822 _ => {
11823 // If we dont append before break, the character is consumed
11824 reindented_line.push(ch);
11825 break;
11826 }
11827 }
11828 }
11829
11830 if !changed {
11831 reindented_line.clear();
11832 continue;
11833 }
11834 // Append the rest of the line and replace old reference with new one
11835 reindented_line.extend(chars);
11836 *line = Cow::Owned(reindented_line.clone());
11837 reindented_line.clear();
11838 }
11839 });
11840 }
11841
11842 pub fn convert_indentation_to_tabs(
11843 &mut self,
11844 _: &ConvertIndentationToTabs,
11845 window: &mut Window,
11846 cx: &mut Context<Self>,
11847 ) {
11848 let settings = self.buffer.read(cx).language_settings(cx);
11849 let tab_size = settings.tab_size.get() as usize;
11850
11851 self.manipulate_mutable_lines(window, cx, |lines| {
11852 // Allocates a reasonably sized buffer once for the whole loop
11853 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11854 // Avoids recomputing spaces that could be inserted many times
11855 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11856 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11857 .collect();
11858
11859 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11860 let mut chars = line.chars();
11861 let mut spaces_count = 0;
11862 let mut first_non_indent_char = None;
11863 let mut changed = false;
11864
11865 for ch in chars.by_ref() {
11866 match ch {
11867 ' ' => {
11868 // Keep track of spaces. Append \t when we reach tab_size
11869 spaces_count += 1;
11870 changed = true;
11871 if spaces_count == tab_size {
11872 reindented_line.push('\t');
11873 spaces_count = 0;
11874 }
11875 }
11876 '\t' => {
11877 reindented_line.push('\t');
11878 spaces_count = 0;
11879 }
11880 _ => {
11881 // Dont append it yet, we might have remaining spaces
11882 first_non_indent_char = Some(ch);
11883 break;
11884 }
11885 }
11886 }
11887
11888 if !changed {
11889 reindented_line.clear();
11890 continue;
11891 }
11892 // Remaining spaces that didn't make a full tab stop
11893 if spaces_count > 0 {
11894 reindented_line.extend(&space_cache[spaces_count - 1]);
11895 }
11896 // If we consume an extra character that was not indentation, add it back
11897 if let Some(extra_char) = first_non_indent_char {
11898 reindented_line.push(extra_char);
11899 }
11900 // Append the rest of the line and replace old reference with new one
11901 reindented_line.extend(chars);
11902 *line = Cow::Owned(reindented_line.clone());
11903 reindented_line.clear();
11904 }
11905 });
11906 }
11907
11908 pub fn convert_to_upper_case(
11909 &mut self,
11910 _: &ConvertToUpperCase,
11911 window: &mut Window,
11912 cx: &mut Context<Self>,
11913 ) {
11914 self.manipulate_text(window, cx, |text| text.to_uppercase())
11915 }
11916
11917 pub fn convert_to_lower_case(
11918 &mut self,
11919 _: &ConvertToLowerCase,
11920 window: &mut Window,
11921 cx: &mut Context<Self>,
11922 ) {
11923 self.manipulate_text(window, cx, |text| text.to_lowercase())
11924 }
11925
11926 pub fn convert_to_title_case(
11927 &mut self,
11928 _: &ConvertToTitleCase,
11929 window: &mut Window,
11930 cx: &mut Context<Self>,
11931 ) {
11932 self.manipulate_text(window, cx, |text| {
11933 text.split('\n')
11934 .map(|line| line.to_case(Case::Title))
11935 .join("\n")
11936 })
11937 }
11938
11939 pub fn convert_to_snake_case(
11940 &mut self,
11941 _: &ConvertToSnakeCase,
11942 window: &mut Window,
11943 cx: &mut Context<Self>,
11944 ) {
11945 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11946 }
11947
11948 pub fn convert_to_kebab_case(
11949 &mut self,
11950 _: &ConvertToKebabCase,
11951 window: &mut Window,
11952 cx: &mut Context<Self>,
11953 ) {
11954 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11955 }
11956
11957 pub fn convert_to_upper_camel_case(
11958 &mut self,
11959 _: &ConvertToUpperCamelCase,
11960 window: &mut Window,
11961 cx: &mut Context<Self>,
11962 ) {
11963 self.manipulate_text(window, cx, |text| {
11964 text.split('\n')
11965 .map(|line| line.to_case(Case::UpperCamel))
11966 .join("\n")
11967 })
11968 }
11969
11970 pub fn convert_to_lower_camel_case(
11971 &mut self,
11972 _: &ConvertToLowerCamelCase,
11973 window: &mut Window,
11974 cx: &mut Context<Self>,
11975 ) {
11976 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11977 }
11978
11979 pub fn convert_to_opposite_case(
11980 &mut self,
11981 _: &ConvertToOppositeCase,
11982 window: &mut Window,
11983 cx: &mut Context<Self>,
11984 ) {
11985 self.manipulate_text(window, cx, |text| {
11986 text.chars()
11987 .fold(String::with_capacity(text.len()), |mut t, c| {
11988 if c.is_uppercase() {
11989 t.extend(c.to_lowercase());
11990 } else {
11991 t.extend(c.to_uppercase());
11992 }
11993 t
11994 })
11995 })
11996 }
11997
11998 pub fn convert_to_sentence_case(
11999 &mut self,
12000 _: &ConvertToSentenceCase,
12001 window: &mut Window,
12002 cx: &mut Context<Self>,
12003 ) {
12004 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12005 }
12006
12007 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12008 self.manipulate_text(window, cx, |text| {
12009 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12010 if has_upper_case_characters {
12011 text.to_lowercase()
12012 } else {
12013 text.to_uppercase()
12014 }
12015 })
12016 }
12017
12018 pub fn convert_to_rot13(
12019 &mut self,
12020 _: &ConvertToRot13,
12021 window: &mut Window,
12022 cx: &mut Context<Self>,
12023 ) {
12024 self.manipulate_text(window, cx, |text| {
12025 text.chars()
12026 .map(|c| match c {
12027 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12028 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12029 _ => c,
12030 })
12031 .collect()
12032 })
12033 }
12034
12035 pub fn convert_to_rot47(
12036 &mut self,
12037 _: &ConvertToRot47,
12038 window: &mut Window,
12039 cx: &mut Context<Self>,
12040 ) {
12041 self.manipulate_text(window, cx, |text| {
12042 text.chars()
12043 .map(|c| {
12044 let code_point = c as u32;
12045 if code_point >= 33 && code_point <= 126 {
12046 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12047 }
12048 c
12049 })
12050 .collect()
12051 })
12052 }
12053
12054 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12055 where
12056 Fn: FnMut(&str) -> String,
12057 {
12058 let buffer = self.buffer.read(cx).snapshot(cx);
12059
12060 let mut new_selections = Vec::new();
12061 let mut edits = Vec::new();
12062 let mut selection_adjustment = 0isize;
12063
12064 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12065 let selection_is_empty = selection.is_empty();
12066
12067 let (start, end) = if selection_is_empty {
12068 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12069 (word_range.start, word_range.end)
12070 } else {
12071 (
12072 buffer.point_to_offset(selection.start),
12073 buffer.point_to_offset(selection.end),
12074 )
12075 };
12076
12077 let text = buffer.text_for_range(start..end).collect::<String>();
12078 let old_length = text.len() as isize;
12079 let text = callback(&text);
12080
12081 new_selections.push(Selection {
12082 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12083 end: MultiBufferOffset(
12084 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12085 ),
12086 goal: SelectionGoal::None,
12087 id: selection.id,
12088 reversed: selection.reversed,
12089 });
12090
12091 selection_adjustment += old_length - text.len() as isize;
12092
12093 edits.push((start..end, text));
12094 }
12095
12096 self.transact(window, cx, |this, window, cx| {
12097 this.buffer.update(cx, |buffer, cx| {
12098 buffer.edit(edits, None, cx);
12099 });
12100
12101 this.change_selections(Default::default(), window, cx, |s| {
12102 s.select(new_selections);
12103 });
12104
12105 this.request_autoscroll(Autoscroll::fit(), cx);
12106 });
12107 }
12108
12109 pub fn move_selection_on_drop(
12110 &mut self,
12111 selection: &Selection<Anchor>,
12112 target: DisplayPoint,
12113 is_cut: bool,
12114 window: &mut Window,
12115 cx: &mut Context<Self>,
12116 ) {
12117 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12118 let buffer = display_map.buffer_snapshot();
12119 let mut edits = Vec::new();
12120 let insert_point = display_map
12121 .clip_point(target, Bias::Left)
12122 .to_point(&display_map);
12123 let text = buffer
12124 .text_for_range(selection.start..selection.end)
12125 .collect::<String>();
12126 if is_cut {
12127 edits.push(((selection.start..selection.end), String::new()));
12128 }
12129 let insert_anchor = buffer.anchor_before(insert_point);
12130 edits.push(((insert_anchor..insert_anchor), text));
12131 let last_edit_start = insert_anchor.bias_left(buffer);
12132 let last_edit_end = insert_anchor.bias_right(buffer);
12133 self.transact(window, cx, |this, window, cx| {
12134 this.buffer.update(cx, |buffer, cx| {
12135 buffer.edit(edits, None, cx);
12136 });
12137 this.change_selections(Default::default(), window, cx, |s| {
12138 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12139 });
12140 });
12141 }
12142
12143 pub fn clear_selection_drag_state(&mut self) {
12144 self.selection_drag_state = SelectionDragState::None;
12145 }
12146
12147 pub fn duplicate(
12148 &mut self,
12149 upwards: bool,
12150 whole_lines: bool,
12151 window: &mut Window,
12152 cx: &mut Context<Self>,
12153 ) {
12154 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12155
12156 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12157 let buffer = display_map.buffer_snapshot();
12158 let selections = self.selections.all::<Point>(&display_map);
12159
12160 let mut edits = Vec::new();
12161 let mut selections_iter = selections.iter().peekable();
12162 while let Some(selection) = selections_iter.next() {
12163 let mut rows = selection.spanned_rows(false, &display_map);
12164 // duplicate line-wise
12165 if whole_lines || selection.start == selection.end {
12166 // Avoid duplicating the same lines twice.
12167 while let Some(next_selection) = selections_iter.peek() {
12168 let next_rows = next_selection.spanned_rows(false, &display_map);
12169 if next_rows.start < rows.end {
12170 rows.end = next_rows.end;
12171 selections_iter.next().unwrap();
12172 } else {
12173 break;
12174 }
12175 }
12176
12177 // Copy the text from the selected row region and splice it either at the start
12178 // or end of the region.
12179 let start = Point::new(rows.start.0, 0);
12180 let end = Point::new(
12181 rows.end.previous_row().0,
12182 buffer.line_len(rows.end.previous_row()),
12183 );
12184
12185 let mut text = buffer.text_for_range(start..end).collect::<String>();
12186
12187 let insert_location = if upwards {
12188 // When duplicating upward, we need to insert before the current line.
12189 // If we're on the last line and it doesn't end with a newline,
12190 // we need to add a newline before the duplicated content.
12191 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12192 && buffer.max_point().column > 0
12193 && !text.ends_with('\n');
12194
12195 if needs_leading_newline {
12196 text.insert(0, '\n');
12197 end
12198 } else {
12199 text.push('\n');
12200 Point::new(rows.start.0, 0)
12201 }
12202 } else {
12203 text.push('\n');
12204 start
12205 };
12206 edits.push((insert_location..insert_location, text));
12207 } else {
12208 // duplicate character-wise
12209 let start = selection.start;
12210 let end = selection.end;
12211 let text = buffer.text_for_range(start..end).collect::<String>();
12212 edits.push((selection.end..selection.end, text));
12213 }
12214 }
12215
12216 self.transact(window, cx, |this, window, cx| {
12217 this.buffer.update(cx, |buffer, cx| {
12218 buffer.edit(edits, None, cx);
12219 });
12220
12221 // When duplicating upward with whole lines, move the cursor to the duplicated line
12222 if upwards && whole_lines {
12223 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12224
12225 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12226 let mut new_ranges = Vec::new();
12227 let selections = s.all::<Point>(&display_map);
12228 let mut selections_iter = selections.iter().peekable();
12229
12230 while let Some(first_selection) = selections_iter.next() {
12231 // Group contiguous selections together to find the total row span
12232 let mut group_selections = vec![first_selection];
12233 let mut rows = first_selection.spanned_rows(false, &display_map);
12234
12235 while let Some(next_selection) = selections_iter.peek() {
12236 let next_rows = next_selection.spanned_rows(false, &display_map);
12237 if next_rows.start < rows.end {
12238 rows.end = next_rows.end;
12239 group_selections.push(selections_iter.next().unwrap());
12240 } else {
12241 break;
12242 }
12243 }
12244
12245 let row_count = rows.end.0 - rows.start.0;
12246
12247 // Move all selections in this group up by the total number of duplicated rows
12248 for selection in group_selections {
12249 let new_start = Point::new(
12250 selection.start.row.saturating_sub(row_count),
12251 selection.start.column,
12252 );
12253
12254 let new_end = Point::new(
12255 selection.end.row.saturating_sub(row_count),
12256 selection.end.column,
12257 );
12258
12259 new_ranges.push(new_start..new_end);
12260 }
12261 }
12262
12263 s.select_ranges(new_ranges);
12264 });
12265 }
12266
12267 this.request_autoscroll(Autoscroll::fit(), cx);
12268 });
12269 }
12270
12271 pub fn duplicate_line_up(
12272 &mut self,
12273 _: &DuplicateLineUp,
12274 window: &mut Window,
12275 cx: &mut Context<Self>,
12276 ) {
12277 self.duplicate(true, true, window, cx);
12278 }
12279
12280 pub fn duplicate_line_down(
12281 &mut self,
12282 _: &DuplicateLineDown,
12283 window: &mut Window,
12284 cx: &mut Context<Self>,
12285 ) {
12286 self.duplicate(false, true, window, cx);
12287 }
12288
12289 pub fn duplicate_selection(
12290 &mut self,
12291 _: &DuplicateSelection,
12292 window: &mut Window,
12293 cx: &mut Context<Self>,
12294 ) {
12295 self.duplicate(false, false, window, cx);
12296 }
12297
12298 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12299 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12300 if self.mode.is_single_line() {
12301 cx.propagate();
12302 return;
12303 }
12304
12305 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12306 let buffer = self.buffer.read(cx).snapshot(cx);
12307
12308 let mut edits = Vec::new();
12309 let mut unfold_ranges = Vec::new();
12310 let mut refold_creases = Vec::new();
12311
12312 let selections = self.selections.all::<Point>(&display_map);
12313 let mut selections = selections.iter().peekable();
12314 let mut contiguous_row_selections = Vec::new();
12315 let mut new_selections = Vec::new();
12316
12317 while let Some(selection) = selections.next() {
12318 // Find all the selections that span a contiguous row range
12319 let (start_row, end_row) = consume_contiguous_rows(
12320 &mut contiguous_row_selections,
12321 selection,
12322 &display_map,
12323 &mut selections,
12324 );
12325
12326 // Move the text spanned by the row range to be before the line preceding the row range
12327 if start_row.0 > 0 {
12328 let range_to_move = Point::new(
12329 start_row.previous_row().0,
12330 buffer.line_len(start_row.previous_row()),
12331 )
12332 ..Point::new(
12333 end_row.previous_row().0,
12334 buffer.line_len(end_row.previous_row()),
12335 );
12336 let insertion_point = display_map
12337 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12338 .0;
12339
12340 // Don't move lines across excerpts
12341 if buffer
12342 .excerpt_containing(insertion_point..range_to_move.end)
12343 .is_some()
12344 {
12345 let text = buffer
12346 .text_for_range(range_to_move.clone())
12347 .flat_map(|s| s.chars())
12348 .skip(1)
12349 .chain(['\n'])
12350 .collect::<String>();
12351
12352 edits.push((
12353 buffer.anchor_after(range_to_move.start)
12354 ..buffer.anchor_before(range_to_move.end),
12355 String::new(),
12356 ));
12357 let insertion_anchor = buffer.anchor_after(insertion_point);
12358 edits.push((insertion_anchor..insertion_anchor, text));
12359
12360 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12361
12362 // Move selections up
12363 new_selections.extend(contiguous_row_selections.drain(..).map(
12364 |mut selection| {
12365 selection.start.row -= row_delta;
12366 selection.end.row -= row_delta;
12367 selection
12368 },
12369 ));
12370
12371 // Move folds up
12372 unfold_ranges.push(range_to_move.clone());
12373 for fold in display_map.folds_in_range(
12374 buffer.anchor_before(range_to_move.start)
12375 ..buffer.anchor_after(range_to_move.end),
12376 ) {
12377 let mut start = fold.range.start.to_point(&buffer);
12378 let mut end = fold.range.end.to_point(&buffer);
12379 start.row -= row_delta;
12380 end.row -= row_delta;
12381 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12382 }
12383 }
12384 }
12385
12386 // If we didn't move line(s), preserve the existing selections
12387 new_selections.append(&mut contiguous_row_selections);
12388 }
12389
12390 self.transact(window, cx, |this, window, cx| {
12391 this.unfold_ranges(&unfold_ranges, true, true, cx);
12392 this.buffer.update(cx, |buffer, cx| {
12393 for (range, text) in edits {
12394 buffer.edit([(range, text)], None, cx);
12395 }
12396 });
12397 this.fold_creases(refold_creases, true, window, cx);
12398 this.change_selections(Default::default(), window, cx, |s| {
12399 s.select(new_selections);
12400 })
12401 });
12402 }
12403
12404 pub fn move_line_down(
12405 &mut self,
12406 _: &MoveLineDown,
12407 window: &mut Window,
12408 cx: &mut Context<Self>,
12409 ) {
12410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12411 if self.mode.is_single_line() {
12412 cx.propagate();
12413 return;
12414 }
12415
12416 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12417 let buffer = self.buffer.read(cx).snapshot(cx);
12418
12419 let mut edits = Vec::new();
12420 let mut unfold_ranges = Vec::new();
12421 let mut refold_creases = Vec::new();
12422
12423 let selections = self.selections.all::<Point>(&display_map);
12424 let mut selections = selections.iter().peekable();
12425 let mut contiguous_row_selections = Vec::new();
12426 let mut new_selections = Vec::new();
12427
12428 while let Some(selection) = selections.next() {
12429 // Find all the selections that span a contiguous row range
12430 let (start_row, end_row) = consume_contiguous_rows(
12431 &mut contiguous_row_selections,
12432 selection,
12433 &display_map,
12434 &mut selections,
12435 );
12436
12437 // Move the text spanned by the row range to be after the last line of the row range
12438 if end_row.0 <= buffer.max_point().row {
12439 let range_to_move =
12440 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12441 let insertion_point = display_map
12442 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12443 .0;
12444
12445 // Don't move lines across excerpt boundaries
12446 if buffer
12447 .excerpt_containing(range_to_move.start..insertion_point)
12448 .is_some()
12449 {
12450 let mut text = String::from("\n");
12451 text.extend(buffer.text_for_range(range_to_move.clone()));
12452 text.pop(); // Drop trailing newline
12453 edits.push((
12454 buffer.anchor_after(range_to_move.start)
12455 ..buffer.anchor_before(range_to_move.end),
12456 String::new(),
12457 ));
12458 let insertion_anchor = buffer.anchor_after(insertion_point);
12459 edits.push((insertion_anchor..insertion_anchor, text));
12460
12461 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12462
12463 // Move selections down
12464 new_selections.extend(contiguous_row_selections.drain(..).map(
12465 |mut selection| {
12466 selection.start.row += row_delta;
12467 selection.end.row += row_delta;
12468 selection
12469 },
12470 ));
12471
12472 // Move folds down
12473 unfold_ranges.push(range_to_move.clone());
12474 for fold in display_map.folds_in_range(
12475 buffer.anchor_before(range_to_move.start)
12476 ..buffer.anchor_after(range_to_move.end),
12477 ) {
12478 let mut start = fold.range.start.to_point(&buffer);
12479 let mut end = fold.range.end.to_point(&buffer);
12480 start.row += row_delta;
12481 end.row += row_delta;
12482 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12483 }
12484 }
12485 }
12486
12487 // If we didn't move line(s), preserve the existing selections
12488 new_selections.append(&mut contiguous_row_selections);
12489 }
12490
12491 self.transact(window, cx, |this, window, cx| {
12492 this.unfold_ranges(&unfold_ranges, true, true, cx);
12493 this.buffer.update(cx, |buffer, cx| {
12494 for (range, text) in edits {
12495 buffer.edit([(range, text)], None, cx);
12496 }
12497 });
12498 this.fold_creases(refold_creases, true, window, cx);
12499 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12500 });
12501 }
12502
12503 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12505 let text_layout_details = &self.text_layout_details(window);
12506 self.transact(window, cx, |this, window, cx| {
12507 let edits = this.change_selections(Default::default(), window, cx, |s| {
12508 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12509 s.move_with(|display_map, selection| {
12510 if !selection.is_empty() {
12511 return;
12512 }
12513
12514 let mut head = selection.head();
12515 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12516 if head.column() == display_map.line_len(head.row()) {
12517 transpose_offset = display_map
12518 .buffer_snapshot()
12519 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12520 }
12521
12522 if transpose_offset == MultiBufferOffset(0) {
12523 return;
12524 }
12525
12526 *head.column_mut() += 1;
12527 head = display_map.clip_point(head, Bias::Right);
12528 let goal = SelectionGoal::HorizontalPosition(
12529 display_map
12530 .x_for_display_point(head, text_layout_details)
12531 .into(),
12532 );
12533 selection.collapse_to(head, goal);
12534
12535 let transpose_start = display_map
12536 .buffer_snapshot()
12537 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12538 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12539 let transpose_end = display_map
12540 .buffer_snapshot()
12541 .clip_offset(transpose_offset + 1usize, Bias::Right);
12542 if let Some(ch) = display_map
12543 .buffer_snapshot()
12544 .chars_at(transpose_start)
12545 .next()
12546 {
12547 edits.push((transpose_start..transpose_offset, String::new()));
12548 edits.push((transpose_end..transpose_end, ch.to_string()));
12549 }
12550 }
12551 });
12552 edits
12553 });
12554 this.buffer
12555 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12556 let selections = this
12557 .selections
12558 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12559 this.change_selections(Default::default(), window, cx, |s| {
12560 s.select(selections);
12561 });
12562 });
12563 }
12564
12565 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12566 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12567 if self.mode.is_single_line() {
12568 cx.propagate();
12569 return;
12570 }
12571
12572 self.rewrap_impl(RewrapOptions::default(), cx)
12573 }
12574
12575 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12576 let buffer = self.buffer.read(cx).snapshot(cx);
12577 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12578
12579 #[derive(Clone, Debug, PartialEq)]
12580 enum CommentFormat {
12581 /// single line comment, with prefix for line
12582 Line(String),
12583 /// single line within a block comment, with prefix for line
12584 BlockLine(String),
12585 /// a single line of a block comment that includes the initial delimiter
12586 BlockCommentWithStart(BlockCommentConfig),
12587 /// a single line of a block comment that includes the ending delimiter
12588 BlockCommentWithEnd(BlockCommentConfig),
12589 }
12590
12591 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12592 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12593 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12594 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12595 .peekable();
12596
12597 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12598 row
12599 } else {
12600 return Vec::new();
12601 };
12602
12603 let language_settings = buffer.language_settings_at(selection.head(), cx);
12604 let language_scope = buffer.language_scope_at(selection.head());
12605
12606 let indent_and_prefix_for_row =
12607 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12608 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12609 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12610 &language_scope
12611 {
12612 let indent_end = Point::new(row, indent.len);
12613 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12614 let line_text_after_indent = buffer
12615 .text_for_range(indent_end..line_end)
12616 .collect::<String>();
12617
12618 let is_within_comment_override = buffer
12619 .language_scope_at(indent_end)
12620 .is_some_and(|scope| scope.override_name() == Some("comment"));
12621 let comment_delimiters = if is_within_comment_override {
12622 // we are within a comment syntax node, but we don't
12623 // yet know what kind of comment: block, doc or line
12624 match (
12625 language_scope.documentation_comment(),
12626 language_scope.block_comment(),
12627 ) {
12628 (Some(config), _) | (_, Some(config))
12629 if buffer.contains_str_at(indent_end, &config.start) =>
12630 {
12631 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12632 }
12633 (Some(config), _) | (_, Some(config))
12634 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12635 {
12636 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12637 }
12638 (Some(config), _) | (_, Some(config))
12639 if buffer.contains_str_at(indent_end, &config.prefix) =>
12640 {
12641 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12642 }
12643 (_, _) => language_scope
12644 .line_comment_prefixes()
12645 .iter()
12646 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12647 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12648 }
12649 } else {
12650 // we not in an overridden comment node, but we may
12651 // be within a non-overridden line comment node
12652 language_scope
12653 .line_comment_prefixes()
12654 .iter()
12655 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12656 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12657 };
12658
12659 let rewrap_prefix = language_scope
12660 .rewrap_prefixes()
12661 .iter()
12662 .find_map(|prefix_regex| {
12663 prefix_regex.find(&line_text_after_indent).map(|mat| {
12664 if mat.start() == 0 {
12665 Some(mat.as_str().to_string())
12666 } else {
12667 None
12668 }
12669 })
12670 })
12671 .flatten();
12672 (comment_delimiters, rewrap_prefix)
12673 } else {
12674 (None, None)
12675 };
12676 (indent, comment_prefix, rewrap_prefix)
12677 };
12678
12679 let mut ranges = Vec::new();
12680 let from_empty_selection = selection.is_empty();
12681
12682 let mut current_range_start = first_row;
12683 let mut prev_row = first_row;
12684 let (
12685 mut current_range_indent,
12686 mut current_range_comment_delimiters,
12687 mut current_range_rewrap_prefix,
12688 ) = indent_and_prefix_for_row(first_row);
12689
12690 for row in non_blank_rows_iter.skip(1) {
12691 let has_paragraph_break = row > prev_row + 1;
12692
12693 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12694 indent_and_prefix_for_row(row);
12695
12696 let has_indent_change = row_indent != current_range_indent;
12697 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12698
12699 let has_boundary_change = has_comment_change
12700 || row_rewrap_prefix.is_some()
12701 || (has_indent_change && current_range_comment_delimiters.is_some());
12702
12703 if has_paragraph_break || has_boundary_change {
12704 ranges.push((
12705 language_settings.clone(),
12706 Point::new(current_range_start, 0)
12707 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12708 current_range_indent,
12709 current_range_comment_delimiters.clone(),
12710 current_range_rewrap_prefix.clone(),
12711 from_empty_selection,
12712 ));
12713 current_range_start = row;
12714 current_range_indent = row_indent;
12715 current_range_comment_delimiters = row_comment_delimiters;
12716 current_range_rewrap_prefix = row_rewrap_prefix;
12717 }
12718 prev_row = row;
12719 }
12720
12721 ranges.push((
12722 language_settings.clone(),
12723 Point::new(current_range_start, 0)
12724 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12725 current_range_indent,
12726 current_range_comment_delimiters,
12727 current_range_rewrap_prefix,
12728 from_empty_selection,
12729 ));
12730
12731 ranges
12732 });
12733
12734 let mut edits = Vec::new();
12735 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12736
12737 for (
12738 language_settings,
12739 wrap_range,
12740 mut indent_size,
12741 comment_prefix,
12742 rewrap_prefix,
12743 from_empty_selection,
12744 ) in wrap_ranges
12745 {
12746 let mut start_row = wrap_range.start.row;
12747 let mut end_row = wrap_range.end.row;
12748
12749 // Skip selections that overlap with a range that has already been rewrapped.
12750 let selection_range = start_row..end_row;
12751 if rewrapped_row_ranges
12752 .iter()
12753 .any(|range| range.overlaps(&selection_range))
12754 {
12755 continue;
12756 }
12757
12758 let tab_size = language_settings.tab_size;
12759
12760 let (line_prefix, inside_comment) = match &comment_prefix {
12761 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12762 (Some(prefix.as_str()), true)
12763 }
12764 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12765 (Some(prefix.as_ref()), true)
12766 }
12767 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12768 start: _,
12769 end: _,
12770 prefix,
12771 tab_size,
12772 })) => {
12773 indent_size.len += tab_size;
12774 (Some(prefix.as_ref()), true)
12775 }
12776 None => (None, false),
12777 };
12778 let indent_prefix = indent_size.chars().collect::<String>();
12779 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12780
12781 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12782 RewrapBehavior::InComments => inside_comment,
12783 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12784 RewrapBehavior::Anywhere => true,
12785 };
12786
12787 let should_rewrap = options.override_language_settings
12788 || allow_rewrap_based_on_language
12789 || self.hard_wrap.is_some();
12790 if !should_rewrap {
12791 continue;
12792 }
12793
12794 if from_empty_selection {
12795 'expand_upwards: while start_row > 0 {
12796 let prev_row = start_row - 1;
12797 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12798 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12799 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12800 {
12801 start_row = prev_row;
12802 } else {
12803 break 'expand_upwards;
12804 }
12805 }
12806
12807 'expand_downwards: while end_row < buffer.max_point().row {
12808 let next_row = end_row + 1;
12809 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12810 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12811 && !buffer.is_line_blank(MultiBufferRow(next_row))
12812 {
12813 end_row = next_row;
12814 } else {
12815 break 'expand_downwards;
12816 }
12817 }
12818 }
12819
12820 let start = Point::new(start_row, 0);
12821 let start_offset = ToOffset::to_offset(&start, &buffer);
12822 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12823 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12824 let mut first_line_delimiter = None;
12825 let mut last_line_delimiter = None;
12826 let Some(lines_without_prefixes) = selection_text
12827 .lines()
12828 .enumerate()
12829 .map(|(ix, line)| {
12830 let line_trimmed = line.trim_start();
12831 if rewrap_prefix.is_some() && ix > 0 {
12832 Ok(line_trimmed)
12833 } else if let Some(
12834 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12835 start,
12836 prefix,
12837 end,
12838 tab_size,
12839 })
12840 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12841 start,
12842 prefix,
12843 end,
12844 tab_size,
12845 }),
12846 ) = &comment_prefix
12847 {
12848 let line_trimmed = line_trimmed
12849 .strip_prefix(start.as_ref())
12850 .map(|s| {
12851 let mut indent_size = indent_size;
12852 indent_size.len -= tab_size;
12853 let indent_prefix: String = indent_size.chars().collect();
12854 first_line_delimiter = Some((indent_prefix, start));
12855 s.trim_start()
12856 })
12857 .unwrap_or(line_trimmed);
12858 let line_trimmed = line_trimmed
12859 .strip_suffix(end.as_ref())
12860 .map(|s| {
12861 last_line_delimiter = Some(end);
12862 s.trim_end()
12863 })
12864 .unwrap_or(line_trimmed);
12865 let line_trimmed = line_trimmed
12866 .strip_prefix(prefix.as_ref())
12867 .unwrap_or(line_trimmed);
12868 Ok(line_trimmed)
12869 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12870 line_trimmed.strip_prefix(prefix).with_context(|| {
12871 format!("line did not start with prefix {prefix:?}: {line:?}")
12872 })
12873 } else {
12874 line_trimmed
12875 .strip_prefix(&line_prefix.trim_start())
12876 .with_context(|| {
12877 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12878 })
12879 }
12880 })
12881 .collect::<Result<Vec<_>, _>>()
12882 .log_err()
12883 else {
12884 continue;
12885 };
12886
12887 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12888 buffer
12889 .language_settings_at(Point::new(start_row, 0), cx)
12890 .preferred_line_length as usize
12891 });
12892
12893 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12894 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12895 } else {
12896 line_prefix.clone()
12897 };
12898
12899 let wrapped_text = {
12900 let mut wrapped_text = wrap_with_prefix(
12901 line_prefix,
12902 subsequent_lines_prefix,
12903 lines_without_prefixes.join("\n"),
12904 wrap_column,
12905 tab_size,
12906 options.preserve_existing_whitespace,
12907 );
12908
12909 if let Some((indent, delimiter)) = first_line_delimiter {
12910 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12911 }
12912 if let Some(last_line) = last_line_delimiter {
12913 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12914 }
12915
12916 wrapped_text
12917 };
12918
12919 // TODO: should always use char-based diff while still supporting cursor behavior that
12920 // matches vim.
12921 let mut diff_options = DiffOptions::default();
12922 if options.override_language_settings {
12923 diff_options.max_word_diff_len = 0;
12924 diff_options.max_word_diff_line_count = 0;
12925 } else {
12926 diff_options.max_word_diff_len = usize::MAX;
12927 diff_options.max_word_diff_line_count = usize::MAX;
12928 }
12929
12930 for (old_range, new_text) in
12931 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12932 {
12933 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12934 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12935 edits.push((edit_start..edit_end, new_text));
12936 }
12937
12938 rewrapped_row_ranges.push(start_row..=end_row);
12939 }
12940
12941 self.buffer
12942 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12943 }
12944
12945 pub fn cut_common(
12946 &mut self,
12947 cut_no_selection_line: bool,
12948 window: &mut Window,
12949 cx: &mut Context<Self>,
12950 ) -> ClipboardItem {
12951 let mut text = String::new();
12952 let buffer = self.buffer.read(cx).snapshot(cx);
12953 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12954 let mut clipboard_selections = Vec::with_capacity(selections.len());
12955 {
12956 let max_point = buffer.max_point();
12957 let mut is_first = true;
12958 let mut prev_selection_was_entire_line = false;
12959 for selection in &mut selections {
12960 let is_entire_line =
12961 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12962 if is_entire_line {
12963 selection.start = Point::new(selection.start.row, 0);
12964 if !selection.is_empty() && selection.end.column == 0 {
12965 selection.end = cmp::min(max_point, selection.end);
12966 } else {
12967 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12968 }
12969 selection.goal = SelectionGoal::None;
12970 }
12971 if is_first {
12972 is_first = false;
12973 } else if !prev_selection_was_entire_line {
12974 text += "\n";
12975 }
12976 prev_selection_was_entire_line = is_entire_line;
12977 let mut len = 0;
12978 for chunk in buffer.text_for_range(selection.start..selection.end) {
12979 text.push_str(chunk);
12980 len += chunk.len();
12981 }
12982
12983 clipboard_selections.push(ClipboardSelection::for_buffer(
12984 len,
12985 is_entire_line,
12986 selection.range(),
12987 &buffer,
12988 self.project.as_ref(),
12989 cx,
12990 ));
12991 }
12992 }
12993
12994 self.transact(window, cx, |this, window, cx| {
12995 this.change_selections(Default::default(), window, cx, |s| {
12996 s.select(selections);
12997 });
12998 this.insert("", window, cx);
12999 });
13000 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13001 }
13002
13003 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13004 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13005 let item = self.cut_common(true, window, cx);
13006 cx.write_to_clipboard(item);
13007 }
13008
13009 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13010 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13011 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13012 s.move_with(|snapshot, sel| {
13013 if sel.is_empty() {
13014 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13015 }
13016 if sel.is_empty() {
13017 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13018 }
13019 });
13020 });
13021 let item = self.cut_common(false, window, cx);
13022 cx.set_global(KillRing(item))
13023 }
13024
13025 pub fn kill_ring_yank(
13026 &mut self,
13027 _: &KillRingYank,
13028 window: &mut Window,
13029 cx: &mut Context<Self>,
13030 ) {
13031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13032 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13033 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13034 (kill_ring.text().to_string(), kill_ring.metadata_json())
13035 } else {
13036 return;
13037 }
13038 } else {
13039 return;
13040 };
13041 self.do_paste(&text, metadata, false, window, cx);
13042 }
13043
13044 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13045 self.do_copy(true, cx);
13046 }
13047
13048 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13049 self.do_copy(false, cx);
13050 }
13051
13052 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13053 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13054 let buffer = self.buffer.read(cx).read(cx);
13055 let mut text = String::new();
13056
13057 let mut clipboard_selections = Vec::with_capacity(selections.len());
13058 {
13059 let max_point = buffer.max_point();
13060 let mut is_first = true;
13061 let mut prev_selection_was_entire_line = false;
13062 for selection in &selections {
13063 let mut start = selection.start;
13064 let mut end = selection.end;
13065 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13066 let mut add_trailing_newline = false;
13067 if is_entire_line {
13068 start = Point::new(start.row, 0);
13069 let next_line_start = Point::new(end.row + 1, 0);
13070 if next_line_start <= max_point {
13071 end = next_line_start;
13072 } else {
13073 // We're on the last line without a trailing newline.
13074 // Copy to the end of the line and add a newline afterwards.
13075 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13076 add_trailing_newline = true;
13077 }
13078 }
13079
13080 let mut trimmed_selections = Vec::new();
13081 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13082 let row = MultiBufferRow(start.row);
13083 let first_indent = buffer.indent_size_for_line(row);
13084 if first_indent.len == 0 || start.column > first_indent.len {
13085 trimmed_selections.push(start..end);
13086 } else {
13087 trimmed_selections.push(
13088 Point::new(row.0, first_indent.len)
13089 ..Point::new(row.0, buffer.line_len(row)),
13090 );
13091 for row in start.row + 1..=end.row {
13092 let mut line_len = buffer.line_len(MultiBufferRow(row));
13093 if row == end.row {
13094 line_len = end.column;
13095 }
13096 if line_len == 0 {
13097 trimmed_selections
13098 .push(Point::new(row, 0)..Point::new(row, line_len));
13099 continue;
13100 }
13101 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13102 if row_indent_size.len >= first_indent.len {
13103 trimmed_selections.push(
13104 Point::new(row, first_indent.len)..Point::new(row, line_len),
13105 );
13106 } else {
13107 trimmed_selections.clear();
13108 trimmed_selections.push(start..end);
13109 break;
13110 }
13111 }
13112 }
13113 } else {
13114 trimmed_selections.push(start..end);
13115 }
13116
13117 for trimmed_range in trimmed_selections {
13118 if is_first {
13119 is_first = false;
13120 } else if !prev_selection_was_entire_line {
13121 text += "\n";
13122 }
13123 prev_selection_was_entire_line = is_entire_line;
13124 let mut len = 0;
13125 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13126 text.push_str(chunk);
13127 len += chunk.len();
13128 }
13129 if add_trailing_newline {
13130 text.push('\n');
13131 len += 1;
13132 }
13133 clipboard_selections.push(ClipboardSelection::for_buffer(
13134 len,
13135 is_entire_line,
13136 trimmed_range,
13137 &buffer,
13138 self.project.as_ref(),
13139 cx,
13140 ));
13141 }
13142 }
13143 }
13144
13145 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13146 text,
13147 clipboard_selections,
13148 ));
13149 }
13150
13151 pub fn do_paste(
13152 &mut self,
13153 text: &String,
13154 clipboard_selections: Option<Vec<ClipboardSelection>>,
13155 handle_entire_lines: bool,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 if self.read_only(cx) {
13160 return;
13161 }
13162
13163 let clipboard_text = Cow::Borrowed(text.as_str());
13164
13165 self.transact(window, cx, |this, window, cx| {
13166 let had_active_edit_prediction = this.has_active_edit_prediction();
13167 let display_map = this.display_snapshot(cx);
13168 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13169 let cursor_offset = this
13170 .selections
13171 .last::<MultiBufferOffset>(&display_map)
13172 .head();
13173
13174 if let Some(mut clipboard_selections) = clipboard_selections {
13175 let all_selections_were_entire_line =
13176 clipboard_selections.iter().all(|s| s.is_entire_line);
13177 let first_selection_indent_column =
13178 clipboard_selections.first().map(|s| s.first_line_indent);
13179 if clipboard_selections.len() != old_selections.len() {
13180 clipboard_selections.drain(..);
13181 }
13182 let mut auto_indent_on_paste = true;
13183
13184 this.buffer.update(cx, |buffer, cx| {
13185 let snapshot = buffer.read(cx);
13186 auto_indent_on_paste = snapshot
13187 .language_settings_at(cursor_offset, cx)
13188 .auto_indent_on_paste;
13189
13190 let mut start_offset = 0;
13191 let mut edits = Vec::new();
13192 let mut original_indent_columns = Vec::new();
13193 for (ix, selection) in old_selections.iter().enumerate() {
13194 let to_insert;
13195 let entire_line;
13196 let original_indent_column;
13197 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13198 let end_offset = start_offset + clipboard_selection.len;
13199 to_insert = &clipboard_text[start_offset..end_offset];
13200 entire_line = clipboard_selection.is_entire_line;
13201 start_offset = if entire_line {
13202 end_offset
13203 } else {
13204 end_offset + 1
13205 };
13206 original_indent_column = Some(clipboard_selection.first_line_indent);
13207 } else {
13208 to_insert = &*clipboard_text;
13209 entire_line = all_selections_were_entire_line;
13210 original_indent_column = first_selection_indent_column
13211 }
13212
13213 let (range, to_insert) =
13214 if selection.is_empty() && handle_entire_lines && entire_line {
13215 // If the corresponding selection was empty when this slice of the
13216 // clipboard text was written, then the entire line containing the
13217 // selection was copied. If this selection is also currently empty,
13218 // then paste the line before the current line of the buffer.
13219 let column = selection.start.to_point(&snapshot).column as usize;
13220 let line_start = selection.start - column;
13221 (line_start..line_start, Cow::Borrowed(to_insert))
13222 } else {
13223 let language = snapshot.language_at(selection.head());
13224 let range = selection.range();
13225 if let Some(language) = language
13226 && language.name() == "Markdown".into()
13227 {
13228 edit_for_markdown_paste(
13229 &snapshot,
13230 range,
13231 to_insert,
13232 url::Url::parse(to_insert).ok(),
13233 )
13234 } else {
13235 (range, Cow::Borrowed(to_insert))
13236 }
13237 };
13238
13239 edits.push((range, to_insert));
13240 original_indent_columns.push(original_indent_column);
13241 }
13242 drop(snapshot);
13243
13244 buffer.edit(
13245 edits,
13246 if auto_indent_on_paste {
13247 Some(AutoindentMode::Block {
13248 original_indent_columns,
13249 })
13250 } else {
13251 None
13252 },
13253 cx,
13254 );
13255 });
13256
13257 let selections = this
13258 .selections
13259 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13260 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13261 } else {
13262 let url = url::Url::parse(&clipboard_text).ok();
13263
13264 let auto_indent_mode = if !clipboard_text.is_empty() {
13265 Some(AutoindentMode::Block {
13266 original_indent_columns: Vec::new(),
13267 })
13268 } else {
13269 None
13270 };
13271
13272 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13273 let snapshot = buffer.snapshot(cx);
13274
13275 let anchors = old_selections
13276 .iter()
13277 .map(|s| {
13278 let anchor = snapshot.anchor_after(s.head());
13279 s.map(|_| anchor)
13280 })
13281 .collect::<Vec<_>>();
13282
13283 let mut edits = Vec::new();
13284
13285 for selection in old_selections.iter() {
13286 let language = snapshot.language_at(selection.head());
13287 let range = selection.range();
13288
13289 let (edit_range, edit_text) = if let Some(language) = language
13290 && language.name() == "Markdown".into()
13291 {
13292 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13293 } else {
13294 (range, clipboard_text.clone())
13295 };
13296
13297 edits.push((edit_range, edit_text));
13298 }
13299
13300 drop(snapshot);
13301 buffer.edit(edits, auto_indent_mode, cx);
13302
13303 anchors
13304 });
13305
13306 this.change_selections(Default::default(), window, cx, |s| {
13307 s.select_anchors(selection_anchors);
13308 });
13309 }
13310
13311 // 🤔 | .. | show_in_menu |
13312 // | .. | true true
13313 // | had_edit_prediction | false true
13314
13315 let trigger_in_words =
13316 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13317
13318 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13319 });
13320 }
13321
13322 pub fn diff_clipboard_with_selection(
13323 &mut self,
13324 _: &DiffClipboardWithSelection,
13325 window: &mut Window,
13326 cx: &mut Context<Self>,
13327 ) {
13328 let selections = self
13329 .selections
13330 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13331
13332 if selections.is_empty() {
13333 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13334 return;
13335 };
13336
13337 let clipboard_text = match cx.read_from_clipboard() {
13338 Some(item) => match item.entries().first() {
13339 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13340 _ => None,
13341 },
13342 None => None,
13343 };
13344
13345 let Some(clipboard_text) = clipboard_text else {
13346 log::warn!("Clipboard doesn't contain text.");
13347 return;
13348 };
13349
13350 window.dispatch_action(
13351 Box::new(DiffClipboardWithSelectionData {
13352 clipboard_text,
13353 editor: cx.entity(),
13354 }),
13355 cx,
13356 );
13357 }
13358
13359 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13360 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13361 if let Some(item) = cx.read_from_clipboard() {
13362 let entries = item.entries();
13363
13364 match entries.first() {
13365 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13366 // of all the pasted entries.
13367 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13368 .do_paste(
13369 clipboard_string.text(),
13370 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13371 true,
13372 window,
13373 cx,
13374 ),
13375 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13376 }
13377 }
13378 }
13379
13380 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13381 if self.read_only(cx) {
13382 return;
13383 }
13384
13385 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13386
13387 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13388 if let Some((selections, _)) =
13389 self.selection_history.transaction(transaction_id).cloned()
13390 {
13391 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13392 s.select_anchors(selections.to_vec());
13393 });
13394 } else {
13395 log::error!(
13396 "No entry in selection_history found for undo. \
13397 This may correspond to a bug where undo does not update the selection. \
13398 If this is occurring, please add details to \
13399 https://github.com/zed-industries/zed/issues/22692"
13400 );
13401 }
13402 self.request_autoscroll(Autoscroll::fit(), cx);
13403 self.unmark_text(window, cx);
13404 self.refresh_edit_prediction(true, false, window, cx);
13405 cx.emit(EditorEvent::Edited { transaction_id });
13406 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13407 }
13408 }
13409
13410 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13411 if self.read_only(cx) {
13412 return;
13413 }
13414
13415 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13416
13417 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13418 if let Some((_, Some(selections))) =
13419 self.selection_history.transaction(transaction_id).cloned()
13420 {
13421 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13422 s.select_anchors(selections.to_vec());
13423 });
13424 } else {
13425 log::error!(
13426 "No entry in selection_history found for redo. \
13427 This may correspond to a bug where undo does not update the selection. \
13428 If this is occurring, please add details to \
13429 https://github.com/zed-industries/zed/issues/22692"
13430 );
13431 }
13432 self.request_autoscroll(Autoscroll::fit(), cx);
13433 self.unmark_text(window, cx);
13434 self.refresh_edit_prediction(true, false, window, cx);
13435 cx.emit(EditorEvent::Edited { transaction_id });
13436 }
13437 }
13438
13439 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13440 self.buffer
13441 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13442 }
13443
13444 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13445 self.buffer
13446 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13447 }
13448
13449 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13450 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13451 self.change_selections(Default::default(), window, cx, |s| {
13452 s.move_with(|map, selection| {
13453 let cursor = if selection.is_empty() {
13454 movement::left(map, selection.start)
13455 } else {
13456 selection.start
13457 };
13458 selection.collapse_to(cursor, SelectionGoal::None);
13459 });
13460 })
13461 }
13462
13463 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13464 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13465 self.change_selections(Default::default(), window, cx, |s| {
13466 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13467 })
13468 }
13469
13470 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13471 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13472 self.change_selections(Default::default(), window, cx, |s| {
13473 s.move_with(|map, selection| {
13474 let cursor = if selection.is_empty() {
13475 movement::right(map, selection.end)
13476 } else {
13477 selection.end
13478 };
13479 selection.collapse_to(cursor, SelectionGoal::None)
13480 });
13481 })
13482 }
13483
13484 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13485 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13486 self.change_selections(Default::default(), window, cx, |s| {
13487 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13488 });
13489 }
13490
13491 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13492 if self.take_rename(true, window, cx).is_some() {
13493 return;
13494 }
13495
13496 if self.mode.is_single_line() {
13497 cx.propagate();
13498 return;
13499 }
13500
13501 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13502
13503 let text_layout_details = &self.text_layout_details(window);
13504 let selection_count = self.selections.count();
13505 let first_selection = self.selections.first_anchor();
13506
13507 self.change_selections(Default::default(), window, cx, |s| {
13508 s.move_with(|map, selection| {
13509 if !selection.is_empty() {
13510 selection.goal = SelectionGoal::None;
13511 }
13512 let (cursor, goal) = movement::up(
13513 map,
13514 selection.start,
13515 selection.goal,
13516 false,
13517 text_layout_details,
13518 );
13519 selection.collapse_to(cursor, goal);
13520 });
13521 });
13522
13523 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13524 {
13525 cx.propagate();
13526 }
13527 }
13528
13529 pub fn move_up_by_lines(
13530 &mut self,
13531 action: &MoveUpByLines,
13532 window: &mut Window,
13533 cx: &mut Context<Self>,
13534 ) {
13535 if self.take_rename(true, window, cx).is_some() {
13536 return;
13537 }
13538
13539 if self.mode.is_single_line() {
13540 cx.propagate();
13541 return;
13542 }
13543
13544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13545
13546 let text_layout_details = &self.text_layout_details(window);
13547
13548 self.change_selections(Default::default(), window, cx, |s| {
13549 s.move_with(|map, selection| {
13550 if !selection.is_empty() {
13551 selection.goal = SelectionGoal::None;
13552 }
13553 let (cursor, goal) = movement::up_by_rows(
13554 map,
13555 selection.start,
13556 action.lines,
13557 selection.goal,
13558 false,
13559 text_layout_details,
13560 );
13561 selection.collapse_to(cursor, goal);
13562 });
13563 })
13564 }
13565
13566 pub fn move_down_by_lines(
13567 &mut self,
13568 action: &MoveDownByLines,
13569 window: &mut Window,
13570 cx: &mut Context<Self>,
13571 ) {
13572 if self.take_rename(true, window, cx).is_some() {
13573 return;
13574 }
13575
13576 if self.mode.is_single_line() {
13577 cx.propagate();
13578 return;
13579 }
13580
13581 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13582
13583 let text_layout_details = &self.text_layout_details(window);
13584
13585 self.change_selections(Default::default(), window, cx, |s| {
13586 s.move_with(|map, selection| {
13587 if !selection.is_empty() {
13588 selection.goal = SelectionGoal::None;
13589 }
13590 let (cursor, goal) = movement::down_by_rows(
13591 map,
13592 selection.start,
13593 action.lines,
13594 selection.goal,
13595 false,
13596 text_layout_details,
13597 );
13598 selection.collapse_to(cursor, goal);
13599 });
13600 })
13601 }
13602
13603 pub fn select_down_by_lines(
13604 &mut self,
13605 action: &SelectDownByLines,
13606 window: &mut Window,
13607 cx: &mut Context<Self>,
13608 ) {
13609 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13610 let text_layout_details = &self.text_layout_details(window);
13611 self.change_selections(Default::default(), window, cx, |s| {
13612 s.move_heads_with(|map, head, goal| {
13613 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13614 })
13615 })
13616 }
13617
13618 pub fn select_up_by_lines(
13619 &mut self,
13620 action: &SelectUpByLines,
13621 window: &mut Window,
13622 cx: &mut Context<Self>,
13623 ) {
13624 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13625 let text_layout_details = &self.text_layout_details(window);
13626 self.change_selections(Default::default(), window, cx, |s| {
13627 s.move_heads_with(|map, head, goal| {
13628 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13629 })
13630 })
13631 }
13632
13633 pub fn select_page_up(
13634 &mut self,
13635 _: &SelectPageUp,
13636 window: &mut Window,
13637 cx: &mut Context<Self>,
13638 ) {
13639 let Some(row_count) = self.visible_row_count() else {
13640 return;
13641 };
13642
13643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13644
13645 let text_layout_details = &self.text_layout_details(window);
13646
13647 self.change_selections(Default::default(), window, cx, |s| {
13648 s.move_heads_with(|map, head, goal| {
13649 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13650 })
13651 })
13652 }
13653
13654 pub fn move_page_up(
13655 &mut self,
13656 action: &MovePageUp,
13657 window: &mut Window,
13658 cx: &mut Context<Self>,
13659 ) {
13660 if self.take_rename(true, window, cx).is_some() {
13661 return;
13662 }
13663
13664 if self
13665 .context_menu
13666 .borrow_mut()
13667 .as_mut()
13668 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13669 .unwrap_or(false)
13670 {
13671 return;
13672 }
13673
13674 if matches!(self.mode, EditorMode::SingleLine) {
13675 cx.propagate();
13676 return;
13677 }
13678
13679 let Some(row_count) = self.visible_row_count() else {
13680 return;
13681 };
13682
13683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13684
13685 let effects = if action.center_cursor {
13686 SelectionEffects::scroll(Autoscroll::center())
13687 } else {
13688 SelectionEffects::default()
13689 };
13690
13691 let text_layout_details = &self.text_layout_details(window);
13692
13693 self.change_selections(effects, window, cx, |s| {
13694 s.move_with(|map, selection| {
13695 if !selection.is_empty() {
13696 selection.goal = SelectionGoal::None;
13697 }
13698 let (cursor, goal) = movement::up_by_rows(
13699 map,
13700 selection.end,
13701 row_count,
13702 selection.goal,
13703 false,
13704 text_layout_details,
13705 );
13706 selection.collapse_to(cursor, goal);
13707 });
13708 });
13709 }
13710
13711 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13713 let text_layout_details = &self.text_layout_details(window);
13714 self.change_selections(Default::default(), window, cx, |s| {
13715 s.move_heads_with(|map, head, goal| {
13716 movement::up(map, head, goal, false, text_layout_details)
13717 })
13718 })
13719 }
13720
13721 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13722 self.take_rename(true, window, cx);
13723
13724 if self.mode.is_single_line() {
13725 cx.propagate();
13726 return;
13727 }
13728
13729 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13730
13731 let text_layout_details = &self.text_layout_details(window);
13732 let selection_count = self.selections.count();
13733 let first_selection = self.selections.first_anchor();
13734
13735 self.change_selections(Default::default(), window, cx, |s| {
13736 s.move_with(|map, selection| {
13737 if !selection.is_empty() {
13738 selection.goal = SelectionGoal::None;
13739 }
13740 let (cursor, goal) = movement::down(
13741 map,
13742 selection.end,
13743 selection.goal,
13744 false,
13745 text_layout_details,
13746 );
13747 selection.collapse_to(cursor, goal);
13748 });
13749 });
13750
13751 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13752 {
13753 cx.propagate();
13754 }
13755 }
13756
13757 pub fn select_page_down(
13758 &mut self,
13759 _: &SelectPageDown,
13760 window: &mut Window,
13761 cx: &mut Context<Self>,
13762 ) {
13763 let Some(row_count) = self.visible_row_count() else {
13764 return;
13765 };
13766
13767 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13768
13769 let text_layout_details = &self.text_layout_details(window);
13770
13771 self.change_selections(Default::default(), window, cx, |s| {
13772 s.move_heads_with(|map, head, goal| {
13773 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13774 })
13775 })
13776 }
13777
13778 pub fn move_page_down(
13779 &mut self,
13780 action: &MovePageDown,
13781 window: &mut Window,
13782 cx: &mut Context<Self>,
13783 ) {
13784 if self.take_rename(true, window, cx).is_some() {
13785 return;
13786 }
13787
13788 if self
13789 .context_menu
13790 .borrow_mut()
13791 .as_mut()
13792 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13793 .unwrap_or(false)
13794 {
13795 return;
13796 }
13797
13798 if matches!(self.mode, EditorMode::SingleLine) {
13799 cx.propagate();
13800 return;
13801 }
13802
13803 let Some(row_count) = self.visible_row_count() else {
13804 return;
13805 };
13806
13807 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13808
13809 let effects = if action.center_cursor {
13810 SelectionEffects::scroll(Autoscroll::center())
13811 } else {
13812 SelectionEffects::default()
13813 };
13814
13815 let text_layout_details = &self.text_layout_details(window);
13816 self.change_selections(effects, window, cx, |s| {
13817 s.move_with(|map, selection| {
13818 if !selection.is_empty() {
13819 selection.goal = SelectionGoal::None;
13820 }
13821 let (cursor, goal) = movement::down_by_rows(
13822 map,
13823 selection.end,
13824 row_count,
13825 selection.goal,
13826 false,
13827 text_layout_details,
13828 );
13829 selection.collapse_to(cursor, goal);
13830 });
13831 });
13832 }
13833
13834 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13835 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13836 let text_layout_details = &self.text_layout_details(window);
13837 self.change_selections(Default::default(), window, cx, |s| {
13838 s.move_heads_with(|map, head, goal| {
13839 movement::down(map, head, goal, false, text_layout_details)
13840 })
13841 });
13842 }
13843
13844 pub fn context_menu_first(
13845 &mut self,
13846 _: &ContextMenuFirst,
13847 window: &mut Window,
13848 cx: &mut Context<Self>,
13849 ) {
13850 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13851 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13852 }
13853 }
13854
13855 pub fn context_menu_prev(
13856 &mut self,
13857 _: &ContextMenuPrevious,
13858 window: &mut Window,
13859 cx: &mut Context<Self>,
13860 ) {
13861 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13862 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13863 }
13864 }
13865
13866 pub fn context_menu_next(
13867 &mut self,
13868 _: &ContextMenuNext,
13869 window: &mut Window,
13870 cx: &mut Context<Self>,
13871 ) {
13872 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13873 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13874 }
13875 }
13876
13877 pub fn context_menu_last(
13878 &mut self,
13879 _: &ContextMenuLast,
13880 window: &mut Window,
13881 cx: &mut Context<Self>,
13882 ) {
13883 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13884 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13885 }
13886 }
13887
13888 pub fn signature_help_prev(
13889 &mut self,
13890 _: &SignatureHelpPrevious,
13891 _: &mut Window,
13892 cx: &mut Context<Self>,
13893 ) {
13894 if let Some(popover) = self.signature_help_state.popover_mut() {
13895 if popover.current_signature == 0 {
13896 popover.current_signature = popover.signatures.len() - 1;
13897 } else {
13898 popover.current_signature -= 1;
13899 }
13900 cx.notify();
13901 }
13902 }
13903
13904 pub fn signature_help_next(
13905 &mut self,
13906 _: &SignatureHelpNext,
13907 _: &mut Window,
13908 cx: &mut Context<Self>,
13909 ) {
13910 if let Some(popover) = self.signature_help_state.popover_mut() {
13911 if popover.current_signature + 1 == popover.signatures.len() {
13912 popover.current_signature = 0;
13913 } else {
13914 popover.current_signature += 1;
13915 }
13916 cx.notify();
13917 }
13918 }
13919
13920 pub fn move_to_previous_word_start(
13921 &mut self,
13922 _: &MoveToPreviousWordStart,
13923 window: &mut Window,
13924 cx: &mut Context<Self>,
13925 ) {
13926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13927 self.change_selections(Default::default(), window, cx, |s| {
13928 s.move_cursors_with(|map, head, _| {
13929 (
13930 movement::previous_word_start(map, head),
13931 SelectionGoal::None,
13932 )
13933 });
13934 })
13935 }
13936
13937 pub fn move_to_previous_subword_start(
13938 &mut self,
13939 _: &MoveToPreviousSubwordStart,
13940 window: &mut Window,
13941 cx: &mut Context<Self>,
13942 ) {
13943 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13944 self.change_selections(Default::default(), window, cx, |s| {
13945 s.move_cursors_with(|map, head, _| {
13946 (
13947 movement::previous_subword_start(map, head),
13948 SelectionGoal::None,
13949 )
13950 });
13951 })
13952 }
13953
13954 pub fn select_to_previous_word_start(
13955 &mut self,
13956 _: &SelectToPreviousWordStart,
13957 window: &mut Window,
13958 cx: &mut Context<Self>,
13959 ) {
13960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13961 self.change_selections(Default::default(), window, cx, |s| {
13962 s.move_heads_with(|map, head, _| {
13963 (
13964 movement::previous_word_start(map, head),
13965 SelectionGoal::None,
13966 )
13967 });
13968 })
13969 }
13970
13971 pub fn select_to_previous_subword_start(
13972 &mut self,
13973 _: &SelectToPreviousSubwordStart,
13974 window: &mut Window,
13975 cx: &mut Context<Self>,
13976 ) {
13977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13978 self.change_selections(Default::default(), window, cx, |s| {
13979 s.move_heads_with(|map, head, _| {
13980 (
13981 movement::previous_subword_start(map, head),
13982 SelectionGoal::None,
13983 )
13984 });
13985 })
13986 }
13987
13988 pub fn delete_to_previous_word_start(
13989 &mut self,
13990 action: &DeleteToPreviousWordStart,
13991 window: &mut Window,
13992 cx: &mut Context<Self>,
13993 ) {
13994 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13995 self.transact(window, cx, |this, window, cx| {
13996 this.select_autoclose_pair(window, cx);
13997 this.change_selections(Default::default(), window, cx, |s| {
13998 s.move_with(|map, selection| {
13999 if selection.is_empty() {
14000 let mut cursor = if action.ignore_newlines {
14001 movement::previous_word_start(map, selection.head())
14002 } else {
14003 movement::previous_word_start_or_newline(map, selection.head())
14004 };
14005 cursor = movement::adjust_greedy_deletion(
14006 map,
14007 selection.head(),
14008 cursor,
14009 action.ignore_brackets,
14010 );
14011 selection.set_head(cursor, SelectionGoal::None);
14012 }
14013 });
14014 });
14015 this.insert("", window, cx);
14016 });
14017 }
14018
14019 pub fn delete_to_previous_subword_start(
14020 &mut self,
14021 _: &DeleteToPreviousSubwordStart,
14022 window: &mut Window,
14023 cx: &mut Context<Self>,
14024 ) {
14025 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14026 self.transact(window, cx, |this, window, cx| {
14027 this.select_autoclose_pair(window, cx);
14028 this.change_selections(Default::default(), window, cx, |s| {
14029 s.move_with(|map, selection| {
14030 if selection.is_empty() {
14031 let mut cursor = movement::previous_subword_start(map, selection.head());
14032 cursor =
14033 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14034 selection.set_head(cursor, SelectionGoal::None);
14035 }
14036 });
14037 });
14038 this.insert("", window, cx);
14039 });
14040 }
14041
14042 pub fn move_to_next_word_end(
14043 &mut self,
14044 _: &MoveToNextWordEnd,
14045 window: &mut Window,
14046 cx: &mut Context<Self>,
14047 ) {
14048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14049 self.change_selections(Default::default(), window, cx, |s| {
14050 s.move_cursors_with(|map, head, _| {
14051 (movement::next_word_end(map, head), SelectionGoal::None)
14052 });
14053 })
14054 }
14055
14056 pub fn move_to_next_subword_end(
14057 &mut self,
14058 _: &MoveToNextSubwordEnd,
14059 window: &mut Window,
14060 cx: &mut Context<Self>,
14061 ) {
14062 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14063 self.change_selections(Default::default(), window, cx, |s| {
14064 s.move_cursors_with(|map, head, _| {
14065 (movement::next_subword_end(map, head), SelectionGoal::None)
14066 });
14067 })
14068 }
14069
14070 pub fn select_to_next_word_end(
14071 &mut self,
14072 _: &SelectToNextWordEnd,
14073 window: &mut Window,
14074 cx: &mut Context<Self>,
14075 ) {
14076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14077 self.change_selections(Default::default(), window, cx, |s| {
14078 s.move_heads_with(|map, head, _| {
14079 (movement::next_word_end(map, head), SelectionGoal::None)
14080 });
14081 })
14082 }
14083
14084 pub fn select_to_next_subword_end(
14085 &mut self,
14086 _: &SelectToNextSubwordEnd,
14087 window: &mut Window,
14088 cx: &mut Context<Self>,
14089 ) {
14090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14091 self.change_selections(Default::default(), window, cx, |s| {
14092 s.move_heads_with(|map, head, _| {
14093 (movement::next_subword_end(map, head), SelectionGoal::None)
14094 });
14095 })
14096 }
14097
14098 pub fn delete_to_next_word_end(
14099 &mut self,
14100 action: &DeleteToNextWordEnd,
14101 window: &mut Window,
14102 cx: &mut Context<Self>,
14103 ) {
14104 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14105 self.transact(window, cx, |this, window, cx| {
14106 this.change_selections(Default::default(), window, cx, |s| {
14107 s.move_with(|map, selection| {
14108 if selection.is_empty() {
14109 let mut cursor = if action.ignore_newlines {
14110 movement::next_word_end(map, selection.head())
14111 } else {
14112 movement::next_word_end_or_newline(map, selection.head())
14113 };
14114 cursor = movement::adjust_greedy_deletion(
14115 map,
14116 selection.head(),
14117 cursor,
14118 action.ignore_brackets,
14119 );
14120 selection.set_head(cursor, SelectionGoal::None);
14121 }
14122 });
14123 });
14124 this.insert("", window, cx);
14125 });
14126 }
14127
14128 pub fn delete_to_next_subword_end(
14129 &mut self,
14130 _: &DeleteToNextSubwordEnd,
14131 window: &mut Window,
14132 cx: &mut Context<Self>,
14133 ) {
14134 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14135 self.transact(window, cx, |this, window, cx| {
14136 this.change_selections(Default::default(), window, cx, |s| {
14137 s.move_with(|map, selection| {
14138 if selection.is_empty() {
14139 let mut cursor = movement::next_subword_end(map, selection.head());
14140 cursor =
14141 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14142 selection.set_head(cursor, SelectionGoal::None);
14143 }
14144 });
14145 });
14146 this.insert("", window, cx);
14147 });
14148 }
14149
14150 pub fn move_to_beginning_of_line(
14151 &mut self,
14152 action: &MoveToBeginningOfLine,
14153 window: &mut Window,
14154 cx: &mut Context<Self>,
14155 ) {
14156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14157 self.change_selections(Default::default(), window, cx, |s| {
14158 s.move_cursors_with(|map, head, _| {
14159 (
14160 movement::indented_line_beginning(
14161 map,
14162 head,
14163 action.stop_at_soft_wraps,
14164 action.stop_at_indent,
14165 ),
14166 SelectionGoal::None,
14167 )
14168 });
14169 })
14170 }
14171
14172 pub fn select_to_beginning_of_line(
14173 &mut self,
14174 action: &SelectToBeginningOfLine,
14175 window: &mut Window,
14176 cx: &mut Context<Self>,
14177 ) {
14178 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14179 self.change_selections(Default::default(), window, cx, |s| {
14180 s.move_heads_with(|map, head, _| {
14181 (
14182 movement::indented_line_beginning(
14183 map,
14184 head,
14185 action.stop_at_soft_wraps,
14186 action.stop_at_indent,
14187 ),
14188 SelectionGoal::None,
14189 )
14190 });
14191 });
14192 }
14193
14194 pub fn delete_to_beginning_of_line(
14195 &mut self,
14196 action: &DeleteToBeginningOfLine,
14197 window: &mut Window,
14198 cx: &mut Context<Self>,
14199 ) {
14200 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14201 self.transact(window, cx, |this, window, cx| {
14202 this.change_selections(Default::default(), window, cx, |s| {
14203 s.move_with(|_, selection| {
14204 selection.reversed = true;
14205 });
14206 });
14207
14208 this.select_to_beginning_of_line(
14209 &SelectToBeginningOfLine {
14210 stop_at_soft_wraps: false,
14211 stop_at_indent: action.stop_at_indent,
14212 },
14213 window,
14214 cx,
14215 );
14216 this.backspace(&Backspace, window, cx);
14217 });
14218 }
14219
14220 pub fn move_to_end_of_line(
14221 &mut self,
14222 action: &MoveToEndOfLine,
14223 window: &mut Window,
14224 cx: &mut Context<Self>,
14225 ) {
14226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14227 self.change_selections(Default::default(), window, cx, |s| {
14228 s.move_cursors_with(|map, head, _| {
14229 (
14230 movement::line_end(map, head, action.stop_at_soft_wraps),
14231 SelectionGoal::None,
14232 )
14233 });
14234 })
14235 }
14236
14237 pub fn select_to_end_of_line(
14238 &mut self,
14239 action: &SelectToEndOfLine,
14240 window: &mut Window,
14241 cx: &mut Context<Self>,
14242 ) {
14243 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14244 self.change_selections(Default::default(), window, cx, |s| {
14245 s.move_heads_with(|map, head, _| {
14246 (
14247 movement::line_end(map, head, action.stop_at_soft_wraps),
14248 SelectionGoal::None,
14249 )
14250 });
14251 })
14252 }
14253
14254 pub fn delete_to_end_of_line(
14255 &mut self,
14256 _: &DeleteToEndOfLine,
14257 window: &mut Window,
14258 cx: &mut Context<Self>,
14259 ) {
14260 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14261 self.transact(window, cx, |this, window, cx| {
14262 this.select_to_end_of_line(
14263 &SelectToEndOfLine {
14264 stop_at_soft_wraps: false,
14265 },
14266 window,
14267 cx,
14268 );
14269 this.delete(&Delete, window, cx);
14270 });
14271 }
14272
14273 pub fn cut_to_end_of_line(
14274 &mut self,
14275 action: &CutToEndOfLine,
14276 window: &mut Window,
14277 cx: &mut Context<Self>,
14278 ) {
14279 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14280 self.transact(window, cx, |this, window, cx| {
14281 this.select_to_end_of_line(
14282 &SelectToEndOfLine {
14283 stop_at_soft_wraps: false,
14284 },
14285 window,
14286 cx,
14287 );
14288 if !action.stop_at_newlines {
14289 this.change_selections(Default::default(), window, cx, |s| {
14290 s.move_with(|_, sel| {
14291 if sel.is_empty() {
14292 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14293 }
14294 });
14295 });
14296 }
14297 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14298 let item = this.cut_common(false, window, cx);
14299 cx.write_to_clipboard(item);
14300 });
14301 }
14302
14303 pub fn move_to_start_of_paragraph(
14304 &mut self,
14305 _: &MoveToStartOfParagraph,
14306 window: &mut Window,
14307 cx: &mut Context<Self>,
14308 ) {
14309 if matches!(self.mode, EditorMode::SingleLine) {
14310 cx.propagate();
14311 return;
14312 }
14313 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14314 self.change_selections(Default::default(), window, cx, |s| {
14315 s.move_with(|map, selection| {
14316 selection.collapse_to(
14317 movement::start_of_paragraph(map, selection.head(), 1),
14318 SelectionGoal::None,
14319 )
14320 });
14321 })
14322 }
14323
14324 pub fn move_to_end_of_paragraph(
14325 &mut self,
14326 _: &MoveToEndOfParagraph,
14327 window: &mut Window,
14328 cx: &mut Context<Self>,
14329 ) {
14330 if matches!(self.mode, EditorMode::SingleLine) {
14331 cx.propagate();
14332 return;
14333 }
14334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14335 self.change_selections(Default::default(), window, cx, |s| {
14336 s.move_with(|map, selection| {
14337 selection.collapse_to(
14338 movement::end_of_paragraph(map, selection.head(), 1),
14339 SelectionGoal::None,
14340 )
14341 });
14342 })
14343 }
14344
14345 pub fn select_to_start_of_paragraph(
14346 &mut self,
14347 _: &SelectToStartOfParagraph,
14348 window: &mut Window,
14349 cx: &mut Context<Self>,
14350 ) {
14351 if matches!(self.mode, EditorMode::SingleLine) {
14352 cx.propagate();
14353 return;
14354 }
14355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14356 self.change_selections(Default::default(), window, cx, |s| {
14357 s.move_heads_with(|map, head, _| {
14358 (
14359 movement::start_of_paragraph(map, head, 1),
14360 SelectionGoal::None,
14361 )
14362 });
14363 })
14364 }
14365
14366 pub fn select_to_end_of_paragraph(
14367 &mut self,
14368 _: &SelectToEndOfParagraph,
14369 window: &mut Window,
14370 cx: &mut Context<Self>,
14371 ) {
14372 if matches!(self.mode, EditorMode::SingleLine) {
14373 cx.propagate();
14374 return;
14375 }
14376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14377 self.change_selections(Default::default(), window, cx, |s| {
14378 s.move_heads_with(|map, head, _| {
14379 (
14380 movement::end_of_paragraph(map, head, 1),
14381 SelectionGoal::None,
14382 )
14383 });
14384 })
14385 }
14386
14387 pub fn move_to_start_of_excerpt(
14388 &mut self,
14389 _: &MoveToStartOfExcerpt,
14390 window: &mut Window,
14391 cx: &mut Context<Self>,
14392 ) {
14393 if matches!(self.mode, EditorMode::SingleLine) {
14394 cx.propagate();
14395 return;
14396 }
14397 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14398 self.change_selections(Default::default(), window, cx, |s| {
14399 s.move_with(|map, selection| {
14400 selection.collapse_to(
14401 movement::start_of_excerpt(
14402 map,
14403 selection.head(),
14404 workspace::searchable::Direction::Prev,
14405 ),
14406 SelectionGoal::None,
14407 )
14408 });
14409 })
14410 }
14411
14412 pub fn move_to_start_of_next_excerpt(
14413 &mut self,
14414 _: &MoveToStartOfNextExcerpt,
14415 window: &mut Window,
14416 cx: &mut Context<Self>,
14417 ) {
14418 if matches!(self.mode, EditorMode::SingleLine) {
14419 cx.propagate();
14420 return;
14421 }
14422
14423 self.change_selections(Default::default(), window, cx, |s| {
14424 s.move_with(|map, selection| {
14425 selection.collapse_to(
14426 movement::start_of_excerpt(
14427 map,
14428 selection.head(),
14429 workspace::searchable::Direction::Next,
14430 ),
14431 SelectionGoal::None,
14432 )
14433 });
14434 })
14435 }
14436
14437 pub fn move_to_end_of_excerpt(
14438 &mut self,
14439 _: &MoveToEndOfExcerpt,
14440 window: &mut Window,
14441 cx: &mut Context<Self>,
14442 ) {
14443 if matches!(self.mode, EditorMode::SingleLine) {
14444 cx.propagate();
14445 return;
14446 }
14447 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14448 self.change_selections(Default::default(), window, cx, |s| {
14449 s.move_with(|map, selection| {
14450 selection.collapse_to(
14451 movement::end_of_excerpt(
14452 map,
14453 selection.head(),
14454 workspace::searchable::Direction::Next,
14455 ),
14456 SelectionGoal::None,
14457 )
14458 });
14459 })
14460 }
14461
14462 pub fn move_to_end_of_previous_excerpt(
14463 &mut self,
14464 _: &MoveToEndOfPreviousExcerpt,
14465 window: &mut Window,
14466 cx: &mut Context<Self>,
14467 ) {
14468 if matches!(self.mode, EditorMode::SingleLine) {
14469 cx.propagate();
14470 return;
14471 }
14472 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14473 self.change_selections(Default::default(), window, cx, |s| {
14474 s.move_with(|map, selection| {
14475 selection.collapse_to(
14476 movement::end_of_excerpt(
14477 map,
14478 selection.head(),
14479 workspace::searchable::Direction::Prev,
14480 ),
14481 SelectionGoal::None,
14482 )
14483 });
14484 })
14485 }
14486
14487 pub fn select_to_start_of_excerpt(
14488 &mut self,
14489 _: &SelectToStartOfExcerpt,
14490 window: &mut Window,
14491 cx: &mut Context<Self>,
14492 ) {
14493 if matches!(self.mode, EditorMode::SingleLine) {
14494 cx.propagate();
14495 return;
14496 }
14497 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14498 self.change_selections(Default::default(), window, cx, |s| {
14499 s.move_heads_with(|map, head, _| {
14500 (
14501 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14502 SelectionGoal::None,
14503 )
14504 });
14505 })
14506 }
14507
14508 pub fn select_to_start_of_next_excerpt(
14509 &mut self,
14510 _: &SelectToStartOfNextExcerpt,
14511 window: &mut Window,
14512 cx: &mut Context<Self>,
14513 ) {
14514 if matches!(self.mode, EditorMode::SingleLine) {
14515 cx.propagate();
14516 return;
14517 }
14518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14519 self.change_selections(Default::default(), window, cx, |s| {
14520 s.move_heads_with(|map, head, _| {
14521 (
14522 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14523 SelectionGoal::None,
14524 )
14525 });
14526 })
14527 }
14528
14529 pub fn select_to_end_of_excerpt(
14530 &mut self,
14531 _: &SelectToEndOfExcerpt,
14532 window: &mut Window,
14533 cx: &mut Context<Self>,
14534 ) {
14535 if matches!(self.mode, EditorMode::SingleLine) {
14536 cx.propagate();
14537 return;
14538 }
14539 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14540 self.change_selections(Default::default(), window, cx, |s| {
14541 s.move_heads_with(|map, head, _| {
14542 (
14543 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14544 SelectionGoal::None,
14545 )
14546 });
14547 })
14548 }
14549
14550 pub fn select_to_end_of_previous_excerpt(
14551 &mut self,
14552 _: &SelectToEndOfPreviousExcerpt,
14553 window: &mut Window,
14554 cx: &mut Context<Self>,
14555 ) {
14556 if matches!(self.mode, EditorMode::SingleLine) {
14557 cx.propagate();
14558 return;
14559 }
14560 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14561 self.change_selections(Default::default(), window, cx, |s| {
14562 s.move_heads_with(|map, head, _| {
14563 (
14564 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14565 SelectionGoal::None,
14566 )
14567 });
14568 })
14569 }
14570
14571 pub fn move_to_beginning(
14572 &mut self,
14573 _: &MoveToBeginning,
14574 window: &mut Window,
14575 cx: &mut Context<Self>,
14576 ) {
14577 if matches!(self.mode, EditorMode::SingleLine) {
14578 cx.propagate();
14579 return;
14580 }
14581 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14582 self.change_selections(Default::default(), window, cx, |s| {
14583 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14584 });
14585 }
14586
14587 pub fn select_to_beginning(
14588 &mut self,
14589 _: &SelectToBeginning,
14590 window: &mut Window,
14591 cx: &mut Context<Self>,
14592 ) {
14593 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14594 selection.set_head(Point::zero(), SelectionGoal::None);
14595 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14596 self.change_selections(Default::default(), window, cx, |s| {
14597 s.select(vec![selection]);
14598 });
14599 }
14600
14601 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14602 if matches!(self.mode, EditorMode::SingleLine) {
14603 cx.propagate();
14604 return;
14605 }
14606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14607 let cursor = self.buffer.read(cx).read(cx).len();
14608 self.change_selections(Default::default(), window, cx, |s| {
14609 s.select_ranges(vec![cursor..cursor])
14610 });
14611 }
14612
14613 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14614 self.nav_history = nav_history;
14615 }
14616
14617 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14618 self.nav_history.as_ref()
14619 }
14620
14621 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14622 self.push_to_nav_history(
14623 self.selections.newest_anchor().head(),
14624 None,
14625 false,
14626 true,
14627 cx,
14628 );
14629 }
14630
14631 fn push_to_nav_history(
14632 &mut self,
14633 cursor_anchor: Anchor,
14634 new_position: Option<Point>,
14635 is_deactivate: bool,
14636 always: bool,
14637 cx: &mut Context<Self>,
14638 ) {
14639 if let Some(nav_history) = self.nav_history.as_mut() {
14640 let buffer = self.buffer.read(cx).read(cx);
14641 let cursor_position = cursor_anchor.to_point(&buffer);
14642 let scroll_state = self.scroll_manager.anchor();
14643 let scroll_top_row = scroll_state.top_row(&buffer);
14644 drop(buffer);
14645
14646 if let Some(new_position) = new_position {
14647 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14648 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14649 return;
14650 }
14651 }
14652
14653 nav_history.push(
14654 Some(NavigationData {
14655 cursor_anchor,
14656 cursor_position,
14657 scroll_anchor: scroll_state,
14658 scroll_top_row,
14659 }),
14660 cx,
14661 );
14662 cx.emit(EditorEvent::PushedToNavHistory {
14663 anchor: cursor_anchor,
14664 is_deactivate,
14665 })
14666 }
14667 }
14668
14669 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14670 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14671 let buffer = self.buffer.read(cx).snapshot(cx);
14672 let mut selection = self
14673 .selections
14674 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14675 selection.set_head(buffer.len(), SelectionGoal::None);
14676 self.change_selections(Default::default(), window, cx, |s| {
14677 s.select(vec![selection]);
14678 });
14679 }
14680
14681 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14683 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14684 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14685 });
14686 }
14687
14688 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14689 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14690 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14691 let mut selections = self.selections.all::<Point>(&display_map);
14692 let max_point = display_map.buffer_snapshot().max_point();
14693 for selection in &mut selections {
14694 let rows = selection.spanned_rows(true, &display_map);
14695 selection.start = Point::new(rows.start.0, 0);
14696 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14697 selection.reversed = false;
14698 }
14699 self.change_selections(Default::default(), window, cx, |s| {
14700 s.select(selections);
14701 });
14702 }
14703
14704 pub fn split_selection_into_lines(
14705 &mut self,
14706 action: &SplitSelectionIntoLines,
14707 window: &mut Window,
14708 cx: &mut Context<Self>,
14709 ) {
14710 let selections = self
14711 .selections
14712 .all::<Point>(&self.display_snapshot(cx))
14713 .into_iter()
14714 .map(|selection| selection.start..selection.end)
14715 .collect::<Vec<_>>();
14716 self.unfold_ranges(&selections, true, true, cx);
14717
14718 let mut new_selection_ranges = Vec::new();
14719 {
14720 let buffer = self.buffer.read(cx).read(cx);
14721 for selection in selections {
14722 for row in selection.start.row..selection.end.row {
14723 let line_start = Point::new(row, 0);
14724 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14725
14726 if action.keep_selections {
14727 // Keep the selection range for each line
14728 let selection_start = if row == selection.start.row {
14729 selection.start
14730 } else {
14731 line_start
14732 };
14733 new_selection_ranges.push(selection_start..line_end);
14734 } else {
14735 // Collapse to cursor at end of line
14736 new_selection_ranges.push(line_end..line_end);
14737 }
14738 }
14739
14740 let is_multiline_selection = selection.start.row != selection.end.row;
14741 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14742 // so this action feels more ergonomic when paired with other selection operations
14743 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14744 if !should_skip_last {
14745 if action.keep_selections {
14746 if is_multiline_selection {
14747 let line_start = Point::new(selection.end.row, 0);
14748 new_selection_ranges.push(line_start..selection.end);
14749 } else {
14750 new_selection_ranges.push(selection.start..selection.end);
14751 }
14752 } else {
14753 new_selection_ranges.push(selection.end..selection.end);
14754 }
14755 }
14756 }
14757 }
14758 self.change_selections(Default::default(), window, cx, |s| {
14759 s.select_ranges(new_selection_ranges);
14760 });
14761 }
14762
14763 pub fn add_selection_above(
14764 &mut self,
14765 action: &AddSelectionAbove,
14766 window: &mut Window,
14767 cx: &mut Context<Self>,
14768 ) {
14769 self.add_selection(true, action.skip_soft_wrap, window, cx);
14770 }
14771
14772 pub fn add_selection_below(
14773 &mut self,
14774 action: &AddSelectionBelow,
14775 window: &mut Window,
14776 cx: &mut Context<Self>,
14777 ) {
14778 self.add_selection(false, action.skip_soft_wrap, window, cx);
14779 }
14780
14781 fn add_selection(
14782 &mut self,
14783 above: bool,
14784 skip_soft_wrap: bool,
14785 window: &mut Window,
14786 cx: &mut Context<Self>,
14787 ) {
14788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14789
14790 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14791 let all_selections = self.selections.all::<Point>(&display_map);
14792 let text_layout_details = self.text_layout_details(window);
14793
14794 let (mut columnar_selections, new_selections_to_columnarize) = {
14795 if let Some(state) = self.add_selections_state.as_ref() {
14796 let columnar_selection_ids: HashSet<_> = state
14797 .groups
14798 .iter()
14799 .flat_map(|group| group.stack.iter())
14800 .copied()
14801 .collect();
14802
14803 all_selections
14804 .into_iter()
14805 .partition(|s| columnar_selection_ids.contains(&s.id))
14806 } else {
14807 (Vec::new(), all_selections)
14808 }
14809 };
14810
14811 let mut state = self
14812 .add_selections_state
14813 .take()
14814 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14815
14816 for selection in new_selections_to_columnarize {
14817 let range = selection.display_range(&display_map).sorted();
14818 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14819 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14820 let positions = start_x.min(end_x)..start_x.max(end_x);
14821 let mut stack = Vec::new();
14822 for row in range.start.row().0..=range.end.row().0 {
14823 if let Some(selection) = self.selections.build_columnar_selection(
14824 &display_map,
14825 DisplayRow(row),
14826 &positions,
14827 selection.reversed,
14828 &text_layout_details,
14829 ) {
14830 stack.push(selection.id);
14831 columnar_selections.push(selection);
14832 }
14833 }
14834 if !stack.is_empty() {
14835 if above {
14836 stack.reverse();
14837 }
14838 state.groups.push(AddSelectionsGroup { above, stack });
14839 }
14840 }
14841
14842 let mut final_selections = Vec::new();
14843 let end_row = if above {
14844 DisplayRow(0)
14845 } else {
14846 display_map.max_point().row()
14847 };
14848
14849 let mut last_added_item_per_group = HashMap::default();
14850 for group in state.groups.iter_mut() {
14851 if let Some(last_id) = group.stack.last() {
14852 last_added_item_per_group.insert(*last_id, group);
14853 }
14854 }
14855
14856 for selection in columnar_selections {
14857 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14858 if above == group.above {
14859 let range = selection.display_range(&display_map).sorted();
14860 debug_assert_eq!(range.start.row(), range.end.row());
14861 let mut row = range.start.row();
14862 let positions =
14863 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14864 Pixels::from(start)..Pixels::from(end)
14865 } else {
14866 let start_x =
14867 display_map.x_for_display_point(range.start, &text_layout_details);
14868 let end_x =
14869 display_map.x_for_display_point(range.end, &text_layout_details);
14870 start_x.min(end_x)..start_x.max(end_x)
14871 };
14872
14873 let mut maybe_new_selection = None;
14874 let direction = if above { -1 } else { 1 };
14875
14876 while row != end_row {
14877 if skip_soft_wrap {
14878 row = display_map
14879 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14880 .row();
14881 } else if above {
14882 row.0 -= 1;
14883 } else {
14884 row.0 += 1;
14885 }
14886
14887 if let Some(new_selection) = self.selections.build_columnar_selection(
14888 &display_map,
14889 row,
14890 &positions,
14891 selection.reversed,
14892 &text_layout_details,
14893 ) {
14894 maybe_new_selection = Some(new_selection);
14895 break;
14896 }
14897 }
14898
14899 if let Some(new_selection) = maybe_new_selection {
14900 group.stack.push(new_selection.id);
14901 if above {
14902 final_selections.push(new_selection);
14903 final_selections.push(selection);
14904 } else {
14905 final_selections.push(selection);
14906 final_selections.push(new_selection);
14907 }
14908 } else {
14909 final_selections.push(selection);
14910 }
14911 } else {
14912 group.stack.pop();
14913 }
14914 } else {
14915 final_selections.push(selection);
14916 }
14917 }
14918
14919 self.change_selections(Default::default(), window, cx, |s| {
14920 s.select(final_selections);
14921 });
14922
14923 let final_selection_ids: HashSet<_> = self
14924 .selections
14925 .all::<Point>(&display_map)
14926 .iter()
14927 .map(|s| s.id)
14928 .collect();
14929 state.groups.retain_mut(|group| {
14930 // selections might get merged above so we remove invalid items from stacks
14931 group.stack.retain(|id| final_selection_ids.contains(id));
14932
14933 // single selection in stack can be treated as initial state
14934 group.stack.len() > 1
14935 });
14936
14937 if !state.groups.is_empty() {
14938 self.add_selections_state = Some(state);
14939 }
14940 }
14941
14942 pub fn insert_snippet_at_selections(
14943 &mut self,
14944 action: &InsertSnippet,
14945 window: &mut Window,
14946 cx: &mut Context<Self>,
14947 ) {
14948 self.try_insert_snippet_at_selections(action, window, cx)
14949 .log_err();
14950 }
14951
14952 fn try_insert_snippet_at_selections(
14953 &mut self,
14954 action: &InsertSnippet,
14955 window: &mut Window,
14956 cx: &mut Context<Self>,
14957 ) -> Result<()> {
14958 let insertion_ranges = self
14959 .selections
14960 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
14961 .into_iter()
14962 .map(|selection| selection.range())
14963 .collect_vec();
14964
14965 let snippet = if let Some(snippet_body) = &action.snippet {
14966 if action.language.is_none() && action.name.is_none() {
14967 Snippet::parse(snippet_body)?
14968 } else {
14969 bail!("`snippet` is mutually exclusive with `language` and `name`")
14970 }
14971 } else if let Some(name) = &action.name {
14972 let project = self.project().context("no project")?;
14973 let snippet_store = project.read(cx).snippets().read(cx);
14974 let snippet = snippet_store
14975 .snippets_for(action.language.clone(), cx)
14976 .into_iter()
14977 .find(|snippet| snippet.name == *name)
14978 .context("snippet not found")?;
14979 Snippet::parse(&snippet.body)?
14980 } else {
14981 // todo(andrew): open modal to select snippet
14982 bail!("`name` or `snippet` is required")
14983 };
14984
14985 self.insert_snippet(&insertion_ranges, snippet, window, cx)
14986 }
14987
14988 fn select_match_ranges(
14989 &mut self,
14990 range: Range<MultiBufferOffset>,
14991 reversed: bool,
14992 replace_newest: bool,
14993 auto_scroll: Option<Autoscroll>,
14994 window: &mut Window,
14995 cx: &mut Context<Editor>,
14996 ) {
14997 self.unfold_ranges(
14998 std::slice::from_ref(&range),
14999 false,
15000 auto_scroll.is_some(),
15001 cx,
15002 );
15003 let effects = if let Some(scroll) = auto_scroll {
15004 SelectionEffects::scroll(scroll)
15005 } else {
15006 SelectionEffects::no_scroll()
15007 };
15008 self.change_selections(effects, window, cx, |s| {
15009 if replace_newest {
15010 s.delete(s.newest_anchor().id);
15011 }
15012 if reversed {
15013 s.insert_range(range.end..range.start);
15014 } else {
15015 s.insert_range(range);
15016 }
15017 });
15018 }
15019
15020 pub fn select_next_match_internal(
15021 &mut self,
15022 display_map: &DisplaySnapshot,
15023 replace_newest: bool,
15024 autoscroll: Option<Autoscroll>,
15025 window: &mut Window,
15026 cx: &mut Context<Self>,
15027 ) -> Result<()> {
15028 let buffer = display_map.buffer_snapshot();
15029 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15030 if let Some(mut select_next_state) = self.select_next_state.take() {
15031 let query = &select_next_state.query;
15032 if !select_next_state.done {
15033 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15034 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15035 let mut next_selected_range = None;
15036
15037 let bytes_after_last_selection =
15038 buffer.bytes_in_range(last_selection.end..buffer.len());
15039 let bytes_before_first_selection =
15040 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15041 let query_matches = query
15042 .stream_find_iter(bytes_after_last_selection)
15043 .map(|result| (last_selection.end, result))
15044 .chain(
15045 query
15046 .stream_find_iter(bytes_before_first_selection)
15047 .map(|result| (MultiBufferOffset(0), result)),
15048 );
15049
15050 for (start_offset, query_match) in query_matches {
15051 let query_match = query_match.unwrap(); // can only fail due to I/O
15052 let offset_range =
15053 start_offset + query_match.start()..start_offset + query_match.end();
15054
15055 if !select_next_state.wordwise
15056 || (!buffer.is_inside_word(offset_range.start, None)
15057 && !buffer.is_inside_word(offset_range.end, None))
15058 {
15059 let idx = selections
15060 .partition_point(|selection| selection.end <= offset_range.start);
15061 let overlaps = selections
15062 .get(idx)
15063 .map_or(false, |selection| selection.start < offset_range.end);
15064
15065 if !overlaps {
15066 next_selected_range = Some(offset_range);
15067 break;
15068 }
15069 }
15070 }
15071
15072 if let Some(next_selected_range) = next_selected_range {
15073 self.select_match_ranges(
15074 next_selected_range,
15075 last_selection.reversed,
15076 replace_newest,
15077 autoscroll,
15078 window,
15079 cx,
15080 );
15081 } else {
15082 select_next_state.done = true;
15083 }
15084 }
15085
15086 self.select_next_state = Some(select_next_state);
15087 } else {
15088 let mut only_carets = true;
15089 let mut same_text_selected = true;
15090 let mut selected_text = None;
15091
15092 let mut selections_iter = selections.iter().peekable();
15093 while let Some(selection) = selections_iter.next() {
15094 if selection.start != selection.end {
15095 only_carets = false;
15096 }
15097
15098 if same_text_selected {
15099 if selected_text.is_none() {
15100 selected_text =
15101 Some(buffer.text_for_range(selection.range()).collect::<String>());
15102 }
15103
15104 if let Some(next_selection) = selections_iter.peek() {
15105 if next_selection.len() == selection.len() {
15106 let next_selected_text = buffer
15107 .text_for_range(next_selection.range())
15108 .collect::<String>();
15109 if Some(next_selected_text) != selected_text {
15110 same_text_selected = false;
15111 selected_text = None;
15112 }
15113 } else {
15114 same_text_selected = false;
15115 selected_text = None;
15116 }
15117 }
15118 }
15119 }
15120
15121 if only_carets {
15122 for selection in &mut selections {
15123 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15124 selection.start = word_range.start;
15125 selection.end = word_range.end;
15126 selection.goal = SelectionGoal::None;
15127 selection.reversed = false;
15128 self.select_match_ranges(
15129 selection.start..selection.end,
15130 selection.reversed,
15131 replace_newest,
15132 autoscroll,
15133 window,
15134 cx,
15135 );
15136 }
15137
15138 if selections.len() == 1 {
15139 let selection = selections
15140 .last()
15141 .expect("ensured that there's only one selection");
15142 let query = buffer
15143 .text_for_range(selection.start..selection.end)
15144 .collect::<String>();
15145 let is_empty = query.is_empty();
15146 let select_state = SelectNextState {
15147 query: self.build_query(&[query], cx)?,
15148 wordwise: true,
15149 done: is_empty,
15150 };
15151 self.select_next_state = Some(select_state);
15152 } else {
15153 self.select_next_state = None;
15154 }
15155 } else if let Some(selected_text) = selected_text {
15156 self.select_next_state = Some(SelectNextState {
15157 query: self.build_query(&[selected_text], cx)?,
15158 wordwise: false,
15159 done: false,
15160 });
15161 self.select_next_match_internal(
15162 display_map,
15163 replace_newest,
15164 autoscroll,
15165 window,
15166 cx,
15167 )?;
15168 }
15169 }
15170 Ok(())
15171 }
15172
15173 pub fn select_all_matches(
15174 &mut self,
15175 _action: &SelectAllMatches,
15176 window: &mut Window,
15177 cx: &mut Context<Self>,
15178 ) -> Result<()> {
15179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15180
15181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15182
15183 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15184 let Some(select_next_state) = self.select_next_state.as_mut() else {
15185 return Ok(());
15186 };
15187 if select_next_state.done {
15188 return Ok(());
15189 }
15190
15191 let mut new_selections = Vec::new();
15192
15193 let reversed = self
15194 .selections
15195 .oldest::<MultiBufferOffset>(&display_map)
15196 .reversed;
15197 let buffer = display_map.buffer_snapshot();
15198 let query_matches = select_next_state
15199 .query
15200 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15201
15202 for query_match in query_matches.into_iter() {
15203 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15204 let offset_range = if reversed {
15205 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15206 } else {
15207 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15208 };
15209
15210 if !select_next_state.wordwise
15211 || (!buffer.is_inside_word(offset_range.start, None)
15212 && !buffer.is_inside_word(offset_range.end, None))
15213 {
15214 new_selections.push(offset_range.start..offset_range.end);
15215 }
15216 }
15217
15218 select_next_state.done = true;
15219
15220 if new_selections.is_empty() {
15221 log::error!("bug: new_selections is empty in select_all_matches");
15222 return Ok(());
15223 }
15224
15225 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15226 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15227 selections.select_ranges(new_selections)
15228 });
15229
15230 Ok(())
15231 }
15232
15233 pub fn select_next(
15234 &mut self,
15235 action: &SelectNext,
15236 window: &mut Window,
15237 cx: &mut Context<Self>,
15238 ) -> Result<()> {
15239 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15240 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15241 self.select_next_match_internal(
15242 &display_map,
15243 action.replace_newest,
15244 Some(Autoscroll::newest()),
15245 window,
15246 cx,
15247 )?;
15248 Ok(())
15249 }
15250
15251 pub fn select_previous(
15252 &mut self,
15253 action: &SelectPrevious,
15254 window: &mut Window,
15255 cx: &mut Context<Self>,
15256 ) -> Result<()> {
15257 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15258 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15259 let buffer = display_map.buffer_snapshot();
15260 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15261 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15262 let query = &select_prev_state.query;
15263 if !select_prev_state.done {
15264 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15265 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15266 let mut next_selected_range = None;
15267 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15268 let bytes_before_last_selection =
15269 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15270 let bytes_after_first_selection =
15271 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15272 let query_matches = query
15273 .stream_find_iter(bytes_before_last_selection)
15274 .map(|result| (last_selection.start, result))
15275 .chain(
15276 query
15277 .stream_find_iter(bytes_after_first_selection)
15278 .map(|result| (buffer.len(), result)),
15279 );
15280 for (end_offset, query_match) in query_matches {
15281 let query_match = query_match.unwrap(); // can only fail due to I/O
15282 let offset_range =
15283 end_offset - query_match.end()..end_offset - query_match.start();
15284
15285 if !select_prev_state.wordwise
15286 || (!buffer.is_inside_word(offset_range.start, None)
15287 && !buffer.is_inside_word(offset_range.end, None))
15288 {
15289 next_selected_range = Some(offset_range);
15290 break;
15291 }
15292 }
15293
15294 if let Some(next_selected_range) = next_selected_range {
15295 self.select_match_ranges(
15296 next_selected_range,
15297 last_selection.reversed,
15298 action.replace_newest,
15299 Some(Autoscroll::newest()),
15300 window,
15301 cx,
15302 );
15303 } else {
15304 select_prev_state.done = true;
15305 }
15306 }
15307
15308 self.select_prev_state = Some(select_prev_state);
15309 } else {
15310 let mut only_carets = true;
15311 let mut same_text_selected = true;
15312 let mut selected_text = None;
15313
15314 let mut selections_iter = selections.iter().peekable();
15315 while let Some(selection) = selections_iter.next() {
15316 if selection.start != selection.end {
15317 only_carets = false;
15318 }
15319
15320 if same_text_selected {
15321 if selected_text.is_none() {
15322 selected_text =
15323 Some(buffer.text_for_range(selection.range()).collect::<String>());
15324 }
15325
15326 if let Some(next_selection) = selections_iter.peek() {
15327 if next_selection.len() == selection.len() {
15328 let next_selected_text = buffer
15329 .text_for_range(next_selection.range())
15330 .collect::<String>();
15331 if Some(next_selected_text) != selected_text {
15332 same_text_selected = false;
15333 selected_text = None;
15334 }
15335 } else {
15336 same_text_selected = false;
15337 selected_text = None;
15338 }
15339 }
15340 }
15341 }
15342
15343 if only_carets {
15344 for selection in &mut selections {
15345 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15346 selection.start = word_range.start;
15347 selection.end = word_range.end;
15348 selection.goal = SelectionGoal::None;
15349 selection.reversed = false;
15350 self.select_match_ranges(
15351 selection.start..selection.end,
15352 selection.reversed,
15353 action.replace_newest,
15354 Some(Autoscroll::newest()),
15355 window,
15356 cx,
15357 );
15358 }
15359 if selections.len() == 1 {
15360 let selection = selections
15361 .last()
15362 .expect("ensured that there's only one selection");
15363 let query = buffer
15364 .text_for_range(selection.start..selection.end)
15365 .collect::<String>();
15366 let is_empty = query.is_empty();
15367 let select_state = SelectNextState {
15368 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15369 wordwise: true,
15370 done: is_empty,
15371 };
15372 self.select_prev_state = Some(select_state);
15373 } else {
15374 self.select_prev_state = None;
15375 }
15376 } else if let Some(selected_text) = selected_text {
15377 self.select_prev_state = Some(SelectNextState {
15378 query: self
15379 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15380 wordwise: false,
15381 done: false,
15382 });
15383 self.select_previous(action, window, cx)?;
15384 }
15385 }
15386 Ok(())
15387 }
15388
15389 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15390 /// setting the case sensitivity based on the global
15391 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15392 /// editor's settings.
15393 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15394 where
15395 I: IntoIterator<Item = P>,
15396 P: AsRef<[u8]>,
15397 {
15398 let case_sensitive = self
15399 .select_next_is_case_sensitive
15400 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15401
15402 let mut builder = AhoCorasickBuilder::new();
15403 builder.ascii_case_insensitive(!case_sensitive);
15404 builder.build(patterns)
15405 }
15406
15407 pub fn find_next_match(
15408 &mut self,
15409 _: &FindNextMatch,
15410 window: &mut Window,
15411 cx: &mut Context<Self>,
15412 ) -> Result<()> {
15413 let selections = self.selections.disjoint_anchors_arc();
15414 match selections.first() {
15415 Some(first) if selections.len() >= 2 => {
15416 self.change_selections(Default::default(), window, cx, |s| {
15417 s.select_ranges([first.range()]);
15418 });
15419 }
15420 _ => self.select_next(
15421 &SelectNext {
15422 replace_newest: true,
15423 },
15424 window,
15425 cx,
15426 )?,
15427 }
15428 Ok(())
15429 }
15430
15431 pub fn find_previous_match(
15432 &mut self,
15433 _: &FindPreviousMatch,
15434 window: &mut Window,
15435 cx: &mut Context<Self>,
15436 ) -> Result<()> {
15437 let selections = self.selections.disjoint_anchors_arc();
15438 match selections.last() {
15439 Some(last) if selections.len() >= 2 => {
15440 self.change_selections(Default::default(), window, cx, |s| {
15441 s.select_ranges([last.range()]);
15442 });
15443 }
15444 _ => self.select_previous(
15445 &SelectPrevious {
15446 replace_newest: true,
15447 },
15448 window,
15449 cx,
15450 )?,
15451 }
15452 Ok(())
15453 }
15454
15455 pub fn toggle_comments(
15456 &mut self,
15457 action: &ToggleComments,
15458 window: &mut Window,
15459 cx: &mut Context<Self>,
15460 ) {
15461 if self.read_only(cx) {
15462 return;
15463 }
15464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15465 let text_layout_details = &self.text_layout_details(window);
15466 self.transact(window, cx, |this, window, cx| {
15467 let mut selections = this
15468 .selections
15469 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15470 let mut edits = Vec::new();
15471 let mut selection_edit_ranges = Vec::new();
15472 let mut last_toggled_row = None;
15473 let snapshot = this.buffer.read(cx).read(cx);
15474 let empty_str: Arc<str> = Arc::default();
15475 let mut suffixes_inserted = Vec::new();
15476 let ignore_indent = action.ignore_indent;
15477
15478 fn comment_prefix_range(
15479 snapshot: &MultiBufferSnapshot,
15480 row: MultiBufferRow,
15481 comment_prefix: &str,
15482 comment_prefix_whitespace: &str,
15483 ignore_indent: bool,
15484 ) -> Range<Point> {
15485 let indent_size = if ignore_indent {
15486 0
15487 } else {
15488 snapshot.indent_size_for_line(row).len
15489 };
15490
15491 let start = Point::new(row.0, indent_size);
15492
15493 let mut line_bytes = snapshot
15494 .bytes_in_range(start..snapshot.max_point())
15495 .flatten()
15496 .copied();
15497
15498 // If this line currently begins with the line comment prefix, then record
15499 // the range containing the prefix.
15500 if line_bytes
15501 .by_ref()
15502 .take(comment_prefix.len())
15503 .eq(comment_prefix.bytes())
15504 {
15505 // Include any whitespace that matches the comment prefix.
15506 let matching_whitespace_len = line_bytes
15507 .zip(comment_prefix_whitespace.bytes())
15508 .take_while(|(a, b)| a == b)
15509 .count() as u32;
15510 let end = Point::new(
15511 start.row,
15512 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15513 );
15514 start..end
15515 } else {
15516 start..start
15517 }
15518 }
15519
15520 fn comment_suffix_range(
15521 snapshot: &MultiBufferSnapshot,
15522 row: MultiBufferRow,
15523 comment_suffix: &str,
15524 comment_suffix_has_leading_space: bool,
15525 ) -> Range<Point> {
15526 let end = Point::new(row.0, snapshot.line_len(row));
15527 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15528
15529 let mut line_end_bytes = snapshot
15530 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15531 .flatten()
15532 .copied();
15533
15534 let leading_space_len = if suffix_start_column > 0
15535 && line_end_bytes.next() == Some(b' ')
15536 && comment_suffix_has_leading_space
15537 {
15538 1
15539 } else {
15540 0
15541 };
15542
15543 // If this line currently begins with the line comment prefix, then record
15544 // the range containing the prefix.
15545 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15546 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15547 start..end
15548 } else {
15549 end..end
15550 }
15551 }
15552
15553 // TODO: Handle selections that cross excerpts
15554 for selection in &mut selections {
15555 let start_column = snapshot
15556 .indent_size_for_line(MultiBufferRow(selection.start.row))
15557 .len;
15558 let language = if let Some(language) =
15559 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15560 {
15561 language
15562 } else {
15563 continue;
15564 };
15565
15566 selection_edit_ranges.clear();
15567
15568 // If multiple selections contain a given row, avoid processing that
15569 // row more than once.
15570 let mut start_row = MultiBufferRow(selection.start.row);
15571 if last_toggled_row == Some(start_row) {
15572 start_row = start_row.next_row();
15573 }
15574 let end_row =
15575 if selection.end.row > selection.start.row && selection.end.column == 0 {
15576 MultiBufferRow(selection.end.row - 1)
15577 } else {
15578 MultiBufferRow(selection.end.row)
15579 };
15580 last_toggled_row = Some(end_row);
15581
15582 if start_row > end_row {
15583 continue;
15584 }
15585
15586 // If the language has line comments, toggle those.
15587 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15588
15589 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15590 if ignore_indent {
15591 full_comment_prefixes = full_comment_prefixes
15592 .into_iter()
15593 .map(|s| Arc::from(s.trim_end()))
15594 .collect();
15595 }
15596
15597 if !full_comment_prefixes.is_empty() {
15598 let first_prefix = full_comment_prefixes
15599 .first()
15600 .expect("prefixes is non-empty");
15601 let prefix_trimmed_lengths = full_comment_prefixes
15602 .iter()
15603 .map(|p| p.trim_end_matches(' ').len())
15604 .collect::<SmallVec<[usize; 4]>>();
15605
15606 let mut all_selection_lines_are_comments = true;
15607
15608 for row in start_row.0..=end_row.0 {
15609 let row = MultiBufferRow(row);
15610 if start_row < end_row && snapshot.is_line_blank(row) {
15611 continue;
15612 }
15613
15614 let prefix_range = full_comment_prefixes
15615 .iter()
15616 .zip(prefix_trimmed_lengths.iter().copied())
15617 .map(|(prefix, trimmed_prefix_len)| {
15618 comment_prefix_range(
15619 snapshot.deref(),
15620 row,
15621 &prefix[..trimmed_prefix_len],
15622 &prefix[trimmed_prefix_len..],
15623 ignore_indent,
15624 )
15625 })
15626 .max_by_key(|range| range.end.column - range.start.column)
15627 .expect("prefixes is non-empty");
15628
15629 if prefix_range.is_empty() {
15630 all_selection_lines_are_comments = false;
15631 }
15632
15633 selection_edit_ranges.push(prefix_range);
15634 }
15635
15636 if all_selection_lines_are_comments {
15637 edits.extend(
15638 selection_edit_ranges
15639 .iter()
15640 .cloned()
15641 .map(|range| (range, empty_str.clone())),
15642 );
15643 } else {
15644 let min_column = selection_edit_ranges
15645 .iter()
15646 .map(|range| range.start.column)
15647 .min()
15648 .unwrap_or(0);
15649 edits.extend(selection_edit_ranges.iter().map(|range| {
15650 let position = Point::new(range.start.row, min_column);
15651 (position..position, first_prefix.clone())
15652 }));
15653 }
15654 } else if let Some(BlockCommentConfig {
15655 start: full_comment_prefix,
15656 end: comment_suffix,
15657 ..
15658 }) = language.block_comment()
15659 {
15660 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15661 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15662 let prefix_range = comment_prefix_range(
15663 snapshot.deref(),
15664 start_row,
15665 comment_prefix,
15666 comment_prefix_whitespace,
15667 ignore_indent,
15668 );
15669 let suffix_range = comment_suffix_range(
15670 snapshot.deref(),
15671 end_row,
15672 comment_suffix.trim_start_matches(' '),
15673 comment_suffix.starts_with(' '),
15674 );
15675
15676 if prefix_range.is_empty() || suffix_range.is_empty() {
15677 edits.push((
15678 prefix_range.start..prefix_range.start,
15679 full_comment_prefix.clone(),
15680 ));
15681 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15682 suffixes_inserted.push((end_row, comment_suffix.len()));
15683 } else {
15684 edits.push((prefix_range, empty_str.clone()));
15685 edits.push((suffix_range, empty_str.clone()));
15686 }
15687 } else {
15688 continue;
15689 }
15690 }
15691
15692 drop(snapshot);
15693 this.buffer.update(cx, |buffer, cx| {
15694 buffer.edit(edits, None, cx);
15695 });
15696
15697 // Adjust selections so that they end before any comment suffixes that
15698 // were inserted.
15699 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15700 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15701 let snapshot = this.buffer.read(cx).read(cx);
15702 for selection in &mut selections {
15703 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15704 match row.cmp(&MultiBufferRow(selection.end.row)) {
15705 Ordering::Less => {
15706 suffixes_inserted.next();
15707 continue;
15708 }
15709 Ordering::Greater => break,
15710 Ordering::Equal => {
15711 if selection.end.column == snapshot.line_len(row) {
15712 if selection.is_empty() {
15713 selection.start.column -= suffix_len as u32;
15714 }
15715 selection.end.column -= suffix_len as u32;
15716 }
15717 break;
15718 }
15719 }
15720 }
15721 }
15722
15723 drop(snapshot);
15724 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15725
15726 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15727 let selections_on_single_row = selections.windows(2).all(|selections| {
15728 selections[0].start.row == selections[1].start.row
15729 && selections[0].end.row == selections[1].end.row
15730 && selections[0].start.row == selections[0].end.row
15731 });
15732 let selections_selecting = selections
15733 .iter()
15734 .any(|selection| selection.start != selection.end);
15735 let advance_downwards = action.advance_downwards
15736 && selections_on_single_row
15737 && !selections_selecting
15738 && !matches!(this.mode, EditorMode::SingleLine);
15739
15740 if advance_downwards {
15741 let snapshot = this.buffer.read(cx).snapshot(cx);
15742
15743 this.change_selections(Default::default(), window, cx, |s| {
15744 s.move_cursors_with(|display_snapshot, display_point, _| {
15745 let mut point = display_point.to_point(display_snapshot);
15746 point.row += 1;
15747 point = snapshot.clip_point(point, Bias::Left);
15748 let display_point = point.to_display_point(display_snapshot);
15749 let goal = SelectionGoal::HorizontalPosition(
15750 display_snapshot
15751 .x_for_display_point(display_point, text_layout_details)
15752 .into(),
15753 );
15754 (display_point, goal)
15755 })
15756 });
15757 }
15758 });
15759 }
15760
15761 pub fn select_enclosing_symbol(
15762 &mut self,
15763 _: &SelectEnclosingSymbol,
15764 window: &mut Window,
15765 cx: &mut Context<Self>,
15766 ) {
15767 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15768
15769 let buffer = self.buffer.read(cx).snapshot(cx);
15770 let old_selections = self
15771 .selections
15772 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15773 .into_boxed_slice();
15774
15775 fn update_selection(
15776 selection: &Selection<MultiBufferOffset>,
15777 buffer_snap: &MultiBufferSnapshot,
15778 ) -> Option<Selection<MultiBufferOffset>> {
15779 let cursor = selection.head();
15780 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15781 for symbol in symbols.iter().rev() {
15782 let start = symbol.range.start.to_offset(buffer_snap);
15783 let end = symbol.range.end.to_offset(buffer_snap);
15784 let new_range = start..end;
15785 if start < selection.start || end > selection.end {
15786 return Some(Selection {
15787 id: selection.id,
15788 start: new_range.start,
15789 end: new_range.end,
15790 goal: SelectionGoal::None,
15791 reversed: selection.reversed,
15792 });
15793 }
15794 }
15795 None
15796 }
15797
15798 let mut selected_larger_symbol = false;
15799 let new_selections = old_selections
15800 .iter()
15801 .map(|selection| match update_selection(selection, &buffer) {
15802 Some(new_selection) => {
15803 if new_selection.range() != selection.range() {
15804 selected_larger_symbol = true;
15805 }
15806 new_selection
15807 }
15808 None => selection.clone(),
15809 })
15810 .collect::<Vec<_>>();
15811
15812 if selected_larger_symbol {
15813 self.change_selections(Default::default(), window, cx, |s| {
15814 s.select(new_selections);
15815 });
15816 }
15817 }
15818
15819 pub fn select_larger_syntax_node(
15820 &mut self,
15821 _: &SelectLargerSyntaxNode,
15822 window: &mut Window,
15823 cx: &mut Context<Self>,
15824 ) {
15825 let Some(visible_row_count) = self.visible_row_count() else {
15826 return;
15827 };
15828 let old_selections: Box<[_]> = self
15829 .selections
15830 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15831 .into();
15832 if old_selections.is_empty() {
15833 return;
15834 }
15835
15836 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15837
15838 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15839 let buffer = self.buffer.read(cx).snapshot(cx);
15840
15841 let mut selected_larger_node = false;
15842 let mut new_selections = old_selections
15843 .iter()
15844 .map(|selection| {
15845 let old_range = selection.start..selection.end;
15846
15847 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15848 // manually select word at selection
15849 if ["string_content", "inline"].contains(&node.kind()) {
15850 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15851 // ignore if word is already selected
15852 if !word_range.is_empty() && old_range != word_range {
15853 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15854 // only select word if start and end point belongs to same word
15855 if word_range == last_word_range {
15856 selected_larger_node = true;
15857 return Selection {
15858 id: selection.id,
15859 start: word_range.start,
15860 end: word_range.end,
15861 goal: SelectionGoal::None,
15862 reversed: selection.reversed,
15863 };
15864 }
15865 }
15866 }
15867 }
15868
15869 let mut new_range = old_range.clone();
15870 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15871 new_range = range;
15872 if !node.is_named() {
15873 continue;
15874 }
15875 if !display_map.intersects_fold(new_range.start)
15876 && !display_map.intersects_fold(new_range.end)
15877 {
15878 break;
15879 }
15880 }
15881
15882 selected_larger_node |= new_range != old_range;
15883 Selection {
15884 id: selection.id,
15885 start: new_range.start,
15886 end: new_range.end,
15887 goal: SelectionGoal::None,
15888 reversed: selection.reversed,
15889 }
15890 })
15891 .collect::<Vec<_>>();
15892
15893 if !selected_larger_node {
15894 return; // don't put this call in the history
15895 }
15896
15897 // scroll based on transformation done to the last selection created by the user
15898 let (last_old, last_new) = old_selections
15899 .last()
15900 .zip(new_selections.last().cloned())
15901 .expect("old_selections isn't empty");
15902
15903 // revert selection
15904 let is_selection_reversed = {
15905 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15906 new_selections.last_mut().expect("checked above").reversed =
15907 should_newest_selection_be_reversed;
15908 should_newest_selection_be_reversed
15909 };
15910
15911 if selected_larger_node {
15912 self.select_syntax_node_history.disable_clearing = true;
15913 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15914 s.select(new_selections.clone());
15915 });
15916 self.select_syntax_node_history.disable_clearing = false;
15917 }
15918
15919 let start_row = last_new.start.to_display_point(&display_map).row().0;
15920 let end_row = last_new.end.to_display_point(&display_map).row().0;
15921 let selection_height = end_row - start_row + 1;
15922 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15923
15924 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15925 let scroll_behavior = if fits_on_the_screen {
15926 self.request_autoscroll(Autoscroll::fit(), cx);
15927 SelectSyntaxNodeScrollBehavior::FitSelection
15928 } else if is_selection_reversed {
15929 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15930 SelectSyntaxNodeScrollBehavior::CursorTop
15931 } else {
15932 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15933 SelectSyntaxNodeScrollBehavior::CursorBottom
15934 };
15935
15936 self.select_syntax_node_history.push((
15937 old_selections,
15938 scroll_behavior,
15939 is_selection_reversed,
15940 ));
15941 }
15942
15943 pub fn select_smaller_syntax_node(
15944 &mut self,
15945 _: &SelectSmallerSyntaxNode,
15946 window: &mut Window,
15947 cx: &mut Context<Self>,
15948 ) {
15949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15950
15951 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15952 self.select_syntax_node_history.pop()
15953 {
15954 if let Some(selection) = selections.last_mut() {
15955 selection.reversed = is_selection_reversed;
15956 }
15957
15958 self.select_syntax_node_history.disable_clearing = true;
15959 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15960 s.select(selections.to_vec());
15961 });
15962 self.select_syntax_node_history.disable_clearing = false;
15963
15964 match scroll_behavior {
15965 SelectSyntaxNodeScrollBehavior::CursorTop => {
15966 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15967 }
15968 SelectSyntaxNodeScrollBehavior::FitSelection => {
15969 self.request_autoscroll(Autoscroll::fit(), cx);
15970 }
15971 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15972 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15973 }
15974 }
15975 }
15976 }
15977
15978 pub fn unwrap_syntax_node(
15979 &mut self,
15980 _: &UnwrapSyntaxNode,
15981 window: &mut Window,
15982 cx: &mut Context<Self>,
15983 ) {
15984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15985
15986 let buffer = self.buffer.read(cx).snapshot(cx);
15987 let selections = self
15988 .selections
15989 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15990 .into_iter()
15991 // subtracting the offset requires sorting
15992 .sorted_by_key(|i| i.start);
15993
15994 let full_edits = selections
15995 .into_iter()
15996 .filter_map(|selection| {
15997 let child = if selection.is_empty()
15998 && let Some((_, ancestor_range)) =
15999 buffer.syntax_ancestor(selection.start..selection.end)
16000 {
16001 ancestor_range
16002 } else {
16003 selection.range()
16004 };
16005
16006 let mut parent = child.clone();
16007 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16008 parent = ancestor_range;
16009 if parent.start < child.start || parent.end > child.end {
16010 break;
16011 }
16012 }
16013
16014 if parent == child {
16015 return None;
16016 }
16017 let text = buffer.text_for_range(child).collect::<String>();
16018 Some((selection.id, parent, text))
16019 })
16020 .collect::<Vec<_>>();
16021 if full_edits.is_empty() {
16022 return;
16023 }
16024
16025 self.transact(window, cx, |this, window, cx| {
16026 this.buffer.update(cx, |buffer, cx| {
16027 buffer.edit(
16028 full_edits
16029 .iter()
16030 .map(|(_, p, t)| (p.clone(), t.clone()))
16031 .collect::<Vec<_>>(),
16032 None,
16033 cx,
16034 );
16035 });
16036 this.change_selections(Default::default(), window, cx, |s| {
16037 let mut offset = 0;
16038 let mut selections = vec![];
16039 for (id, parent, text) in full_edits {
16040 let start = parent.start - offset;
16041 offset += (parent.end - parent.start) - text.len();
16042 selections.push(Selection {
16043 id,
16044 start,
16045 end: start + text.len(),
16046 reversed: false,
16047 goal: Default::default(),
16048 });
16049 }
16050 s.select(selections);
16051 });
16052 });
16053 }
16054
16055 pub fn select_next_syntax_node(
16056 &mut self,
16057 _: &SelectNextSyntaxNode,
16058 window: &mut Window,
16059 cx: &mut Context<Self>,
16060 ) {
16061 let old_selections: Box<[_]> = self
16062 .selections
16063 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16064 .into();
16065 if old_selections.is_empty() {
16066 return;
16067 }
16068
16069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16070
16071 let buffer = self.buffer.read(cx).snapshot(cx);
16072 let mut selected_sibling = false;
16073
16074 let new_selections = old_selections
16075 .iter()
16076 .map(|selection| {
16077 let old_range = selection.start..selection.end;
16078
16079 let old_range =
16080 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16081 let excerpt = buffer.excerpt_containing(old_range.clone());
16082
16083 if let Some(mut excerpt) = excerpt
16084 && let Some(node) = excerpt
16085 .buffer()
16086 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16087 {
16088 let new_range = excerpt.map_range_from_buffer(
16089 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16090 );
16091 selected_sibling = true;
16092 Selection {
16093 id: selection.id,
16094 start: new_range.start,
16095 end: new_range.end,
16096 goal: SelectionGoal::None,
16097 reversed: selection.reversed,
16098 }
16099 } else {
16100 selection.clone()
16101 }
16102 })
16103 .collect::<Vec<_>>();
16104
16105 if selected_sibling {
16106 self.change_selections(
16107 SelectionEffects::scroll(Autoscroll::fit()),
16108 window,
16109 cx,
16110 |s| {
16111 s.select(new_selections);
16112 },
16113 );
16114 }
16115 }
16116
16117 pub fn select_prev_syntax_node(
16118 &mut self,
16119 _: &SelectPreviousSyntaxNode,
16120 window: &mut Window,
16121 cx: &mut Context<Self>,
16122 ) {
16123 let old_selections: Box<[_]> = self
16124 .selections
16125 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16126 .into();
16127 if old_selections.is_empty() {
16128 return;
16129 }
16130
16131 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16132
16133 let buffer = self.buffer.read(cx).snapshot(cx);
16134 let mut selected_sibling = false;
16135
16136 let new_selections = old_selections
16137 .iter()
16138 .map(|selection| {
16139 let old_range = selection.start..selection.end;
16140 let old_range =
16141 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16142 let excerpt = buffer.excerpt_containing(old_range.clone());
16143
16144 if let Some(mut excerpt) = excerpt
16145 && let Some(node) = excerpt
16146 .buffer()
16147 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16148 {
16149 let new_range = excerpt.map_range_from_buffer(
16150 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16151 );
16152 selected_sibling = true;
16153 Selection {
16154 id: selection.id,
16155 start: new_range.start,
16156 end: new_range.end,
16157 goal: SelectionGoal::None,
16158 reversed: selection.reversed,
16159 }
16160 } else {
16161 selection.clone()
16162 }
16163 })
16164 .collect::<Vec<_>>();
16165
16166 if selected_sibling {
16167 self.change_selections(
16168 SelectionEffects::scroll(Autoscroll::fit()),
16169 window,
16170 cx,
16171 |s| {
16172 s.select(new_selections);
16173 },
16174 );
16175 }
16176 }
16177
16178 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16179 if !EditorSettings::get_global(cx).gutter.runnables {
16180 self.clear_tasks();
16181 return Task::ready(());
16182 }
16183 let project = self.project().map(Entity::downgrade);
16184 let task_sources = self.lsp_task_sources(cx);
16185 let multi_buffer = self.buffer.downgrade();
16186 cx.spawn_in(window, async move |editor, cx| {
16187 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16188 let Some(project) = project.and_then(|p| p.upgrade()) else {
16189 return;
16190 };
16191 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16192 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16193 }) else {
16194 return;
16195 };
16196
16197 let hide_runnables = project
16198 .update(cx, |project, _| project.is_via_collab())
16199 .unwrap_or(true);
16200 if hide_runnables {
16201 return;
16202 }
16203 let new_rows =
16204 cx.background_spawn({
16205 let snapshot = display_snapshot.clone();
16206 async move {
16207 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16208 }
16209 })
16210 .await;
16211 let Ok(lsp_tasks) =
16212 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16213 else {
16214 return;
16215 };
16216 let lsp_tasks = lsp_tasks.await;
16217
16218 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16219 lsp_tasks
16220 .into_iter()
16221 .flat_map(|(kind, tasks)| {
16222 tasks.into_iter().filter_map(move |(location, task)| {
16223 Some((kind.clone(), location?, task))
16224 })
16225 })
16226 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16227 let buffer = location.target.buffer;
16228 let buffer_snapshot = buffer.read(cx).snapshot();
16229 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16230 |(excerpt_id, snapshot, _)| {
16231 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16232 display_snapshot
16233 .buffer_snapshot()
16234 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16235 } else {
16236 None
16237 }
16238 },
16239 );
16240 if let Some(offset) = offset {
16241 let task_buffer_range =
16242 location.target.range.to_point(&buffer_snapshot);
16243 let context_buffer_range =
16244 task_buffer_range.to_offset(&buffer_snapshot);
16245 let context_range = BufferOffset(context_buffer_range.start)
16246 ..BufferOffset(context_buffer_range.end);
16247
16248 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16249 .or_insert_with(|| RunnableTasks {
16250 templates: Vec::new(),
16251 offset,
16252 column: task_buffer_range.start.column,
16253 extra_variables: HashMap::default(),
16254 context_range,
16255 })
16256 .templates
16257 .push((kind, task.original_task().clone()));
16258 }
16259
16260 acc
16261 })
16262 }) else {
16263 return;
16264 };
16265
16266 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16267 buffer.language_settings(cx).tasks.prefer_lsp
16268 }) else {
16269 return;
16270 };
16271
16272 let rows = Self::runnable_rows(
16273 project,
16274 display_snapshot,
16275 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16276 new_rows,
16277 cx.clone(),
16278 )
16279 .await;
16280 editor
16281 .update(cx, |editor, _| {
16282 editor.clear_tasks();
16283 for (key, mut value) in rows {
16284 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16285 value.templates.extend(lsp_tasks.templates);
16286 }
16287
16288 editor.insert_tasks(key, value);
16289 }
16290 for (key, value) in lsp_tasks_by_rows {
16291 editor.insert_tasks(key, value);
16292 }
16293 })
16294 .ok();
16295 })
16296 }
16297 fn fetch_runnable_ranges(
16298 snapshot: &DisplaySnapshot,
16299 range: Range<Anchor>,
16300 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16301 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16302 }
16303
16304 fn runnable_rows(
16305 project: Entity<Project>,
16306 snapshot: DisplaySnapshot,
16307 prefer_lsp: bool,
16308 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16309 cx: AsyncWindowContext,
16310 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16311 cx.spawn(async move |cx| {
16312 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16313 for (run_range, mut runnable) in runnable_ranges {
16314 let Some(tasks) = cx
16315 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16316 .ok()
16317 else {
16318 continue;
16319 };
16320 let mut tasks = tasks.await;
16321
16322 if prefer_lsp {
16323 tasks.retain(|(task_kind, _)| {
16324 !matches!(task_kind, TaskSourceKind::Language { .. })
16325 });
16326 }
16327 if tasks.is_empty() {
16328 continue;
16329 }
16330
16331 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16332 let Some(row) = snapshot
16333 .buffer_snapshot()
16334 .buffer_line_for_row(MultiBufferRow(point.row))
16335 .map(|(_, range)| range.start.row)
16336 else {
16337 continue;
16338 };
16339
16340 let context_range =
16341 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16342 runnable_rows.push((
16343 (runnable.buffer_id, row),
16344 RunnableTasks {
16345 templates: tasks,
16346 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16347 context_range,
16348 column: point.column,
16349 extra_variables: runnable.extra_captures,
16350 },
16351 ));
16352 }
16353 runnable_rows
16354 })
16355 }
16356
16357 fn templates_with_tags(
16358 project: &Entity<Project>,
16359 runnable: &mut Runnable,
16360 cx: &mut App,
16361 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16362 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16363 let (worktree_id, file) = project
16364 .buffer_for_id(runnable.buffer, cx)
16365 .and_then(|buffer| buffer.read(cx).file())
16366 .map(|file| (file.worktree_id(cx), file.clone()))
16367 .unzip();
16368
16369 (
16370 project.task_store().read(cx).task_inventory().cloned(),
16371 worktree_id,
16372 file,
16373 )
16374 });
16375
16376 let tags = mem::take(&mut runnable.tags);
16377 let language = runnable.language.clone();
16378 cx.spawn(async move |cx| {
16379 let mut templates_with_tags = Vec::new();
16380 if let Some(inventory) = inventory {
16381 for RunnableTag(tag) in tags {
16382 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16383 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16384 }) else {
16385 return templates_with_tags;
16386 };
16387 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16388 move |(_, template)| {
16389 template.tags.iter().any(|source_tag| source_tag == &tag)
16390 },
16391 ));
16392 }
16393 }
16394 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16395
16396 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16397 // Strongest source wins; if we have worktree tag binding, prefer that to
16398 // global and language bindings;
16399 // if we have a global binding, prefer that to language binding.
16400 let first_mismatch = templates_with_tags
16401 .iter()
16402 .position(|(tag_source, _)| tag_source != leading_tag_source);
16403 if let Some(index) = first_mismatch {
16404 templates_with_tags.truncate(index);
16405 }
16406 }
16407
16408 templates_with_tags
16409 })
16410 }
16411
16412 pub fn move_to_enclosing_bracket(
16413 &mut self,
16414 _: &MoveToEnclosingBracket,
16415 window: &mut Window,
16416 cx: &mut Context<Self>,
16417 ) {
16418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16419 self.change_selections(Default::default(), window, cx, |s| {
16420 s.move_offsets_with(|snapshot, selection| {
16421 let Some(enclosing_bracket_ranges) =
16422 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16423 else {
16424 return;
16425 };
16426
16427 let mut best_length = usize::MAX;
16428 let mut best_inside = false;
16429 let mut best_in_bracket_range = false;
16430 let mut best_destination = None;
16431 for (open, close) in enclosing_bracket_ranges {
16432 let close = close.to_inclusive();
16433 let length = *close.end() - open.start;
16434 let inside = selection.start >= open.end && selection.end <= *close.start();
16435 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16436 || close.contains(&selection.head());
16437
16438 // If best is next to a bracket and current isn't, skip
16439 if !in_bracket_range && best_in_bracket_range {
16440 continue;
16441 }
16442
16443 // Prefer smaller lengths unless best is inside and current isn't
16444 if length > best_length && (best_inside || !inside) {
16445 continue;
16446 }
16447
16448 best_length = length;
16449 best_inside = inside;
16450 best_in_bracket_range = in_bracket_range;
16451 best_destination = Some(
16452 if close.contains(&selection.start) && close.contains(&selection.end) {
16453 if inside { open.end } else { open.start }
16454 } else if inside {
16455 *close.start()
16456 } else {
16457 *close.end()
16458 },
16459 );
16460 }
16461
16462 if let Some(destination) = best_destination {
16463 selection.collapse_to(destination, SelectionGoal::None);
16464 }
16465 })
16466 });
16467 }
16468
16469 pub fn undo_selection(
16470 &mut self,
16471 _: &UndoSelection,
16472 window: &mut Window,
16473 cx: &mut Context<Self>,
16474 ) {
16475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16476 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16477 self.selection_history.mode = SelectionHistoryMode::Undoing;
16478 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16479 this.end_selection(window, cx);
16480 this.change_selections(
16481 SelectionEffects::scroll(Autoscroll::newest()),
16482 window,
16483 cx,
16484 |s| s.select_anchors(entry.selections.to_vec()),
16485 );
16486 });
16487 self.selection_history.mode = SelectionHistoryMode::Normal;
16488
16489 self.select_next_state = entry.select_next_state;
16490 self.select_prev_state = entry.select_prev_state;
16491 self.add_selections_state = entry.add_selections_state;
16492 }
16493 }
16494
16495 pub fn redo_selection(
16496 &mut self,
16497 _: &RedoSelection,
16498 window: &mut Window,
16499 cx: &mut Context<Self>,
16500 ) {
16501 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16502 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16503 self.selection_history.mode = SelectionHistoryMode::Redoing;
16504 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16505 this.end_selection(window, cx);
16506 this.change_selections(
16507 SelectionEffects::scroll(Autoscroll::newest()),
16508 window,
16509 cx,
16510 |s| s.select_anchors(entry.selections.to_vec()),
16511 );
16512 });
16513 self.selection_history.mode = SelectionHistoryMode::Normal;
16514
16515 self.select_next_state = entry.select_next_state;
16516 self.select_prev_state = entry.select_prev_state;
16517 self.add_selections_state = entry.add_selections_state;
16518 }
16519 }
16520
16521 pub fn expand_excerpts(
16522 &mut self,
16523 action: &ExpandExcerpts,
16524 _: &mut Window,
16525 cx: &mut Context<Self>,
16526 ) {
16527 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16528 }
16529
16530 pub fn expand_excerpts_down(
16531 &mut self,
16532 action: &ExpandExcerptsDown,
16533 _: &mut Window,
16534 cx: &mut Context<Self>,
16535 ) {
16536 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16537 }
16538
16539 pub fn expand_excerpts_up(
16540 &mut self,
16541 action: &ExpandExcerptsUp,
16542 _: &mut Window,
16543 cx: &mut Context<Self>,
16544 ) {
16545 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16546 }
16547
16548 pub fn expand_excerpts_for_direction(
16549 &mut self,
16550 lines: u32,
16551 direction: ExpandExcerptDirection,
16552
16553 cx: &mut Context<Self>,
16554 ) {
16555 let selections = self.selections.disjoint_anchors_arc();
16556
16557 let lines = if lines == 0 {
16558 EditorSettings::get_global(cx).expand_excerpt_lines
16559 } else {
16560 lines
16561 };
16562
16563 self.buffer.update(cx, |buffer, cx| {
16564 let snapshot = buffer.snapshot(cx);
16565 let mut excerpt_ids = selections
16566 .iter()
16567 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16568 .collect::<Vec<_>>();
16569 excerpt_ids.sort();
16570 excerpt_ids.dedup();
16571 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16572 })
16573 }
16574
16575 pub fn expand_excerpt(
16576 &mut self,
16577 excerpt: ExcerptId,
16578 direction: ExpandExcerptDirection,
16579 window: &mut Window,
16580 cx: &mut Context<Self>,
16581 ) {
16582 let current_scroll_position = self.scroll_position(cx);
16583 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16584 let mut scroll = None;
16585
16586 if direction == ExpandExcerptDirection::Down {
16587 let multi_buffer = self.buffer.read(cx);
16588 let snapshot = multi_buffer.snapshot(cx);
16589 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16590 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16591 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16592 {
16593 let buffer_snapshot = buffer.read(cx).snapshot();
16594 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16595 let last_row = buffer_snapshot.max_point().row;
16596 let lines_below = last_row.saturating_sub(excerpt_end_row);
16597 if lines_below >= lines_to_expand {
16598 scroll = Some(
16599 current_scroll_position
16600 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16601 );
16602 }
16603 }
16604 }
16605 if direction == ExpandExcerptDirection::Up
16606 && self
16607 .buffer
16608 .read(cx)
16609 .snapshot(cx)
16610 .excerpt_before(excerpt)
16611 .is_none()
16612 {
16613 scroll = Some(current_scroll_position);
16614 }
16615
16616 self.buffer.update(cx, |buffer, cx| {
16617 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16618 });
16619
16620 if let Some(new_scroll_position) = scroll {
16621 self.set_scroll_position(new_scroll_position, window, cx);
16622 }
16623 }
16624
16625 pub fn go_to_singleton_buffer_point(
16626 &mut self,
16627 point: Point,
16628 window: &mut Window,
16629 cx: &mut Context<Self>,
16630 ) {
16631 self.go_to_singleton_buffer_range(point..point, window, cx);
16632 }
16633
16634 pub fn go_to_singleton_buffer_range(
16635 &mut self,
16636 range: Range<Point>,
16637 window: &mut Window,
16638 cx: &mut Context<Self>,
16639 ) {
16640 let multibuffer = self.buffer().read(cx);
16641 let Some(buffer) = multibuffer.as_singleton() else {
16642 return;
16643 };
16644 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16645 return;
16646 };
16647 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16648 return;
16649 };
16650 self.change_selections(
16651 SelectionEffects::default().nav_history(true),
16652 window,
16653 cx,
16654 |s| s.select_anchor_ranges([start..end]),
16655 );
16656 }
16657
16658 pub fn go_to_diagnostic(
16659 &mut self,
16660 action: &GoToDiagnostic,
16661 window: &mut Window,
16662 cx: &mut Context<Self>,
16663 ) {
16664 if !self.diagnostics_enabled() {
16665 return;
16666 }
16667 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16668 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16669 }
16670
16671 pub fn go_to_prev_diagnostic(
16672 &mut self,
16673 action: &GoToPreviousDiagnostic,
16674 window: &mut Window,
16675 cx: &mut Context<Self>,
16676 ) {
16677 if !self.diagnostics_enabled() {
16678 return;
16679 }
16680 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16681 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16682 }
16683
16684 pub fn go_to_diagnostic_impl(
16685 &mut self,
16686 direction: Direction,
16687 severity: GoToDiagnosticSeverityFilter,
16688 window: &mut Window,
16689 cx: &mut Context<Self>,
16690 ) {
16691 let buffer = self.buffer.read(cx).snapshot(cx);
16692 let selection = self
16693 .selections
16694 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16695
16696 let mut active_group_id = None;
16697 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16698 && active_group.active_range.start.to_offset(&buffer) == selection.start
16699 {
16700 active_group_id = Some(active_group.group_id);
16701 }
16702
16703 fn filtered<'a>(
16704 severity: GoToDiagnosticSeverityFilter,
16705 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16706 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16707 diagnostics
16708 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16709 .filter(|entry| entry.range.start != entry.range.end)
16710 .filter(|entry| !entry.diagnostic.is_unnecessary)
16711 }
16712
16713 let before = filtered(
16714 severity,
16715 buffer
16716 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16717 .filter(|entry| entry.range.start <= selection.start),
16718 );
16719 let after = filtered(
16720 severity,
16721 buffer
16722 .diagnostics_in_range(selection.start..buffer.len())
16723 .filter(|entry| entry.range.start >= selection.start),
16724 );
16725
16726 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16727 if direction == Direction::Prev {
16728 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16729 {
16730 for diagnostic in prev_diagnostics.into_iter().rev() {
16731 if diagnostic.range.start != selection.start
16732 || active_group_id
16733 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16734 {
16735 found = Some(diagnostic);
16736 break 'outer;
16737 }
16738 }
16739 }
16740 } else {
16741 for diagnostic in after.chain(before) {
16742 if diagnostic.range.start != selection.start
16743 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16744 {
16745 found = Some(diagnostic);
16746 break;
16747 }
16748 }
16749 }
16750 let Some(next_diagnostic) = found else {
16751 return;
16752 };
16753
16754 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16755 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16756 return;
16757 };
16758 let snapshot = self.snapshot(window, cx);
16759 if snapshot.intersects_fold(next_diagnostic.range.start) {
16760 self.unfold_ranges(
16761 std::slice::from_ref(&next_diagnostic.range),
16762 true,
16763 false,
16764 cx,
16765 );
16766 }
16767 self.change_selections(Default::default(), window, cx, |s| {
16768 s.select_ranges(vec![
16769 next_diagnostic.range.start..next_diagnostic.range.start,
16770 ])
16771 });
16772 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16773 self.refresh_edit_prediction(false, true, window, cx);
16774 }
16775
16776 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16778 let snapshot = self.snapshot(window, cx);
16779 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16780 self.go_to_hunk_before_or_after_position(
16781 &snapshot,
16782 selection.head(),
16783 Direction::Next,
16784 window,
16785 cx,
16786 );
16787 }
16788
16789 pub fn go_to_hunk_before_or_after_position(
16790 &mut self,
16791 snapshot: &EditorSnapshot,
16792 position: Point,
16793 direction: Direction,
16794 window: &mut Window,
16795 cx: &mut Context<Editor>,
16796 ) {
16797 let row = if direction == Direction::Next {
16798 self.hunk_after_position(snapshot, position)
16799 .map(|hunk| hunk.row_range.start)
16800 } else {
16801 self.hunk_before_position(snapshot, position)
16802 };
16803
16804 if let Some(row) = row {
16805 let destination = Point::new(row.0, 0);
16806 let autoscroll = Autoscroll::center();
16807
16808 self.unfold_ranges(&[destination..destination], false, false, cx);
16809 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16810 s.select_ranges([destination..destination]);
16811 });
16812 }
16813 }
16814
16815 fn hunk_after_position(
16816 &mut self,
16817 snapshot: &EditorSnapshot,
16818 position: Point,
16819 ) -> Option<MultiBufferDiffHunk> {
16820 snapshot
16821 .buffer_snapshot()
16822 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16823 .find(|hunk| hunk.row_range.start.0 > position.row)
16824 .or_else(|| {
16825 snapshot
16826 .buffer_snapshot()
16827 .diff_hunks_in_range(Point::zero()..position)
16828 .find(|hunk| hunk.row_range.end.0 < position.row)
16829 })
16830 }
16831
16832 fn go_to_prev_hunk(
16833 &mut self,
16834 _: &GoToPreviousHunk,
16835 window: &mut Window,
16836 cx: &mut Context<Self>,
16837 ) {
16838 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16839 let snapshot = self.snapshot(window, cx);
16840 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16841 self.go_to_hunk_before_or_after_position(
16842 &snapshot,
16843 selection.head(),
16844 Direction::Prev,
16845 window,
16846 cx,
16847 );
16848 }
16849
16850 fn hunk_before_position(
16851 &mut self,
16852 snapshot: &EditorSnapshot,
16853 position: Point,
16854 ) -> Option<MultiBufferRow> {
16855 snapshot
16856 .buffer_snapshot()
16857 .diff_hunk_before(position)
16858 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16859 }
16860
16861 fn go_to_next_change(
16862 &mut self,
16863 _: &GoToNextChange,
16864 window: &mut Window,
16865 cx: &mut Context<Self>,
16866 ) {
16867 if let Some(selections) = self
16868 .change_list
16869 .next_change(1, Direction::Next)
16870 .map(|s| s.to_vec())
16871 {
16872 self.change_selections(Default::default(), window, cx, |s| {
16873 let map = s.display_snapshot();
16874 s.select_display_ranges(selections.iter().map(|a| {
16875 let point = a.to_display_point(&map);
16876 point..point
16877 }))
16878 })
16879 }
16880 }
16881
16882 fn go_to_previous_change(
16883 &mut self,
16884 _: &GoToPreviousChange,
16885 window: &mut Window,
16886 cx: &mut Context<Self>,
16887 ) {
16888 if let Some(selections) = self
16889 .change_list
16890 .next_change(1, Direction::Prev)
16891 .map(|s| s.to_vec())
16892 {
16893 self.change_selections(Default::default(), window, cx, |s| {
16894 let map = s.display_snapshot();
16895 s.select_display_ranges(selections.iter().map(|a| {
16896 let point = a.to_display_point(&map);
16897 point..point
16898 }))
16899 })
16900 }
16901 }
16902
16903 pub fn go_to_next_document_highlight(
16904 &mut self,
16905 _: &GoToNextDocumentHighlight,
16906 window: &mut Window,
16907 cx: &mut Context<Self>,
16908 ) {
16909 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16910 }
16911
16912 pub fn go_to_prev_document_highlight(
16913 &mut self,
16914 _: &GoToPreviousDocumentHighlight,
16915 window: &mut Window,
16916 cx: &mut Context<Self>,
16917 ) {
16918 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16919 }
16920
16921 pub fn go_to_document_highlight_before_or_after_position(
16922 &mut self,
16923 direction: Direction,
16924 window: &mut Window,
16925 cx: &mut Context<Editor>,
16926 ) {
16927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16928 let snapshot = self.snapshot(window, cx);
16929 let buffer = &snapshot.buffer_snapshot();
16930 let position = self
16931 .selections
16932 .newest::<Point>(&snapshot.display_snapshot)
16933 .head();
16934 let anchor_position = buffer.anchor_after(position);
16935
16936 // Get all document highlights (both read and write)
16937 let mut all_highlights = Vec::new();
16938
16939 if let Some((_, read_highlights)) = self
16940 .background_highlights
16941 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16942 {
16943 all_highlights.extend(read_highlights.iter());
16944 }
16945
16946 if let Some((_, write_highlights)) = self
16947 .background_highlights
16948 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16949 {
16950 all_highlights.extend(write_highlights.iter());
16951 }
16952
16953 if all_highlights.is_empty() {
16954 return;
16955 }
16956
16957 // Sort highlights by position
16958 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16959
16960 let target_highlight = match direction {
16961 Direction::Next => {
16962 // Find the first highlight after the current position
16963 all_highlights
16964 .iter()
16965 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16966 }
16967 Direction::Prev => {
16968 // Find the last highlight before the current position
16969 all_highlights
16970 .iter()
16971 .rev()
16972 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16973 }
16974 };
16975
16976 if let Some(highlight) = target_highlight {
16977 let destination = highlight.start.to_point(buffer);
16978 let autoscroll = Autoscroll::center();
16979
16980 self.unfold_ranges(&[destination..destination], false, false, cx);
16981 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16982 s.select_ranges([destination..destination]);
16983 });
16984 }
16985 }
16986
16987 fn go_to_line<T: 'static>(
16988 &mut self,
16989 position: Anchor,
16990 highlight_color: Option<Hsla>,
16991 window: &mut Window,
16992 cx: &mut Context<Self>,
16993 ) {
16994 let snapshot = self.snapshot(window, cx).display_snapshot;
16995 let position = position.to_point(&snapshot.buffer_snapshot());
16996 let start = snapshot
16997 .buffer_snapshot()
16998 .clip_point(Point::new(position.row, 0), Bias::Left);
16999 let end = start + Point::new(1, 0);
17000 let start = snapshot.buffer_snapshot().anchor_before(start);
17001 let end = snapshot.buffer_snapshot().anchor_before(end);
17002
17003 self.highlight_rows::<T>(
17004 start..end,
17005 highlight_color
17006 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17007 Default::default(),
17008 cx,
17009 );
17010
17011 if self.buffer.read(cx).is_singleton() {
17012 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17013 }
17014 }
17015
17016 pub fn go_to_definition(
17017 &mut self,
17018 _: &GoToDefinition,
17019 window: &mut Window,
17020 cx: &mut Context<Self>,
17021 ) -> Task<Result<Navigated>> {
17022 let definition =
17023 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17024 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17025 cx.spawn_in(window, async move |editor, cx| {
17026 if definition.await? == Navigated::Yes {
17027 return Ok(Navigated::Yes);
17028 }
17029 match fallback_strategy {
17030 GoToDefinitionFallback::None => Ok(Navigated::No),
17031 GoToDefinitionFallback::FindAllReferences => {
17032 match editor.update_in(cx, |editor, window, cx| {
17033 editor.find_all_references(&FindAllReferences::default(), window, cx)
17034 })? {
17035 Some(references) => references.await,
17036 None => Ok(Navigated::No),
17037 }
17038 }
17039 }
17040 })
17041 }
17042
17043 pub fn go_to_declaration(
17044 &mut self,
17045 _: &GoToDeclaration,
17046 window: &mut Window,
17047 cx: &mut Context<Self>,
17048 ) -> Task<Result<Navigated>> {
17049 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17050 }
17051
17052 pub fn go_to_declaration_split(
17053 &mut self,
17054 _: &GoToDeclaration,
17055 window: &mut Window,
17056 cx: &mut Context<Self>,
17057 ) -> Task<Result<Navigated>> {
17058 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17059 }
17060
17061 pub fn go_to_implementation(
17062 &mut self,
17063 _: &GoToImplementation,
17064 window: &mut Window,
17065 cx: &mut Context<Self>,
17066 ) -> Task<Result<Navigated>> {
17067 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17068 }
17069
17070 pub fn go_to_implementation_split(
17071 &mut self,
17072 _: &GoToImplementationSplit,
17073 window: &mut Window,
17074 cx: &mut Context<Self>,
17075 ) -> Task<Result<Navigated>> {
17076 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17077 }
17078
17079 pub fn go_to_type_definition(
17080 &mut self,
17081 _: &GoToTypeDefinition,
17082 window: &mut Window,
17083 cx: &mut Context<Self>,
17084 ) -> Task<Result<Navigated>> {
17085 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17086 }
17087
17088 pub fn go_to_definition_split(
17089 &mut self,
17090 _: &GoToDefinitionSplit,
17091 window: &mut Window,
17092 cx: &mut Context<Self>,
17093 ) -> Task<Result<Navigated>> {
17094 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17095 }
17096
17097 pub fn go_to_type_definition_split(
17098 &mut self,
17099 _: &GoToTypeDefinitionSplit,
17100 window: &mut Window,
17101 cx: &mut Context<Self>,
17102 ) -> Task<Result<Navigated>> {
17103 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17104 }
17105
17106 fn go_to_definition_of_kind(
17107 &mut self,
17108 kind: GotoDefinitionKind,
17109 split: bool,
17110 window: &mut Window,
17111 cx: &mut Context<Self>,
17112 ) -> Task<Result<Navigated>> {
17113 let Some(provider) = self.semantics_provider.clone() else {
17114 return Task::ready(Ok(Navigated::No));
17115 };
17116 let head = self
17117 .selections
17118 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17119 .head();
17120 let buffer = self.buffer.read(cx);
17121 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17122 return Task::ready(Ok(Navigated::No));
17123 };
17124 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17125 return Task::ready(Ok(Navigated::No));
17126 };
17127
17128 cx.spawn_in(window, async move |editor, cx| {
17129 let Some(definitions) = definitions.await? else {
17130 return Ok(Navigated::No);
17131 };
17132 let navigated = editor
17133 .update_in(cx, |editor, window, cx| {
17134 editor.navigate_to_hover_links(
17135 Some(kind),
17136 definitions
17137 .into_iter()
17138 .filter(|location| {
17139 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17140 })
17141 .map(HoverLink::Text)
17142 .collect::<Vec<_>>(),
17143 split,
17144 window,
17145 cx,
17146 )
17147 })?
17148 .await?;
17149 anyhow::Ok(navigated)
17150 })
17151 }
17152
17153 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17154 let selection = self.selections.newest_anchor();
17155 let head = selection.head();
17156 let tail = selection.tail();
17157
17158 let Some((buffer, start_position)) =
17159 self.buffer.read(cx).text_anchor_for_position(head, cx)
17160 else {
17161 return;
17162 };
17163
17164 let end_position = if head != tail {
17165 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17166 return;
17167 };
17168 Some(pos)
17169 } else {
17170 None
17171 };
17172
17173 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17174 let url = if let Some(end_pos) = end_position {
17175 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17176 } else {
17177 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17178 };
17179
17180 if let Some(url) = url {
17181 cx.update(|window, cx| {
17182 if parse_zed_link(&url, cx).is_some() {
17183 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17184 } else {
17185 cx.open_url(&url);
17186 }
17187 })?;
17188 }
17189
17190 anyhow::Ok(())
17191 });
17192
17193 url_finder.detach();
17194 }
17195
17196 pub fn open_selected_filename(
17197 &mut self,
17198 _: &OpenSelectedFilename,
17199 window: &mut Window,
17200 cx: &mut Context<Self>,
17201 ) {
17202 let Some(workspace) = self.workspace() else {
17203 return;
17204 };
17205
17206 let position = self.selections.newest_anchor().head();
17207
17208 let Some((buffer, buffer_position)) =
17209 self.buffer.read(cx).text_anchor_for_position(position, cx)
17210 else {
17211 return;
17212 };
17213
17214 let project = self.project.clone();
17215
17216 cx.spawn_in(window, async move |_, cx| {
17217 let result = find_file(&buffer, project, buffer_position, cx).await;
17218
17219 if let Some((_, path)) = result {
17220 workspace
17221 .update_in(cx, |workspace, window, cx| {
17222 workspace.open_resolved_path(path, window, cx)
17223 })?
17224 .await?;
17225 }
17226 anyhow::Ok(())
17227 })
17228 .detach();
17229 }
17230
17231 pub(crate) fn navigate_to_hover_links(
17232 &mut self,
17233 kind: Option<GotoDefinitionKind>,
17234 definitions: Vec<HoverLink>,
17235 split: bool,
17236 window: &mut Window,
17237 cx: &mut Context<Editor>,
17238 ) -> Task<Result<Navigated>> {
17239 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17240 let mut first_url_or_file = None;
17241 let definitions: Vec<_> = definitions
17242 .into_iter()
17243 .filter_map(|def| match def {
17244 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17245 HoverLink::InlayHint(lsp_location, server_id) => {
17246 let computation =
17247 self.compute_target_location(lsp_location, server_id, window, cx);
17248 Some(cx.background_spawn(computation))
17249 }
17250 HoverLink::Url(url) => {
17251 first_url_or_file = Some(Either::Left(url));
17252 None
17253 }
17254 HoverLink::File(path) => {
17255 first_url_or_file = Some(Either::Right(path));
17256 None
17257 }
17258 })
17259 .collect();
17260
17261 let workspace = self.workspace();
17262
17263 cx.spawn_in(window, async move |editor, cx| {
17264 let locations: Vec<Location> = future::join_all(definitions)
17265 .await
17266 .into_iter()
17267 .filter_map(|location| location.transpose())
17268 .collect::<Result<_>>()
17269 .context("location tasks")?;
17270 let mut locations = cx.update(|_, cx| {
17271 locations
17272 .into_iter()
17273 .map(|location| {
17274 let buffer = location.buffer.read(cx);
17275 (location.buffer, location.range.to_point(buffer))
17276 })
17277 .into_group_map()
17278 })?;
17279 let mut num_locations = 0;
17280 for ranges in locations.values_mut() {
17281 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17282 ranges.dedup();
17283 num_locations += ranges.len();
17284 }
17285
17286 if num_locations > 1 {
17287 let tab_kind = match kind {
17288 Some(GotoDefinitionKind::Implementation) => "Implementations",
17289 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17290 Some(GotoDefinitionKind::Declaration) => "Declarations",
17291 Some(GotoDefinitionKind::Type) => "Types",
17292 };
17293 let title = editor
17294 .update_in(cx, |_, _, cx| {
17295 let target = locations
17296 .iter()
17297 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17298 .map(|(buffer, location)| {
17299 buffer
17300 .read(cx)
17301 .text_for_range(location.clone())
17302 .collect::<String>()
17303 })
17304 .filter(|text| !text.contains('\n'))
17305 .unique()
17306 .take(3)
17307 .join(", ");
17308 if target.is_empty() {
17309 tab_kind.to_owned()
17310 } else {
17311 format!("{tab_kind} for {target}")
17312 }
17313 })
17314 .context("buffer title")?;
17315
17316 let Some(workspace) = workspace else {
17317 return Ok(Navigated::No);
17318 };
17319
17320 let opened = workspace
17321 .update_in(cx, |workspace, window, cx| {
17322 let allow_preview = PreviewTabsSettings::get_global(cx)
17323 .enable_preview_multibuffer_from_code_navigation;
17324 Self::open_locations_in_multibuffer(
17325 workspace,
17326 locations,
17327 title,
17328 split,
17329 allow_preview,
17330 MultibufferSelectionMode::First,
17331 window,
17332 cx,
17333 )
17334 })
17335 .is_ok();
17336
17337 anyhow::Ok(Navigated::from_bool(opened))
17338 } else if num_locations == 0 {
17339 // If there is one url or file, open it directly
17340 match first_url_or_file {
17341 Some(Either::Left(url)) => {
17342 cx.update(|window, cx| {
17343 if parse_zed_link(&url, cx).is_some() {
17344 window
17345 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17346 } else {
17347 cx.open_url(&url);
17348 }
17349 })?;
17350 Ok(Navigated::Yes)
17351 }
17352 Some(Either::Right(path)) => {
17353 // TODO(andrew): respect preview tab settings
17354 // `enable_keep_preview_on_code_navigation` and
17355 // `enable_preview_file_from_code_navigation`
17356 let Some(workspace) = workspace else {
17357 return Ok(Navigated::No);
17358 };
17359 workspace
17360 .update_in(cx, |workspace, window, cx| {
17361 workspace.open_resolved_path(path, window, cx)
17362 })?
17363 .await?;
17364 Ok(Navigated::Yes)
17365 }
17366 None => Ok(Navigated::No),
17367 }
17368 } else {
17369 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17370 let target_range = target_ranges.first().unwrap().clone();
17371
17372 editor.update_in(cx, |editor, window, cx| {
17373 let range = target_range.to_point(target_buffer.read(cx));
17374 let range = editor.range_for_match(&range);
17375 let range = collapse_multiline_range(range);
17376
17377 if !split
17378 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17379 {
17380 editor.go_to_singleton_buffer_range(range, window, cx);
17381 } else {
17382 let Some(workspace) = workspace else {
17383 return Navigated::No;
17384 };
17385 let pane = workspace.read(cx).active_pane().clone();
17386 window.defer(cx, move |window, cx| {
17387 let target_editor: Entity<Self> =
17388 workspace.update(cx, |workspace, cx| {
17389 let pane = if split {
17390 workspace.adjacent_pane(window, cx)
17391 } else {
17392 workspace.active_pane().clone()
17393 };
17394
17395 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17396 let keep_old_preview = preview_tabs_settings
17397 .enable_keep_preview_on_code_navigation;
17398 let allow_new_preview = preview_tabs_settings
17399 .enable_preview_file_from_code_navigation;
17400
17401 workspace.open_project_item(
17402 pane,
17403 target_buffer.clone(),
17404 true,
17405 true,
17406 keep_old_preview,
17407 allow_new_preview,
17408 window,
17409 cx,
17410 )
17411 });
17412 target_editor.update(cx, |target_editor, cx| {
17413 // When selecting a definition in a different buffer, disable the nav history
17414 // to avoid creating a history entry at the previous cursor location.
17415 pane.update(cx, |pane, _| pane.disable_history());
17416 target_editor.go_to_singleton_buffer_range(range, window, cx);
17417 pane.update(cx, |pane, _| pane.enable_history());
17418 });
17419 });
17420 }
17421 Navigated::Yes
17422 })
17423 }
17424 })
17425 }
17426
17427 fn compute_target_location(
17428 &self,
17429 lsp_location: lsp::Location,
17430 server_id: LanguageServerId,
17431 window: &mut Window,
17432 cx: &mut Context<Self>,
17433 ) -> Task<anyhow::Result<Option<Location>>> {
17434 let Some(project) = self.project.clone() else {
17435 return Task::ready(Ok(None));
17436 };
17437
17438 cx.spawn_in(window, async move |editor, cx| {
17439 let location_task = editor.update(cx, |_, cx| {
17440 project.update(cx, |project, cx| {
17441 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17442 })
17443 })?;
17444 let location = Some({
17445 let target_buffer_handle = location_task.await.context("open local buffer")?;
17446 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17447 let target_start = target_buffer
17448 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17449 let target_end = target_buffer
17450 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17451 target_buffer.anchor_after(target_start)
17452 ..target_buffer.anchor_before(target_end)
17453 })?;
17454 Location {
17455 buffer: target_buffer_handle,
17456 range,
17457 }
17458 });
17459 Ok(location)
17460 })
17461 }
17462
17463 fn go_to_next_reference(
17464 &mut self,
17465 _: &GoToNextReference,
17466 window: &mut Window,
17467 cx: &mut Context<Self>,
17468 ) {
17469 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17470 if let Some(task) = task {
17471 task.detach();
17472 };
17473 }
17474
17475 fn go_to_prev_reference(
17476 &mut self,
17477 _: &GoToPreviousReference,
17478 window: &mut Window,
17479 cx: &mut Context<Self>,
17480 ) {
17481 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17482 if let Some(task) = task {
17483 task.detach();
17484 };
17485 }
17486
17487 pub fn go_to_reference_before_or_after_position(
17488 &mut self,
17489 direction: Direction,
17490 count: usize,
17491 window: &mut Window,
17492 cx: &mut Context<Self>,
17493 ) -> Option<Task<Result<()>>> {
17494 let selection = self.selections.newest_anchor();
17495 let head = selection.head();
17496
17497 let multi_buffer = self.buffer.read(cx);
17498
17499 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17500 let workspace = self.workspace()?;
17501 let project = workspace.read(cx).project().clone();
17502 let references =
17503 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17504 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17505 let Some(locations) = references.await? else {
17506 return Ok(());
17507 };
17508
17509 if locations.is_empty() {
17510 // totally normal - the cursor may be on something which is not
17511 // a symbol (e.g. a keyword)
17512 log::info!("no references found under cursor");
17513 return Ok(());
17514 }
17515
17516 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17517
17518 let (locations, current_location_index) =
17519 multi_buffer.update(cx, |multi_buffer, cx| {
17520 let mut locations = locations
17521 .into_iter()
17522 .filter_map(|loc| {
17523 let start = multi_buffer.buffer_anchor_to_anchor(
17524 &loc.buffer,
17525 loc.range.start,
17526 cx,
17527 )?;
17528 let end = multi_buffer.buffer_anchor_to_anchor(
17529 &loc.buffer,
17530 loc.range.end,
17531 cx,
17532 )?;
17533 Some(start..end)
17534 })
17535 .collect::<Vec<_>>();
17536
17537 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17538 // There is an O(n) implementation, but given this list will be
17539 // small (usually <100 items), the extra O(log(n)) factor isn't
17540 // worth the (surprisingly large amount of) extra complexity.
17541 locations
17542 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17543
17544 let head_offset = head.to_offset(&multi_buffer_snapshot);
17545
17546 let current_location_index = locations.iter().position(|loc| {
17547 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17548 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17549 });
17550
17551 (locations, current_location_index)
17552 })?;
17553
17554 let Some(current_location_index) = current_location_index else {
17555 // This indicates something has gone wrong, because we already
17556 // handle the "no references" case above
17557 log::error!(
17558 "failed to find current reference under cursor. Total references: {}",
17559 locations.len()
17560 );
17561 return Ok(());
17562 };
17563
17564 let destination_location_index = match direction {
17565 Direction::Next => (current_location_index + count) % locations.len(),
17566 Direction::Prev => {
17567 (current_location_index + locations.len() - count % locations.len())
17568 % locations.len()
17569 }
17570 };
17571
17572 // TODO(cameron): is this needed?
17573 // the thinking is to avoid "jumping to the current location" (avoid
17574 // polluting "jumplist" in vim terms)
17575 if current_location_index == destination_location_index {
17576 return Ok(());
17577 }
17578
17579 let Range { start, end } = locations[destination_location_index];
17580
17581 editor.update_in(cx, |editor, window, cx| {
17582 let effects = SelectionEffects::default();
17583
17584 editor.unfold_ranges(&[start..end], false, false, cx);
17585 editor.change_selections(effects, window, cx, |s| {
17586 s.select_ranges([start..start]);
17587 });
17588 })?;
17589
17590 Ok(())
17591 }))
17592 }
17593
17594 pub fn find_all_references(
17595 &mut self,
17596 action: &FindAllReferences,
17597 window: &mut Window,
17598 cx: &mut Context<Self>,
17599 ) -> Option<Task<Result<Navigated>>> {
17600 let always_open_multibuffer = action.always_open_multibuffer;
17601 let selection = self.selections.newest_anchor();
17602 let multi_buffer = self.buffer.read(cx);
17603 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17604 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17605 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17606 let head = selection_offset.head();
17607
17608 let head_anchor = multi_buffer_snapshot.anchor_at(
17609 head,
17610 if head < selection_offset.tail() {
17611 Bias::Right
17612 } else {
17613 Bias::Left
17614 },
17615 );
17616
17617 match self
17618 .find_all_references_task_sources
17619 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17620 {
17621 Ok(_) => {
17622 log::info!(
17623 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17624 );
17625 return None;
17626 }
17627 Err(i) => {
17628 self.find_all_references_task_sources.insert(i, head_anchor);
17629 }
17630 }
17631
17632 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17633 let workspace = self.workspace()?;
17634 let project = workspace.read(cx).project().clone();
17635 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17636 Some(cx.spawn_in(window, async move |editor, cx| {
17637 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17638 if let Ok(i) = editor
17639 .find_all_references_task_sources
17640 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17641 {
17642 editor.find_all_references_task_sources.remove(i);
17643 }
17644 });
17645
17646 let Some(locations) = references.await? else {
17647 return anyhow::Ok(Navigated::No);
17648 };
17649 let mut locations = cx.update(|_, cx| {
17650 locations
17651 .into_iter()
17652 .map(|location| {
17653 let buffer = location.buffer.read(cx);
17654 (location.buffer, location.range.to_point(buffer))
17655 })
17656 // if special-casing the single-match case, remove ranges
17657 // that intersect current selection
17658 .filter(|(location_buffer, location)| {
17659 if always_open_multibuffer || &buffer != location_buffer {
17660 return true;
17661 }
17662
17663 !location.contains_inclusive(&selection_point.range())
17664 })
17665 .into_group_map()
17666 })?;
17667 if locations.is_empty() {
17668 return anyhow::Ok(Navigated::No);
17669 }
17670 for ranges in locations.values_mut() {
17671 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17672 ranges.dedup();
17673 }
17674 let mut num_locations = 0;
17675 for ranges in locations.values_mut() {
17676 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17677 ranges.dedup();
17678 num_locations += ranges.len();
17679 }
17680
17681 if num_locations == 1 && !always_open_multibuffer {
17682 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17683 let target_range = target_ranges.first().unwrap().clone();
17684
17685 return editor.update_in(cx, |editor, window, cx| {
17686 let range = target_range.to_point(target_buffer.read(cx));
17687 let range = editor.range_for_match(&range);
17688 let range = range.start..range.start;
17689
17690 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17691 editor.go_to_singleton_buffer_range(range, window, cx);
17692 } else {
17693 let pane = workspace.read(cx).active_pane().clone();
17694 window.defer(cx, move |window, cx| {
17695 let target_editor: Entity<Self> =
17696 workspace.update(cx, |workspace, cx| {
17697 let pane = workspace.active_pane().clone();
17698
17699 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17700 let keep_old_preview = preview_tabs_settings
17701 .enable_keep_preview_on_code_navigation;
17702 let allow_new_preview = preview_tabs_settings
17703 .enable_preview_file_from_code_navigation;
17704
17705 workspace.open_project_item(
17706 pane,
17707 target_buffer.clone(),
17708 true,
17709 true,
17710 keep_old_preview,
17711 allow_new_preview,
17712 window,
17713 cx,
17714 )
17715 });
17716 target_editor.update(cx, |target_editor, cx| {
17717 // When selecting a definition in a different buffer, disable the nav history
17718 // to avoid creating a history entry at the previous cursor location.
17719 pane.update(cx, |pane, _| pane.disable_history());
17720 target_editor.go_to_singleton_buffer_range(range, window, cx);
17721 pane.update(cx, |pane, _| pane.enable_history());
17722 });
17723 });
17724 }
17725 Navigated::No
17726 });
17727 }
17728
17729 workspace.update_in(cx, |workspace, window, cx| {
17730 let target = locations
17731 .iter()
17732 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17733 .map(|(buffer, location)| {
17734 buffer
17735 .read(cx)
17736 .text_for_range(location.clone())
17737 .collect::<String>()
17738 })
17739 .filter(|text| !text.contains('\n'))
17740 .unique()
17741 .take(3)
17742 .join(", ");
17743 let title = if target.is_empty() {
17744 "References".to_owned()
17745 } else {
17746 format!("References to {target}")
17747 };
17748 let allow_preview = PreviewTabsSettings::get_global(cx)
17749 .enable_preview_multibuffer_from_code_navigation;
17750 Self::open_locations_in_multibuffer(
17751 workspace,
17752 locations,
17753 title,
17754 false,
17755 allow_preview,
17756 MultibufferSelectionMode::First,
17757 window,
17758 cx,
17759 );
17760 Navigated::Yes
17761 })
17762 }))
17763 }
17764
17765 /// Opens a multibuffer with the given project locations in it.
17766 pub fn open_locations_in_multibuffer(
17767 workspace: &mut Workspace,
17768 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17769 title: String,
17770 split: bool,
17771 allow_preview: bool,
17772 multibuffer_selection_mode: MultibufferSelectionMode,
17773 window: &mut Window,
17774 cx: &mut Context<Workspace>,
17775 ) {
17776 if locations.is_empty() {
17777 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17778 return;
17779 }
17780
17781 let capability = workspace.project().read(cx).capability();
17782 let mut ranges = <Vec<Range<Anchor>>>::new();
17783
17784 // a key to find existing multibuffer editors with the same set of locations
17785 // to prevent us from opening more and more multibuffer tabs for searches and the like
17786 let mut key = (title.clone(), vec![]);
17787 let excerpt_buffer = cx.new(|cx| {
17788 let key = &mut key.1;
17789 let mut multibuffer = MultiBuffer::new(capability);
17790 for (buffer, mut ranges_for_buffer) in locations {
17791 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17792 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17793 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17794 PathKey::for_buffer(&buffer, cx),
17795 buffer.clone(),
17796 ranges_for_buffer,
17797 multibuffer_context_lines(cx),
17798 cx,
17799 );
17800 ranges.extend(new_ranges)
17801 }
17802
17803 multibuffer.with_title(title)
17804 });
17805 let existing = workspace.active_pane().update(cx, |pane, cx| {
17806 pane.items()
17807 .filter_map(|item| item.downcast::<Editor>())
17808 .find(|editor| {
17809 editor
17810 .read(cx)
17811 .lookup_key
17812 .as_ref()
17813 .and_then(|it| {
17814 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17815 })
17816 .is_some_and(|it| *it == key)
17817 })
17818 });
17819 let was_existing = existing.is_some();
17820 let editor = existing.unwrap_or_else(|| {
17821 cx.new(|cx| {
17822 let mut editor = Editor::for_multibuffer(
17823 excerpt_buffer,
17824 Some(workspace.project().clone()),
17825 window,
17826 cx,
17827 );
17828 editor.lookup_key = Some(Box::new(key));
17829 editor
17830 })
17831 });
17832 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17833 MultibufferSelectionMode::First => {
17834 if let Some(first_range) = ranges.first() {
17835 editor.change_selections(
17836 SelectionEffects::no_scroll(),
17837 window,
17838 cx,
17839 |selections| {
17840 selections.clear_disjoint();
17841 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17842 },
17843 );
17844 }
17845 editor.highlight_background::<Self>(
17846 &ranges,
17847 |_, theme| theme.colors().editor_highlighted_line_background,
17848 cx,
17849 );
17850 }
17851 MultibufferSelectionMode::All => {
17852 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17853 selections.clear_disjoint();
17854 selections.select_anchor_ranges(ranges);
17855 });
17856 }
17857 });
17858
17859 let item = Box::new(editor);
17860
17861 let pane = if split {
17862 workspace.adjacent_pane(window, cx)
17863 } else {
17864 workspace.active_pane().clone()
17865 };
17866 let activate_pane = split;
17867
17868 let mut destination_index = None;
17869 pane.update(cx, |pane, cx| {
17870 if allow_preview && !was_existing {
17871 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17872 }
17873 if was_existing && !allow_preview {
17874 pane.unpreview_item_if_preview(item.item_id());
17875 }
17876 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17877 });
17878 }
17879
17880 pub fn rename(
17881 &mut self,
17882 _: &Rename,
17883 window: &mut Window,
17884 cx: &mut Context<Self>,
17885 ) -> Option<Task<Result<()>>> {
17886 use language::ToOffset as _;
17887
17888 let provider = self.semantics_provider.clone()?;
17889 let selection = self.selections.newest_anchor().clone();
17890 let (cursor_buffer, cursor_buffer_position) = self
17891 .buffer
17892 .read(cx)
17893 .text_anchor_for_position(selection.head(), cx)?;
17894 let (tail_buffer, cursor_buffer_position_end) = self
17895 .buffer
17896 .read(cx)
17897 .text_anchor_for_position(selection.tail(), cx)?;
17898 if tail_buffer != cursor_buffer {
17899 return None;
17900 }
17901
17902 let snapshot = cursor_buffer.read(cx).snapshot();
17903 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17904 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17905 let prepare_rename = provider
17906 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17907 .unwrap_or_else(|| Task::ready(Ok(None)));
17908 drop(snapshot);
17909
17910 Some(cx.spawn_in(window, async move |this, cx| {
17911 let rename_range = if let Some(range) = prepare_rename.await? {
17912 Some(range)
17913 } else {
17914 this.update(cx, |this, cx| {
17915 let buffer = this.buffer.read(cx).snapshot(cx);
17916 let mut buffer_highlights = this
17917 .document_highlights_for_position(selection.head(), &buffer)
17918 .filter(|highlight| {
17919 highlight.start.excerpt_id == selection.head().excerpt_id
17920 && highlight.end.excerpt_id == selection.head().excerpt_id
17921 });
17922 buffer_highlights
17923 .next()
17924 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17925 })?
17926 };
17927 if let Some(rename_range) = rename_range {
17928 this.update_in(cx, |this, window, cx| {
17929 let snapshot = cursor_buffer.read(cx).snapshot();
17930 let rename_buffer_range = rename_range.to_offset(&snapshot);
17931 let cursor_offset_in_rename_range =
17932 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17933 let cursor_offset_in_rename_range_end =
17934 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17935
17936 this.take_rename(false, window, cx);
17937 let buffer = this.buffer.read(cx).read(cx);
17938 let cursor_offset = selection.head().to_offset(&buffer);
17939 let rename_start =
17940 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17941 let rename_end = rename_start + rename_buffer_range.len();
17942 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17943 let mut old_highlight_id = None;
17944 let old_name: Arc<str> = buffer
17945 .chunks(rename_start..rename_end, true)
17946 .map(|chunk| {
17947 if old_highlight_id.is_none() {
17948 old_highlight_id = chunk.syntax_highlight_id;
17949 }
17950 chunk.text
17951 })
17952 .collect::<String>()
17953 .into();
17954
17955 drop(buffer);
17956
17957 // Position the selection in the rename editor so that it matches the current selection.
17958 this.show_local_selections = false;
17959 let rename_editor = cx.new(|cx| {
17960 let mut editor = Editor::single_line(window, cx);
17961 editor.buffer.update(cx, |buffer, cx| {
17962 buffer.edit(
17963 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17964 None,
17965 cx,
17966 )
17967 });
17968 let cursor_offset_in_rename_range =
17969 MultiBufferOffset(cursor_offset_in_rename_range);
17970 let cursor_offset_in_rename_range_end =
17971 MultiBufferOffset(cursor_offset_in_rename_range_end);
17972 let rename_selection_range = match cursor_offset_in_rename_range
17973 .cmp(&cursor_offset_in_rename_range_end)
17974 {
17975 Ordering::Equal => {
17976 editor.select_all(&SelectAll, window, cx);
17977 return editor;
17978 }
17979 Ordering::Less => {
17980 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17981 }
17982 Ordering::Greater => {
17983 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17984 }
17985 };
17986 if rename_selection_range.end.0 > old_name.len() {
17987 editor.select_all(&SelectAll, window, cx);
17988 } else {
17989 editor.change_selections(Default::default(), window, cx, |s| {
17990 s.select_ranges([rename_selection_range]);
17991 });
17992 }
17993 editor
17994 });
17995 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17996 if e == &EditorEvent::Focused {
17997 cx.emit(EditorEvent::FocusedIn)
17998 }
17999 })
18000 .detach();
18001
18002 let write_highlights =
18003 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18004 let read_highlights =
18005 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18006 let ranges = write_highlights
18007 .iter()
18008 .flat_map(|(_, ranges)| ranges.iter())
18009 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18010 .cloned()
18011 .collect();
18012
18013 this.highlight_text::<Rename>(
18014 ranges,
18015 HighlightStyle {
18016 fade_out: Some(0.6),
18017 ..Default::default()
18018 },
18019 cx,
18020 );
18021 let rename_focus_handle = rename_editor.focus_handle(cx);
18022 window.focus(&rename_focus_handle, cx);
18023 let block_id = this.insert_blocks(
18024 [BlockProperties {
18025 style: BlockStyle::Flex,
18026 placement: BlockPlacement::Below(range.start),
18027 height: Some(1),
18028 render: Arc::new({
18029 let rename_editor = rename_editor.clone();
18030 move |cx: &mut BlockContext| {
18031 let mut text_style = cx.editor_style.text.clone();
18032 if let Some(highlight_style) = old_highlight_id
18033 .and_then(|h| h.style(&cx.editor_style.syntax))
18034 {
18035 text_style = text_style.highlight(highlight_style);
18036 }
18037 div()
18038 .block_mouse_except_scroll()
18039 .pl(cx.anchor_x)
18040 .child(EditorElement::new(
18041 &rename_editor,
18042 EditorStyle {
18043 background: cx.theme().system().transparent,
18044 local_player: cx.editor_style.local_player,
18045 text: text_style,
18046 scrollbar_width: cx.editor_style.scrollbar_width,
18047 syntax: cx.editor_style.syntax.clone(),
18048 status: cx.editor_style.status.clone(),
18049 inlay_hints_style: HighlightStyle {
18050 font_weight: Some(FontWeight::BOLD),
18051 ..make_inlay_hints_style(cx.app)
18052 },
18053 edit_prediction_styles: make_suggestion_styles(
18054 cx.app,
18055 ),
18056 ..EditorStyle::default()
18057 },
18058 ))
18059 .into_any_element()
18060 }
18061 }),
18062 priority: 0,
18063 }],
18064 Some(Autoscroll::fit()),
18065 cx,
18066 )[0];
18067 this.pending_rename = Some(RenameState {
18068 range,
18069 old_name,
18070 editor: rename_editor,
18071 block_id,
18072 });
18073 })?;
18074 }
18075
18076 Ok(())
18077 }))
18078 }
18079
18080 pub fn confirm_rename(
18081 &mut self,
18082 _: &ConfirmRename,
18083 window: &mut Window,
18084 cx: &mut Context<Self>,
18085 ) -> Option<Task<Result<()>>> {
18086 let rename = self.take_rename(false, window, cx)?;
18087 let workspace = self.workspace()?.downgrade();
18088 let (buffer, start) = self
18089 .buffer
18090 .read(cx)
18091 .text_anchor_for_position(rename.range.start, cx)?;
18092 let (end_buffer, _) = self
18093 .buffer
18094 .read(cx)
18095 .text_anchor_for_position(rename.range.end, cx)?;
18096 if buffer != end_buffer {
18097 return None;
18098 }
18099
18100 let old_name = rename.old_name;
18101 let new_name = rename.editor.read(cx).text(cx);
18102
18103 let rename = self.semantics_provider.as_ref()?.perform_rename(
18104 &buffer,
18105 start,
18106 new_name.clone(),
18107 cx,
18108 )?;
18109
18110 Some(cx.spawn_in(window, async move |editor, cx| {
18111 let project_transaction = rename.await?;
18112 Self::open_project_transaction(
18113 &editor,
18114 workspace,
18115 project_transaction,
18116 format!("Rename: {} → {}", old_name, new_name),
18117 cx,
18118 )
18119 .await?;
18120
18121 editor.update(cx, |editor, cx| {
18122 editor.refresh_document_highlights(cx);
18123 })?;
18124 Ok(())
18125 }))
18126 }
18127
18128 fn take_rename(
18129 &mut self,
18130 moving_cursor: bool,
18131 window: &mut Window,
18132 cx: &mut Context<Self>,
18133 ) -> Option<RenameState> {
18134 let rename = self.pending_rename.take()?;
18135 if rename.editor.focus_handle(cx).is_focused(window) {
18136 window.focus(&self.focus_handle, cx);
18137 }
18138
18139 self.remove_blocks(
18140 [rename.block_id].into_iter().collect(),
18141 Some(Autoscroll::fit()),
18142 cx,
18143 );
18144 self.clear_highlights::<Rename>(cx);
18145 self.show_local_selections = true;
18146
18147 if moving_cursor {
18148 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18149 editor
18150 .selections
18151 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18152 .head()
18153 });
18154
18155 // Update the selection to match the position of the selection inside
18156 // the rename editor.
18157 let snapshot = self.buffer.read(cx).read(cx);
18158 let rename_range = rename.range.to_offset(&snapshot);
18159 let cursor_in_editor = snapshot
18160 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18161 .min(rename_range.end);
18162 drop(snapshot);
18163
18164 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18165 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18166 });
18167 } else {
18168 self.refresh_document_highlights(cx);
18169 }
18170
18171 Some(rename)
18172 }
18173
18174 pub fn pending_rename(&self) -> Option<&RenameState> {
18175 self.pending_rename.as_ref()
18176 }
18177
18178 fn format(
18179 &mut self,
18180 _: &Format,
18181 window: &mut Window,
18182 cx: &mut Context<Self>,
18183 ) -> Option<Task<Result<()>>> {
18184 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18185
18186 let project = match &self.project {
18187 Some(project) => project.clone(),
18188 None => return None,
18189 };
18190
18191 Some(self.perform_format(
18192 project,
18193 FormatTrigger::Manual,
18194 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18195 window,
18196 cx,
18197 ))
18198 }
18199
18200 fn format_selections(
18201 &mut self,
18202 _: &FormatSelections,
18203 window: &mut Window,
18204 cx: &mut Context<Self>,
18205 ) -> Option<Task<Result<()>>> {
18206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18207
18208 let project = match &self.project {
18209 Some(project) => project.clone(),
18210 None => return None,
18211 };
18212
18213 let ranges = self
18214 .selections
18215 .all_adjusted(&self.display_snapshot(cx))
18216 .into_iter()
18217 .map(|selection| selection.range())
18218 .collect_vec();
18219
18220 Some(self.perform_format(
18221 project,
18222 FormatTrigger::Manual,
18223 FormatTarget::Ranges(ranges),
18224 window,
18225 cx,
18226 ))
18227 }
18228
18229 fn perform_format(
18230 &mut self,
18231 project: Entity<Project>,
18232 trigger: FormatTrigger,
18233 target: FormatTarget,
18234 window: &mut Window,
18235 cx: &mut Context<Self>,
18236 ) -> Task<Result<()>> {
18237 let buffer = self.buffer.clone();
18238 let (buffers, target) = match target {
18239 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18240 FormatTarget::Ranges(selection_ranges) => {
18241 let multi_buffer = buffer.read(cx);
18242 let snapshot = multi_buffer.read(cx);
18243 let mut buffers = HashSet::default();
18244 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18245 BTreeMap::new();
18246 for selection_range in selection_ranges {
18247 for (buffer, buffer_range, _) in
18248 snapshot.range_to_buffer_ranges(selection_range)
18249 {
18250 let buffer_id = buffer.remote_id();
18251 let start = buffer.anchor_before(buffer_range.start);
18252 let end = buffer.anchor_after(buffer_range.end);
18253 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18254 buffer_id_to_ranges
18255 .entry(buffer_id)
18256 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18257 .or_insert_with(|| vec![start..end]);
18258 }
18259 }
18260 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18261 }
18262 };
18263
18264 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18265 let selections_prev = transaction_id_prev
18266 .and_then(|transaction_id_prev| {
18267 // default to selections as they were after the last edit, if we have them,
18268 // instead of how they are now.
18269 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18270 // will take you back to where you made the last edit, instead of staying where you scrolled
18271 self.selection_history
18272 .transaction(transaction_id_prev)
18273 .map(|t| t.0.clone())
18274 })
18275 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18276
18277 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18278 let format = project.update(cx, |project, cx| {
18279 project.format(buffers, target, true, trigger, cx)
18280 });
18281
18282 cx.spawn_in(window, async move |editor, cx| {
18283 let transaction = futures::select_biased! {
18284 transaction = format.log_err().fuse() => transaction,
18285 () = timeout => {
18286 log::warn!("timed out waiting for formatting");
18287 None
18288 }
18289 };
18290
18291 buffer
18292 .update(cx, |buffer, cx| {
18293 if let Some(transaction) = transaction
18294 && !buffer.is_singleton()
18295 {
18296 buffer.push_transaction(&transaction.0, cx);
18297 }
18298 cx.notify();
18299 })
18300 .ok();
18301
18302 if let Some(transaction_id_now) =
18303 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18304 {
18305 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18306 if has_new_transaction {
18307 _ = editor.update(cx, |editor, _| {
18308 editor
18309 .selection_history
18310 .insert_transaction(transaction_id_now, selections_prev);
18311 });
18312 }
18313 }
18314
18315 Ok(())
18316 })
18317 }
18318
18319 fn organize_imports(
18320 &mut self,
18321 _: &OrganizeImports,
18322 window: &mut Window,
18323 cx: &mut Context<Self>,
18324 ) -> Option<Task<Result<()>>> {
18325 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18326 let project = match &self.project {
18327 Some(project) => project.clone(),
18328 None => return None,
18329 };
18330 Some(self.perform_code_action_kind(
18331 project,
18332 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18333 window,
18334 cx,
18335 ))
18336 }
18337
18338 fn perform_code_action_kind(
18339 &mut self,
18340 project: Entity<Project>,
18341 kind: CodeActionKind,
18342 window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) -> Task<Result<()>> {
18345 let buffer = self.buffer.clone();
18346 let buffers = buffer.read(cx).all_buffers();
18347 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18348 let apply_action = project.update(cx, |project, cx| {
18349 project.apply_code_action_kind(buffers, kind, true, cx)
18350 });
18351 cx.spawn_in(window, async move |_, cx| {
18352 let transaction = futures::select_biased! {
18353 () = timeout => {
18354 log::warn!("timed out waiting for executing code action");
18355 None
18356 }
18357 transaction = apply_action.log_err().fuse() => transaction,
18358 };
18359 buffer
18360 .update(cx, |buffer, cx| {
18361 // check if we need this
18362 if let Some(transaction) = transaction
18363 && !buffer.is_singleton()
18364 {
18365 buffer.push_transaction(&transaction.0, cx);
18366 }
18367 cx.notify();
18368 })
18369 .ok();
18370 Ok(())
18371 })
18372 }
18373
18374 pub fn restart_language_server(
18375 &mut self,
18376 _: &RestartLanguageServer,
18377 _: &mut Window,
18378 cx: &mut Context<Self>,
18379 ) {
18380 if let Some(project) = self.project.clone() {
18381 self.buffer.update(cx, |multi_buffer, cx| {
18382 project.update(cx, |project, cx| {
18383 project.restart_language_servers_for_buffers(
18384 multi_buffer.all_buffers().into_iter().collect(),
18385 HashSet::default(),
18386 cx,
18387 );
18388 });
18389 })
18390 }
18391 }
18392
18393 pub fn stop_language_server(
18394 &mut self,
18395 _: &StopLanguageServer,
18396 _: &mut Window,
18397 cx: &mut Context<Self>,
18398 ) {
18399 if let Some(project) = self.project.clone() {
18400 self.buffer.update(cx, |multi_buffer, cx| {
18401 project.update(cx, |project, cx| {
18402 project.stop_language_servers_for_buffers(
18403 multi_buffer.all_buffers().into_iter().collect(),
18404 HashSet::default(),
18405 cx,
18406 );
18407 });
18408 });
18409 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18410 }
18411 }
18412
18413 fn cancel_language_server_work(
18414 workspace: &mut Workspace,
18415 _: &actions::CancelLanguageServerWork,
18416 _: &mut Window,
18417 cx: &mut Context<Workspace>,
18418 ) {
18419 let project = workspace.project();
18420 let buffers = workspace
18421 .active_item(cx)
18422 .and_then(|item| item.act_as::<Editor>(cx))
18423 .map_or(HashSet::default(), |editor| {
18424 editor.read(cx).buffer.read(cx).all_buffers()
18425 });
18426 project.update(cx, |project, cx| {
18427 project.cancel_language_server_work_for_buffers(buffers, cx);
18428 });
18429 }
18430
18431 fn show_character_palette(
18432 &mut self,
18433 _: &ShowCharacterPalette,
18434 window: &mut Window,
18435 _: &mut Context<Self>,
18436 ) {
18437 window.show_character_palette();
18438 }
18439
18440 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18441 if !self.diagnostics_enabled() {
18442 return;
18443 }
18444
18445 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18446 let buffer = self.buffer.read(cx).snapshot(cx);
18447 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18448 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18449 let is_valid = buffer
18450 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18451 .any(|entry| {
18452 entry.diagnostic.is_primary
18453 && !entry.range.is_empty()
18454 && entry.range.start == primary_range_start
18455 && entry.diagnostic.message == active_diagnostics.active_message
18456 });
18457
18458 if !is_valid {
18459 self.dismiss_diagnostics(cx);
18460 }
18461 }
18462 }
18463
18464 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18465 match &self.active_diagnostics {
18466 ActiveDiagnostic::Group(group) => Some(group),
18467 _ => None,
18468 }
18469 }
18470
18471 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18472 if !self.diagnostics_enabled() {
18473 return;
18474 }
18475 self.dismiss_diagnostics(cx);
18476 self.active_diagnostics = ActiveDiagnostic::All;
18477 }
18478
18479 fn activate_diagnostics(
18480 &mut self,
18481 buffer_id: BufferId,
18482 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18483 window: &mut Window,
18484 cx: &mut Context<Self>,
18485 ) {
18486 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18487 return;
18488 }
18489 self.dismiss_diagnostics(cx);
18490 let snapshot = self.snapshot(window, cx);
18491 let buffer = self.buffer.read(cx).snapshot(cx);
18492 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18493 return;
18494 };
18495
18496 let diagnostic_group = buffer
18497 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18498 .collect::<Vec<_>>();
18499
18500 let language_registry = self
18501 .project()
18502 .map(|project| project.read(cx).languages().clone());
18503
18504 let blocks = renderer.render_group(
18505 diagnostic_group,
18506 buffer_id,
18507 snapshot,
18508 cx.weak_entity(),
18509 language_registry,
18510 cx,
18511 );
18512
18513 let blocks = self.display_map.update(cx, |display_map, cx| {
18514 display_map.insert_blocks(blocks, cx).into_iter().collect()
18515 });
18516 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18517 active_range: buffer.anchor_before(diagnostic.range.start)
18518 ..buffer.anchor_after(diagnostic.range.end),
18519 active_message: diagnostic.diagnostic.message.clone(),
18520 group_id: diagnostic.diagnostic.group_id,
18521 blocks,
18522 });
18523 cx.notify();
18524 }
18525
18526 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18527 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18528 return;
18529 };
18530
18531 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18532 if let ActiveDiagnostic::Group(group) = prev {
18533 self.display_map.update(cx, |display_map, cx| {
18534 display_map.remove_blocks(group.blocks, cx);
18535 });
18536 cx.notify();
18537 }
18538 }
18539
18540 /// Disable inline diagnostics rendering for this editor.
18541 pub fn disable_inline_diagnostics(&mut self) {
18542 self.inline_diagnostics_enabled = false;
18543 self.inline_diagnostics_update = Task::ready(());
18544 self.inline_diagnostics.clear();
18545 }
18546
18547 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18548 self.diagnostics_enabled = false;
18549 self.dismiss_diagnostics(cx);
18550 self.inline_diagnostics_update = Task::ready(());
18551 self.inline_diagnostics.clear();
18552 }
18553
18554 pub fn disable_word_completions(&mut self) {
18555 self.word_completions_enabled = false;
18556 }
18557
18558 pub fn diagnostics_enabled(&self) -> bool {
18559 self.diagnostics_enabled && self.mode.is_full()
18560 }
18561
18562 pub fn inline_diagnostics_enabled(&self) -> bool {
18563 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18564 }
18565
18566 pub fn show_inline_diagnostics(&self) -> bool {
18567 self.show_inline_diagnostics
18568 }
18569
18570 pub fn toggle_inline_diagnostics(
18571 &mut self,
18572 _: &ToggleInlineDiagnostics,
18573 window: &mut Window,
18574 cx: &mut Context<Editor>,
18575 ) {
18576 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18577 self.refresh_inline_diagnostics(false, window, cx);
18578 }
18579
18580 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18581 self.diagnostics_max_severity = severity;
18582 self.display_map.update(cx, |display_map, _| {
18583 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18584 });
18585 }
18586
18587 pub fn toggle_diagnostics(
18588 &mut self,
18589 _: &ToggleDiagnostics,
18590 window: &mut Window,
18591 cx: &mut Context<Editor>,
18592 ) {
18593 if !self.diagnostics_enabled() {
18594 return;
18595 }
18596
18597 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18598 EditorSettings::get_global(cx)
18599 .diagnostics_max_severity
18600 .filter(|severity| severity != &DiagnosticSeverity::Off)
18601 .unwrap_or(DiagnosticSeverity::Hint)
18602 } else {
18603 DiagnosticSeverity::Off
18604 };
18605 self.set_max_diagnostics_severity(new_severity, cx);
18606 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18607 self.active_diagnostics = ActiveDiagnostic::None;
18608 self.inline_diagnostics_update = Task::ready(());
18609 self.inline_diagnostics.clear();
18610 } else {
18611 self.refresh_inline_diagnostics(false, window, cx);
18612 }
18613
18614 cx.notify();
18615 }
18616
18617 pub fn toggle_minimap(
18618 &mut self,
18619 _: &ToggleMinimap,
18620 window: &mut Window,
18621 cx: &mut Context<Editor>,
18622 ) {
18623 if self.supports_minimap(cx) {
18624 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18625 }
18626 }
18627
18628 fn refresh_inline_diagnostics(
18629 &mut self,
18630 debounce: bool,
18631 window: &mut Window,
18632 cx: &mut Context<Self>,
18633 ) {
18634 let max_severity = ProjectSettings::get_global(cx)
18635 .diagnostics
18636 .inline
18637 .max_severity
18638 .unwrap_or(self.diagnostics_max_severity);
18639
18640 if !self.inline_diagnostics_enabled()
18641 || !self.diagnostics_enabled()
18642 || !self.show_inline_diagnostics
18643 || max_severity == DiagnosticSeverity::Off
18644 {
18645 self.inline_diagnostics_update = Task::ready(());
18646 self.inline_diagnostics.clear();
18647 return;
18648 }
18649
18650 let debounce_ms = ProjectSettings::get_global(cx)
18651 .diagnostics
18652 .inline
18653 .update_debounce_ms;
18654 let debounce = if debounce && debounce_ms > 0 {
18655 Some(Duration::from_millis(debounce_ms))
18656 } else {
18657 None
18658 };
18659 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18660 if let Some(debounce) = debounce {
18661 cx.background_executor().timer(debounce).await;
18662 }
18663 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18664 editor
18665 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18666 .ok()
18667 }) else {
18668 return;
18669 };
18670
18671 let new_inline_diagnostics = cx
18672 .background_spawn(async move {
18673 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18674 for diagnostic_entry in
18675 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18676 {
18677 let message = diagnostic_entry
18678 .diagnostic
18679 .message
18680 .split_once('\n')
18681 .map(|(line, _)| line)
18682 .map(SharedString::new)
18683 .unwrap_or_else(|| {
18684 SharedString::new(&*diagnostic_entry.diagnostic.message)
18685 });
18686 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18687 let (Ok(i) | Err(i)) = inline_diagnostics
18688 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18689 inline_diagnostics.insert(
18690 i,
18691 (
18692 start_anchor,
18693 InlineDiagnostic {
18694 message,
18695 group_id: diagnostic_entry.diagnostic.group_id,
18696 start: diagnostic_entry.range.start.to_point(&snapshot),
18697 is_primary: diagnostic_entry.diagnostic.is_primary,
18698 severity: diagnostic_entry.diagnostic.severity,
18699 },
18700 ),
18701 );
18702 }
18703 inline_diagnostics
18704 })
18705 .await;
18706
18707 editor
18708 .update(cx, |editor, cx| {
18709 editor.inline_diagnostics = new_inline_diagnostics;
18710 cx.notify();
18711 })
18712 .ok();
18713 });
18714 }
18715
18716 fn pull_diagnostics(
18717 &mut self,
18718 buffer_id: Option<BufferId>,
18719 window: &Window,
18720 cx: &mut Context<Self>,
18721 ) -> Option<()> {
18722 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18723 return None;
18724 }
18725 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18726 .diagnostics
18727 .lsp_pull_diagnostics;
18728 if !pull_diagnostics_settings.enabled {
18729 return None;
18730 }
18731 let project = self.project()?.downgrade();
18732
18733 let mut edited_buffer_ids = HashSet::default();
18734 let mut edited_worktree_ids = HashSet::default();
18735 let edited_buffers = match buffer_id {
18736 Some(buffer_id) => {
18737 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18738 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18739 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18740 edited_worktree_ids.insert(worktree_id);
18741 vec![buffer]
18742 }
18743 None => self
18744 .buffer()
18745 .read(cx)
18746 .all_buffers()
18747 .into_iter()
18748 .filter(|buffer| {
18749 let buffer = buffer.read(cx);
18750 match buffer.file().map(|f| f.worktree_id(cx)) {
18751 Some(worktree_id) => {
18752 edited_buffer_ids.insert(buffer.remote_id());
18753 edited_worktree_ids.insert(worktree_id);
18754 true
18755 }
18756 None => false,
18757 }
18758 })
18759 .collect::<Vec<_>>(),
18760 };
18761
18762 if edited_buffers.is_empty() {
18763 self.pull_diagnostics_task = Task::ready(());
18764 self.pull_diagnostics_background_task = Task::ready(());
18765 return None;
18766 }
18767
18768 let mut already_used_buffers = HashSet::default();
18769 let related_open_buffers = self
18770 .workspace
18771 .as_ref()
18772 .and_then(|(workspace, _)| workspace.upgrade())
18773 .into_iter()
18774 .flat_map(|workspace| workspace.read(cx).panes())
18775 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18776 .filter(|editor| editor != &cx.entity())
18777 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18778 .filter(|buffer| {
18779 let buffer = buffer.read(cx);
18780 let buffer_id = buffer.remote_id();
18781 if already_used_buffers.insert(buffer_id) {
18782 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18783 return !edited_buffer_ids.contains(&buffer_id)
18784 && !edited_worktree_ids.contains(&worktree_id);
18785 }
18786 }
18787 false
18788 })
18789 .collect::<Vec<_>>();
18790
18791 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18792 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18793 if buffers.is_empty() {
18794 return Task::ready(());
18795 }
18796 let project_weak = project.clone();
18797 cx.spawn_in(window, async move |_, cx| {
18798 cx.background_executor().timer(delay).await;
18799
18800 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18801 buffers
18802 .into_iter()
18803 .filter_map(|buffer| {
18804 project_weak
18805 .update(cx, |project, cx| {
18806 project.lsp_store().update(cx, |lsp_store, cx| {
18807 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18808 })
18809 })
18810 .ok()
18811 })
18812 .collect::<FuturesUnordered<_>>()
18813 }) else {
18814 return;
18815 };
18816
18817 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18818 if let Err(e) = pull_task {
18819 log::error!("Failed to update project diagnostics: {e:#}");
18820 }
18821 }
18822 })
18823 };
18824
18825 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18826 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18827
18828 Some(())
18829 }
18830
18831 pub fn set_selections_from_remote(
18832 &mut self,
18833 selections: Vec<Selection<Anchor>>,
18834 pending_selection: Option<Selection<Anchor>>,
18835 window: &mut Window,
18836 cx: &mut Context<Self>,
18837 ) {
18838 let old_cursor_position = self.selections.newest_anchor().head();
18839 self.selections
18840 .change_with(&self.display_snapshot(cx), |s| {
18841 s.select_anchors(selections);
18842 if let Some(pending_selection) = pending_selection {
18843 s.set_pending(pending_selection, SelectMode::Character);
18844 } else {
18845 s.clear_pending();
18846 }
18847 });
18848 self.selections_did_change(
18849 false,
18850 &old_cursor_position,
18851 SelectionEffects::default(),
18852 window,
18853 cx,
18854 );
18855 }
18856
18857 pub fn transact(
18858 &mut self,
18859 window: &mut Window,
18860 cx: &mut Context<Self>,
18861 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18862 ) -> Option<TransactionId> {
18863 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18864 this.start_transaction_at(Instant::now(), window, cx);
18865 update(this, window, cx);
18866 this.end_transaction_at(Instant::now(), cx)
18867 })
18868 }
18869
18870 pub fn start_transaction_at(
18871 &mut self,
18872 now: Instant,
18873 window: &mut Window,
18874 cx: &mut Context<Self>,
18875 ) -> Option<TransactionId> {
18876 self.end_selection(window, cx);
18877 if let Some(tx_id) = self
18878 .buffer
18879 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18880 {
18881 self.selection_history
18882 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18883 cx.emit(EditorEvent::TransactionBegun {
18884 transaction_id: tx_id,
18885 });
18886 Some(tx_id)
18887 } else {
18888 None
18889 }
18890 }
18891
18892 pub fn end_transaction_at(
18893 &mut self,
18894 now: Instant,
18895 cx: &mut Context<Self>,
18896 ) -> Option<TransactionId> {
18897 if let Some(transaction_id) = self
18898 .buffer
18899 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18900 {
18901 if let Some((_, end_selections)) =
18902 self.selection_history.transaction_mut(transaction_id)
18903 {
18904 *end_selections = Some(self.selections.disjoint_anchors_arc());
18905 } else {
18906 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18907 }
18908
18909 cx.emit(EditorEvent::Edited { transaction_id });
18910 Some(transaction_id)
18911 } else {
18912 None
18913 }
18914 }
18915
18916 pub fn modify_transaction_selection_history(
18917 &mut self,
18918 transaction_id: TransactionId,
18919 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18920 ) -> bool {
18921 self.selection_history
18922 .transaction_mut(transaction_id)
18923 .map(modify)
18924 .is_some()
18925 }
18926
18927 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18928 if self.selection_mark_mode {
18929 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18930 s.move_with(|_, sel| {
18931 sel.collapse_to(sel.head(), SelectionGoal::None);
18932 });
18933 })
18934 }
18935 self.selection_mark_mode = true;
18936 cx.notify();
18937 }
18938
18939 pub fn swap_selection_ends(
18940 &mut self,
18941 _: &actions::SwapSelectionEnds,
18942 window: &mut Window,
18943 cx: &mut Context<Self>,
18944 ) {
18945 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18946 s.move_with(|_, sel| {
18947 if sel.start != sel.end {
18948 sel.reversed = !sel.reversed
18949 }
18950 });
18951 });
18952 self.request_autoscroll(Autoscroll::newest(), cx);
18953 cx.notify();
18954 }
18955
18956 pub fn toggle_focus(
18957 workspace: &mut Workspace,
18958 _: &actions::ToggleFocus,
18959 window: &mut Window,
18960 cx: &mut Context<Workspace>,
18961 ) {
18962 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18963 return;
18964 };
18965 workspace.activate_item(&item, true, true, window, cx);
18966 }
18967
18968 pub fn toggle_fold(
18969 &mut self,
18970 _: &actions::ToggleFold,
18971 window: &mut Window,
18972 cx: &mut Context<Self>,
18973 ) {
18974 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18975 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18976 let selection = self.selections.newest::<Point>(&display_map);
18977
18978 let range = if selection.is_empty() {
18979 let point = selection.head().to_display_point(&display_map);
18980 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18981 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18982 .to_point(&display_map);
18983 start..end
18984 } else {
18985 selection.range()
18986 };
18987 if display_map.folds_in_range(range).next().is_some() {
18988 self.unfold_lines(&Default::default(), window, cx)
18989 } else {
18990 self.fold(&Default::default(), window, cx)
18991 }
18992 } else {
18993 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18994 let buffer_ids: HashSet<_> = self
18995 .selections
18996 .disjoint_anchor_ranges()
18997 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18998 .collect();
18999
19000 let should_unfold = buffer_ids
19001 .iter()
19002 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19003
19004 for buffer_id in buffer_ids {
19005 if should_unfold {
19006 self.unfold_buffer(buffer_id, cx);
19007 } else {
19008 self.fold_buffer(buffer_id, cx);
19009 }
19010 }
19011 }
19012 }
19013
19014 pub fn toggle_fold_recursive(
19015 &mut self,
19016 _: &actions::ToggleFoldRecursive,
19017 window: &mut Window,
19018 cx: &mut Context<Self>,
19019 ) {
19020 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19021
19022 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19023 let range = if selection.is_empty() {
19024 let point = selection.head().to_display_point(&display_map);
19025 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19026 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19027 .to_point(&display_map);
19028 start..end
19029 } else {
19030 selection.range()
19031 };
19032 if display_map.folds_in_range(range).next().is_some() {
19033 self.unfold_recursive(&Default::default(), window, cx)
19034 } else {
19035 self.fold_recursive(&Default::default(), window, cx)
19036 }
19037 }
19038
19039 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19040 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19041 let mut to_fold = Vec::new();
19042 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19043 let selections = self.selections.all_adjusted(&display_map);
19044
19045 for selection in selections {
19046 let range = selection.range().sorted();
19047 let buffer_start_row = range.start.row;
19048
19049 if range.start.row != range.end.row {
19050 let mut found = false;
19051 let mut row = range.start.row;
19052 while row <= range.end.row {
19053 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19054 {
19055 found = true;
19056 row = crease.range().end.row + 1;
19057 to_fold.push(crease);
19058 } else {
19059 row += 1
19060 }
19061 }
19062 if found {
19063 continue;
19064 }
19065 }
19066
19067 for row in (0..=range.start.row).rev() {
19068 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19069 && crease.range().end.row >= buffer_start_row
19070 {
19071 to_fold.push(crease);
19072 if row <= range.start.row {
19073 break;
19074 }
19075 }
19076 }
19077 }
19078
19079 self.fold_creases(to_fold, true, window, cx);
19080 } else {
19081 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19082 let buffer_ids = self
19083 .selections
19084 .disjoint_anchor_ranges()
19085 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19086 .collect::<HashSet<_>>();
19087 for buffer_id in buffer_ids {
19088 self.fold_buffer(buffer_id, cx);
19089 }
19090 }
19091 }
19092
19093 pub fn toggle_fold_all(
19094 &mut self,
19095 _: &actions::ToggleFoldAll,
19096 window: &mut Window,
19097 cx: &mut Context<Self>,
19098 ) {
19099 if self.buffer.read(cx).is_singleton() {
19100 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19101 let has_folds = display_map
19102 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19103 .next()
19104 .is_some();
19105
19106 if has_folds {
19107 self.unfold_all(&actions::UnfoldAll, window, cx);
19108 } else {
19109 self.fold_all(&actions::FoldAll, window, cx);
19110 }
19111 } else {
19112 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19113 let should_unfold = buffer_ids
19114 .iter()
19115 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19116
19117 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19118 editor
19119 .update_in(cx, |editor, _, cx| {
19120 for buffer_id in buffer_ids {
19121 if should_unfold {
19122 editor.unfold_buffer(buffer_id, cx);
19123 } else {
19124 editor.fold_buffer(buffer_id, cx);
19125 }
19126 }
19127 })
19128 .ok();
19129 });
19130 }
19131 }
19132
19133 fn fold_at_level(
19134 &mut self,
19135 fold_at: &FoldAtLevel,
19136 window: &mut Window,
19137 cx: &mut Context<Self>,
19138 ) {
19139 if !self.buffer.read(cx).is_singleton() {
19140 return;
19141 }
19142
19143 let fold_at_level = fold_at.0;
19144 let snapshot = self.buffer.read(cx).snapshot(cx);
19145 let mut to_fold = Vec::new();
19146 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19147
19148 let row_ranges_to_keep: Vec<Range<u32>> = self
19149 .selections
19150 .all::<Point>(&self.display_snapshot(cx))
19151 .into_iter()
19152 .map(|sel| sel.start.row..sel.end.row)
19153 .collect();
19154
19155 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19156 while start_row < end_row {
19157 match self
19158 .snapshot(window, cx)
19159 .crease_for_buffer_row(MultiBufferRow(start_row))
19160 {
19161 Some(crease) => {
19162 let nested_start_row = crease.range().start.row + 1;
19163 let nested_end_row = crease.range().end.row;
19164
19165 if current_level < fold_at_level {
19166 stack.push((nested_start_row, nested_end_row, current_level + 1));
19167 } else if current_level == fold_at_level {
19168 // Fold iff there is no selection completely contained within the fold region
19169 if !row_ranges_to_keep.iter().any(|selection| {
19170 selection.end >= nested_start_row
19171 && selection.start <= nested_end_row
19172 }) {
19173 to_fold.push(crease);
19174 }
19175 }
19176
19177 start_row = nested_end_row + 1;
19178 }
19179 None => start_row += 1,
19180 }
19181 }
19182 }
19183
19184 self.fold_creases(to_fold, true, window, cx);
19185 }
19186
19187 pub fn fold_at_level_1(
19188 &mut self,
19189 _: &actions::FoldAtLevel1,
19190 window: &mut Window,
19191 cx: &mut Context<Self>,
19192 ) {
19193 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19194 }
19195
19196 pub fn fold_at_level_2(
19197 &mut self,
19198 _: &actions::FoldAtLevel2,
19199 window: &mut Window,
19200 cx: &mut Context<Self>,
19201 ) {
19202 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19203 }
19204
19205 pub fn fold_at_level_3(
19206 &mut self,
19207 _: &actions::FoldAtLevel3,
19208 window: &mut Window,
19209 cx: &mut Context<Self>,
19210 ) {
19211 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19212 }
19213
19214 pub fn fold_at_level_4(
19215 &mut self,
19216 _: &actions::FoldAtLevel4,
19217 window: &mut Window,
19218 cx: &mut Context<Self>,
19219 ) {
19220 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19221 }
19222
19223 pub fn fold_at_level_5(
19224 &mut self,
19225 _: &actions::FoldAtLevel5,
19226 window: &mut Window,
19227 cx: &mut Context<Self>,
19228 ) {
19229 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19230 }
19231
19232 pub fn fold_at_level_6(
19233 &mut self,
19234 _: &actions::FoldAtLevel6,
19235 window: &mut Window,
19236 cx: &mut Context<Self>,
19237 ) {
19238 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19239 }
19240
19241 pub fn fold_at_level_7(
19242 &mut self,
19243 _: &actions::FoldAtLevel7,
19244 window: &mut Window,
19245 cx: &mut Context<Self>,
19246 ) {
19247 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19248 }
19249
19250 pub fn fold_at_level_8(
19251 &mut self,
19252 _: &actions::FoldAtLevel8,
19253 window: &mut Window,
19254 cx: &mut Context<Self>,
19255 ) {
19256 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19257 }
19258
19259 pub fn fold_at_level_9(
19260 &mut self,
19261 _: &actions::FoldAtLevel9,
19262 window: &mut Window,
19263 cx: &mut Context<Self>,
19264 ) {
19265 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19266 }
19267
19268 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19269 if self.buffer.read(cx).is_singleton() {
19270 let mut fold_ranges = Vec::new();
19271 let snapshot = self.buffer.read(cx).snapshot(cx);
19272
19273 for row in 0..snapshot.max_row().0 {
19274 if let Some(foldable_range) = self
19275 .snapshot(window, cx)
19276 .crease_for_buffer_row(MultiBufferRow(row))
19277 {
19278 fold_ranges.push(foldable_range);
19279 }
19280 }
19281
19282 self.fold_creases(fold_ranges, true, window, cx);
19283 } else {
19284 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19285 editor
19286 .update_in(cx, |editor, _, cx| {
19287 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19288 editor.fold_buffer(buffer_id, cx);
19289 }
19290 })
19291 .ok();
19292 });
19293 }
19294 }
19295
19296 pub fn fold_function_bodies(
19297 &mut self,
19298 _: &actions::FoldFunctionBodies,
19299 window: &mut Window,
19300 cx: &mut Context<Self>,
19301 ) {
19302 let snapshot = self.buffer.read(cx).snapshot(cx);
19303
19304 let ranges = snapshot
19305 .text_object_ranges(
19306 MultiBufferOffset(0)..snapshot.len(),
19307 TreeSitterOptions::default(),
19308 )
19309 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19310 .collect::<Vec<_>>();
19311
19312 let creases = ranges
19313 .into_iter()
19314 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19315 .collect();
19316
19317 self.fold_creases(creases, true, window, cx);
19318 }
19319
19320 pub fn fold_recursive(
19321 &mut self,
19322 _: &actions::FoldRecursive,
19323 window: &mut Window,
19324 cx: &mut Context<Self>,
19325 ) {
19326 let mut to_fold = Vec::new();
19327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19328 let selections = self.selections.all_adjusted(&display_map);
19329
19330 for selection in selections {
19331 let range = selection.range().sorted();
19332 let buffer_start_row = range.start.row;
19333
19334 if range.start.row != range.end.row {
19335 let mut found = false;
19336 for row in range.start.row..=range.end.row {
19337 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19338 found = true;
19339 to_fold.push(crease);
19340 }
19341 }
19342 if found {
19343 continue;
19344 }
19345 }
19346
19347 for row in (0..=range.start.row).rev() {
19348 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19349 if crease.range().end.row >= buffer_start_row {
19350 to_fold.push(crease);
19351 } else {
19352 break;
19353 }
19354 }
19355 }
19356 }
19357
19358 self.fold_creases(to_fold, true, window, cx);
19359 }
19360
19361 pub fn fold_at(
19362 &mut self,
19363 buffer_row: MultiBufferRow,
19364 window: &mut Window,
19365 cx: &mut Context<Self>,
19366 ) {
19367 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19368
19369 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19370 let autoscroll = self
19371 .selections
19372 .all::<Point>(&display_map)
19373 .iter()
19374 .any(|selection| crease.range().overlaps(&selection.range()));
19375
19376 self.fold_creases(vec![crease], autoscroll, window, cx);
19377 }
19378 }
19379
19380 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19381 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19382 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19383 let buffer = display_map.buffer_snapshot();
19384 let selections = self.selections.all::<Point>(&display_map);
19385 let ranges = selections
19386 .iter()
19387 .map(|s| {
19388 let range = s.display_range(&display_map).sorted();
19389 let mut start = range.start.to_point(&display_map);
19390 let mut end = range.end.to_point(&display_map);
19391 start.column = 0;
19392 end.column = buffer.line_len(MultiBufferRow(end.row));
19393 start..end
19394 })
19395 .collect::<Vec<_>>();
19396
19397 self.unfold_ranges(&ranges, true, true, cx);
19398 } else {
19399 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19400 let buffer_ids = self
19401 .selections
19402 .disjoint_anchor_ranges()
19403 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19404 .collect::<HashSet<_>>();
19405 for buffer_id in buffer_ids {
19406 self.unfold_buffer(buffer_id, cx);
19407 }
19408 }
19409 }
19410
19411 pub fn unfold_recursive(
19412 &mut self,
19413 _: &UnfoldRecursive,
19414 _window: &mut Window,
19415 cx: &mut Context<Self>,
19416 ) {
19417 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19418 let selections = self.selections.all::<Point>(&display_map);
19419 let ranges = selections
19420 .iter()
19421 .map(|s| {
19422 let mut range = s.display_range(&display_map).sorted();
19423 *range.start.column_mut() = 0;
19424 *range.end.column_mut() = display_map.line_len(range.end.row());
19425 let start = range.start.to_point(&display_map);
19426 let end = range.end.to_point(&display_map);
19427 start..end
19428 })
19429 .collect::<Vec<_>>();
19430
19431 self.unfold_ranges(&ranges, true, true, cx);
19432 }
19433
19434 pub fn unfold_at(
19435 &mut self,
19436 buffer_row: MultiBufferRow,
19437 _window: &mut Window,
19438 cx: &mut Context<Self>,
19439 ) {
19440 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19441
19442 let intersection_range = Point::new(buffer_row.0, 0)
19443 ..Point::new(
19444 buffer_row.0,
19445 display_map.buffer_snapshot().line_len(buffer_row),
19446 );
19447
19448 let autoscroll = self
19449 .selections
19450 .all::<Point>(&display_map)
19451 .iter()
19452 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19453
19454 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19455 }
19456
19457 pub fn unfold_all(
19458 &mut self,
19459 _: &actions::UnfoldAll,
19460 _window: &mut Window,
19461 cx: &mut Context<Self>,
19462 ) {
19463 if self.buffer.read(cx).is_singleton() {
19464 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19465 self.unfold_ranges(
19466 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19467 true,
19468 true,
19469 cx,
19470 );
19471 } else {
19472 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19473 editor
19474 .update(cx, |editor, cx| {
19475 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19476 editor.unfold_buffer(buffer_id, cx);
19477 }
19478 })
19479 .ok();
19480 });
19481 }
19482 }
19483
19484 pub fn fold_selected_ranges(
19485 &mut self,
19486 _: &FoldSelectedRanges,
19487 window: &mut Window,
19488 cx: &mut Context<Self>,
19489 ) {
19490 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19491 let selections = self.selections.all_adjusted(&display_map);
19492 let ranges = selections
19493 .into_iter()
19494 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19495 .collect::<Vec<_>>();
19496 self.fold_creases(ranges, true, window, cx);
19497 }
19498
19499 pub fn fold_ranges<T: ToOffset + Clone>(
19500 &mut self,
19501 ranges: Vec<Range<T>>,
19502 auto_scroll: bool,
19503 window: &mut Window,
19504 cx: &mut Context<Self>,
19505 ) {
19506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19507 let ranges = ranges
19508 .into_iter()
19509 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19510 .collect::<Vec<_>>();
19511 self.fold_creases(ranges, auto_scroll, window, cx);
19512 }
19513
19514 pub fn fold_creases<T: ToOffset + Clone>(
19515 &mut self,
19516 creases: Vec<Crease<T>>,
19517 auto_scroll: bool,
19518 _window: &mut Window,
19519 cx: &mut Context<Self>,
19520 ) {
19521 if creases.is_empty() {
19522 return;
19523 }
19524
19525 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19526
19527 if auto_scroll {
19528 self.request_autoscroll(Autoscroll::fit(), cx);
19529 }
19530
19531 cx.notify();
19532
19533 self.scrollbar_marker_state.dirty = true;
19534 self.folds_did_change(cx);
19535 }
19536
19537 /// Removes any folds whose ranges intersect any of the given ranges.
19538 pub fn unfold_ranges<T: ToOffset + Clone>(
19539 &mut self,
19540 ranges: &[Range<T>],
19541 inclusive: bool,
19542 auto_scroll: bool,
19543 cx: &mut Context<Self>,
19544 ) {
19545 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19546 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19547 });
19548 self.folds_did_change(cx);
19549 }
19550
19551 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19552 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19553 return;
19554 }
19555
19556 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19557 self.display_map.update(cx, |display_map, cx| {
19558 display_map.fold_buffers([buffer_id], cx)
19559 });
19560
19561 let snapshot = self.display_snapshot(cx);
19562 self.selections.change_with(&snapshot, |selections| {
19563 selections.remove_selections_from_buffer(buffer_id);
19564 });
19565
19566 cx.emit(EditorEvent::BufferFoldToggled {
19567 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19568 folded: true,
19569 });
19570 cx.notify();
19571 }
19572
19573 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19574 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19575 return;
19576 }
19577 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19578 self.display_map.update(cx, |display_map, cx| {
19579 display_map.unfold_buffers([buffer_id], cx);
19580 });
19581 cx.emit(EditorEvent::BufferFoldToggled {
19582 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19583 folded: false,
19584 });
19585 cx.notify();
19586 }
19587
19588 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19589 self.display_map.read(cx).is_buffer_folded(buffer)
19590 }
19591
19592 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19593 self.display_map.read(cx).folded_buffers()
19594 }
19595
19596 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19597 self.display_map.update(cx, |display_map, cx| {
19598 display_map.disable_header_for_buffer(buffer_id, cx);
19599 });
19600 cx.notify();
19601 }
19602
19603 /// Removes any folds with the given ranges.
19604 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19605 &mut self,
19606 ranges: &[Range<T>],
19607 type_id: TypeId,
19608 auto_scroll: bool,
19609 cx: &mut Context<Self>,
19610 ) {
19611 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19612 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19613 });
19614 self.folds_did_change(cx);
19615 }
19616
19617 fn remove_folds_with<T: ToOffset + Clone>(
19618 &mut self,
19619 ranges: &[Range<T>],
19620 auto_scroll: bool,
19621 cx: &mut Context<Self>,
19622 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19623 ) {
19624 if ranges.is_empty() {
19625 return;
19626 }
19627
19628 let mut buffers_affected = HashSet::default();
19629 let multi_buffer = self.buffer().read(cx);
19630 for range in ranges {
19631 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19632 buffers_affected.insert(buffer.read(cx).remote_id());
19633 };
19634 }
19635
19636 self.display_map.update(cx, update);
19637
19638 if auto_scroll {
19639 self.request_autoscroll(Autoscroll::fit(), cx);
19640 }
19641
19642 cx.notify();
19643 self.scrollbar_marker_state.dirty = true;
19644 self.active_indent_guides_state.dirty = true;
19645 }
19646
19647 pub fn update_renderer_widths(
19648 &mut self,
19649 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19650 cx: &mut Context<Self>,
19651 ) -> bool {
19652 self.display_map
19653 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19654 }
19655
19656 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19657 self.display_map.read(cx).fold_placeholder.clone()
19658 }
19659
19660 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19661 self.use_base_text_line_numbers = show;
19662 }
19663
19664 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19665 self.buffer.update(cx, |buffer, cx| {
19666 buffer.set_all_diff_hunks_expanded(cx);
19667 });
19668 }
19669
19670 pub fn expand_all_diff_hunks(
19671 &mut self,
19672 _: &ExpandAllDiffHunks,
19673 _window: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 self.buffer.update(cx, |buffer, cx| {
19677 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19678 });
19679 }
19680
19681 pub fn collapse_all_diff_hunks(
19682 &mut self,
19683 _: &CollapseAllDiffHunks,
19684 _window: &mut Window,
19685 cx: &mut Context<Self>,
19686 ) {
19687 self.buffer.update(cx, |buffer, cx| {
19688 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19689 });
19690 }
19691
19692 pub fn toggle_selected_diff_hunks(
19693 &mut self,
19694 _: &ToggleSelectedDiffHunks,
19695 _window: &mut Window,
19696 cx: &mut Context<Self>,
19697 ) {
19698 let ranges: Vec<_> = self
19699 .selections
19700 .disjoint_anchors()
19701 .iter()
19702 .map(|s| s.range())
19703 .collect();
19704 self.toggle_diff_hunks_in_ranges(ranges, cx);
19705 }
19706
19707 pub fn diff_hunks_in_ranges<'a>(
19708 &'a self,
19709 ranges: &'a [Range<Anchor>],
19710 buffer: &'a MultiBufferSnapshot,
19711 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19712 ranges.iter().flat_map(move |range| {
19713 let end_excerpt_id = range.end.excerpt_id;
19714 let range = range.to_point(buffer);
19715 let mut peek_end = range.end;
19716 if range.end.row < buffer.max_row().0 {
19717 peek_end = Point::new(range.end.row + 1, 0);
19718 }
19719 buffer
19720 .diff_hunks_in_range(range.start..peek_end)
19721 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19722 })
19723 }
19724
19725 pub fn has_stageable_diff_hunks_in_ranges(
19726 &self,
19727 ranges: &[Range<Anchor>],
19728 snapshot: &MultiBufferSnapshot,
19729 ) -> bool {
19730 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19731 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19732 }
19733
19734 pub fn toggle_staged_selected_diff_hunks(
19735 &mut self,
19736 _: &::git::ToggleStaged,
19737 _: &mut Window,
19738 cx: &mut Context<Self>,
19739 ) {
19740 let snapshot = self.buffer.read(cx).snapshot(cx);
19741 let ranges: Vec<_> = self
19742 .selections
19743 .disjoint_anchors()
19744 .iter()
19745 .map(|s| s.range())
19746 .collect();
19747 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19748 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19749 }
19750
19751 pub fn set_render_diff_hunk_controls(
19752 &mut self,
19753 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19754 cx: &mut Context<Self>,
19755 ) {
19756 self.render_diff_hunk_controls = render_diff_hunk_controls;
19757 cx.notify();
19758 }
19759
19760 pub fn stage_and_next(
19761 &mut self,
19762 _: &::git::StageAndNext,
19763 window: &mut Window,
19764 cx: &mut Context<Self>,
19765 ) {
19766 self.do_stage_or_unstage_and_next(true, window, cx);
19767 }
19768
19769 pub fn unstage_and_next(
19770 &mut self,
19771 _: &::git::UnstageAndNext,
19772 window: &mut Window,
19773 cx: &mut Context<Self>,
19774 ) {
19775 self.do_stage_or_unstage_and_next(false, window, cx);
19776 }
19777
19778 pub fn stage_or_unstage_diff_hunks(
19779 &mut self,
19780 stage: bool,
19781 ranges: Vec<Range<Anchor>>,
19782 cx: &mut Context<Self>,
19783 ) {
19784 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19785 cx.spawn(async move |this, cx| {
19786 task.await?;
19787 this.update(cx, |this, cx| {
19788 let snapshot = this.buffer.read(cx).snapshot(cx);
19789 let chunk_by = this
19790 .diff_hunks_in_ranges(&ranges, &snapshot)
19791 .chunk_by(|hunk| hunk.buffer_id);
19792 for (buffer_id, hunks) in &chunk_by {
19793 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19794 }
19795 })
19796 })
19797 .detach_and_log_err(cx);
19798 }
19799
19800 fn save_buffers_for_ranges_if_needed(
19801 &mut self,
19802 ranges: &[Range<Anchor>],
19803 cx: &mut Context<Editor>,
19804 ) -> Task<Result<()>> {
19805 let multibuffer = self.buffer.read(cx);
19806 let snapshot = multibuffer.read(cx);
19807 let buffer_ids: HashSet<_> = ranges
19808 .iter()
19809 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19810 .collect();
19811 drop(snapshot);
19812
19813 let mut buffers = HashSet::default();
19814 for buffer_id in buffer_ids {
19815 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19816 let buffer = buffer_entity.read(cx);
19817 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19818 {
19819 buffers.insert(buffer_entity);
19820 }
19821 }
19822 }
19823
19824 if let Some(project) = &self.project {
19825 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19826 } else {
19827 Task::ready(Ok(()))
19828 }
19829 }
19830
19831 fn do_stage_or_unstage_and_next(
19832 &mut self,
19833 stage: bool,
19834 window: &mut Window,
19835 cx: &mut Context<Self>,
19836 ) {
19837 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19838
19839 if ranges.iter().any(|range| range.start != range.end) {
19840 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19841 return;
19842 }
19843
19844 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19845 let snapshot = self.snapshot(window, cx);
19846 let position = self
19847 .selections
19848 .newest::<Point>(&snapshot.display_snapshot)
19849 .head();
19850 let mut row = snapshot
19851 .buffer_snapshot()
19852 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19853 .find(|hunk| hunk.row_range.start.0 > position.row)
19854 .map(|hunk| hunk.row_range.start);
19855
19856 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19857 // Outside of the project diff editor, wrap around to the beginning.
19858 if !all_diff_hunks_expanded {
19859 row = row.or_else(|| {
19860 snapshot
19861 .buffer_snapshot()
19862 .diff_hunks_in_range(Point::zero()..position)
19863 .find(|hunk| hunk.row_range.end.0 < position.row)
19864 .map(|hunk| hunk.row_range.start)
19865 });
19866 }
19867
19868 if let Some(row) = row {
19869 let destination = Point::new(row.0, 0);
19870 let autoscroll = Autoscroll::center();
19871
19872 self.unfold_ranges(&[destination..destination], false, false, cx);
19873 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19874 s.select_ranges([destination..destination]);
19875 });
19876 }
19877 }
19878
19879 fn do_stage_or_unstage(
19880 &self,
19881 stage: bool,
19882 buffer_id: BufferId,
19883 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19884 cx: &mut App,
19885 ) -> Option<()> {
19886 let project = self.project()?;
19887 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19888 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19889 let buffer_snapshot = buffer.read(cx).snapshot();
19890 let file_exists = buffer_snapshot
19891 .file()
19892 .is_some_and(|file| file.disk_state().exists());
19893 diff.update(cx, |diff, cx| {
19894 diff.stage_or_unstage_hunks(
19895 stage,
19896 &hunks
19897 .map(|hunk| buffer_diff::DiffHunk {
19898 buffer_range: hunk.buffer_range,
19899 // We don't need to pass in word diffs here because they're only used for rendering and
19900 // this function changes internal state
19901 base_word_diffs: Vec::default(),
19902 buffer_word_diffs: Vec::default(),
19903 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19904 ..hunk.diff_base_byte_range.end.0,
19905 secondary_status: hunk.secondary_status,
19906 range: Point::zero()..Point::zero(), // unused
19907 })
19908 .collect::<Vec<_>>(),
19909 &buffer_snapshot,
19910 file_exists,
19911 cx,
19912 )
19913 });
19914 None
19915 }
19916
19917 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19918 let ranges: Vec<_> = self
19919 .selections
19920 .disjoint_anchors()
19921 .iter()
19922 .map(|s| s.range())
19923 .collect();
19924 self.buffer
19925 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19926 }
19927
19928 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19929 self.buffer.update(cx, |buffer, cx| {
19930 let ranges = vec![Anchor::min()..Anchor::max()];
19931 if !buffer.all_diff_hunks_expanded()
19932 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19933 {
19934 buffer.collapse_diff_hunks(ranges, cx);
19935 true
19936 } else {
19937 false
19938 }
19939 })
19940 }
19941
19942 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19943 if self.buffer.read(cx).all_diff_hunks_expanded() {
19944 return true;
19945 }
19946 let ranges = vec![Anchor::min()..Anchor::max()];
19947 self.buffer
19948 .read(cx)
19949 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19950 }
19951
19952 fn toggle_diff_hunks_in_ranges(
19953 &mut self,
19954 ranges: Vec<Range<Anchor>>,
19955 cx: &mut Context<Editor>,
19956 ) {
19957 self.buffer.update(cx, |buffer, cx| {
19958 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19959 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19960 })
19961 }
19962
19963 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19964 self.buffer.update(cx, |buffer, cx| {
19965 let snapshot = buffer.snapshot(cx);
19966 let excerpt_id = range.end.excerpt_id;
19967 let point_range = range.to_point(&snapshot);
19968 let expand = !buffer.single_hunk_is_expanded(range, cx);
19969 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19970 })
19971 }
19972
19973 pub(crate) fn apply_all_diff_hunks(
19974 &mut self,
19975 _: &ApplyAllDiffHunks,
19976 window: &mut Window,
19977 cx: &mut Context<Self>,
19978 ) {
19979 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19980
19981 let buffers = self.buffer.read(cx).all_buffers();
19982 for branch_buffer in buffers {
19983 branch_buffer.update(cx, |branch_buffer, cx| {
19984 branch_buffer.merge_into_base(Vec::new(), cx);
19985 });
19986 }
19987
19988 if let Some(project) = self.project.clone() {
19989 self.save(
19990 SaveOptions {
19991 format: true,
19992 autosave: false,
19993 },
19994 project,
19995 window,
19996 cx,
19997 )
19998 .detach_and_log_err(cx);
19999 }
20000 }
20001
20002 pub(crate) fn apply_selected_diff_hunks(
20003 &mut self,
20004 _: &ApplyDiffHunk,
20005 window: &mut Window,
20006 cx: &mut Context<Self>,
20007 ) {
20008 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20009 let snapshot = self.snapshot(window, cx);
20010 let hunks = snapshot.hunks_for_ranges(
20011 self.selections
20012 .all(&snapshot.display_snapshot)
20013 .into_iter()
20014 .map(|selection| selection.range()),
20015 );
20016 let mut ranges_by_buffer = HashMap::default();
20017 self.transact(window, cx, |editor, _window, cx| {
20018 for hunk in hunks {
20019 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20020 ranges_by_buffer
20021 .entry(buffer.clone())
20022 .or_insert_with(Vec::new)
20023 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20024 }
20025 }
20026
20027 for (buffer, ranges) in ranges_by_buffer {
20028 buffer.update(cx, |buffer, cx| {
20029 buffer.merge_into_base(ranges, cx);
20030 });
20031 }
20032 });
20033
20034 if let Some(project) = self.project.clone() {
20035 self.save(
20036 SaveOptions {
20037 format: true,
20038 autosave: false,
20039 },
20040 project,
20041 window,
20042 cx,
20043 )
20044 .detach_and_log_err(cx);
20045 }
20046 }
20047
20048 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20049 if hovered != self.gutter_hovered {
20050 self.gutter_hovered = hovered;
20051 cx.notify();
20052 }
20053 }
20054
20055 pub fn insert_blocks(
20056 &mut self,
20057 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20058 autoscroll: Option<Autoscroll>,
20059 cx: &mut Context<Self>,
20060 ) -> Vec<CustomBlockId> {
20061 let blocks = self
20062 .display_map
20063 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20064 if let Some(autoscroll) = autoscroll {
20065 self.request_autoscroll(autoscroll, cx);
20066 }
20067 cx.notify();
20068 blocks
20069 }
20070
20071 pub fn resize_blocks(
20072 &mut self,
20073 heights: HashMap<CustomBlockId, u32>,
20074 autoscroll: Option<Autoscroll>,
20075 cx: &mut Context<Self>,
20076 ) {
20077 self.display_map
20078 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20079 if let Some(autoscroll) = autoscroll {
20080 self.request_autoscroll(autoscroll, cx);
20081 }
20082 cx.notify();
20083 }
20084
20085 pub fn replace_blocks(
20086 &mut self,
20087 renderers: HashMap<CustomBlockId, RenderBlock>,
20088 autoscroll: Option<Autoscroll>,
20089 cx: &mut Context<Self>,
20090 ) {
20091 self.display_map
20092 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20093 if let Some(autoscroll) = autoscroll {
20094 self.request_autoscroll(autoscroll, cx);
20095 }
20096 cx.notify();
20097 }
20098
20099 pub fn remove_blocks(
20100 &mut self,
20101 block_ids: HashSet<CustomBlockId>,
20102 autoscroll: Option<Autoscroll>,
20103 cx: &mut Context<Self>,
20104 ) {
20105 self.display_map.update(cx, |display_map, cx| {
20106 display_map.remove_blocks(block_ids, cx)
20107 });
20108 if let Some(autoscroll) = autoscroll {
20109 self.request_autoscroll(autoscroll, cx);
20110 }
20111 cx.notify();
20112 }
20113
20114 pub fn row_for_block(
20115 &self,
20116 block_id: CustomBlockId,
20117 cx: &mut Context<Self>,
20118 ) -> Option<DisplayRow> {
20119 self.display_map
20120 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20121 }
20122
20123 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20124 self.focused_block = Some(focused_block);
20125 }
20126
20127 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20128 self.focused_block.take()
20129 }
20130
20131 pub fn insert_creases(
20132 &mut self,
20133 creases: impl IntoIterator<Item = Crease<Anchor>>,
20134 cx: &mut Context<Self>,
20135 ) -> Vec<CreaseId> {
20136 self.display_map
20137 .update(cx, |map, cx| map.insert_creases(creases, cx))
20138 }
20139
20140 pub fn remove_creases(
20141 &mut self,
20142 ids: impl IntoIterator<Item = CreaseId>,
20143 cx: &mut Context<Self>,
20144 ) -> Vec<(CreaseId, Range<Anchor>)> {
20145 self.display_map
20146 .update(cx, |map, cx| map.remove_creases(ids, cx))
20147 }
20148
20149 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20150 self.display_map
20151 .update(cx, |map, cx| map.snapshot(cx))
20152 .longest_row()
20153 }
20154
20155 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20156 self.display_map
20157 .update(cx, |map, cx| map.snapshot(cx))
20158 .max_point()
20159 }
20160
20161 pub fn text(&self, cx: &App) -> String {
20162 self.buffer.read(cx).read(cx).text()
20163 }
20164
20165 pub fn is_empty(&self, cx: &App) -> bool {
20166 self.buffer.read(cx).read(cx).is_empty()
20167 }
20168
20169 pub fn text_option(&self, cx: &App) -> Option<String> {
20170 let text = self.text(cx);
20171 let text = text.trim();
20172
20173 if text.is_empty() {
20174 return None;
20175 }
20176
20177 Some(text.to_string())
20178 }
20179
20180 pub fn set_text(
20181 &mut self,
20182 text: impl Into<Arc<str>>,
20183 window: &mut Window,
20184 cx: &mut Context<Self>,
20185 ) {
20186 self.transact(window, cx, |this, _, cx| {
20187 this.buffer
20188 .read(cx)
20189 .as_singleton()
20190 .expect("you can only call set_text on editors for singleton buffers")
20191 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20192 });
20193 }
20194
20195 pub fn display_text(&self, cx: &mut App) -> String {
20196 self.display_map
20197 .update(cx, |map, cx| map.snapshot(cx))
20198 .text()
20199 }
20200
20201 fn create_minimap(
20202 &self,
20203 minimap_settings: MinimapSettings,
20204 window: &mut Window,
20205 cx: &mut Context<Self>,
20206 ) -> Option<Entity<Self>> {
20207 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20208 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20209 }
20210
20211 fn initialize_new_minimap(
20212 &self,
20213 minimap_settings: MinimapSettings,
20214 window: &mut Window,
20215 cx: &mut Context<Self>,
20216 ) -> Entity<Self> {
20217 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20218
20219 let mut minimap = Editor::new_internal(
20220 EditorMode::Minimap {
20221 parent: cx.weak_entity(),
20222 },
20223 self.buffer.clone(),
20224 None,
20225 Some(self.display_map.clone()),
20226 window,
20227 cx,
20228 );
20229 minimap.scroll_manager.clone_state(&self.scroll_manager);
20230 minimap.set_text_style_refinement(TextStyleRefinement {
20231 font_size: Some(MINIMAP_FONT_SIZE),
20232 font_weight: Some(MINIMAP_FONT_WEIGHT),
20233 ..Default::default()
20234 });
20235 minimap.update_minimap_configuration(minimap_settings, cx);
20236 cx.new(|_| minimap)
20237 }
20238
20239 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20240 let current_line_highlight = minimap_settings
20241 .current_line_highlight
20242 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20243 self.set_current_line_highlight(Some(current_line_highlight));
20244 }
20245
20246 pub fn minimap(&self) -> Option<&Entity<Self>> {
20247 self.minimap
20248 .as_ref()
20249 .filter(|_| self.minimap_visibility.visible())
20250 }
20251
20252 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20253 let mut wrap_guides = smallvec![];
20254
20255 if self.show_wrap_guides == Some(false) {
20256 return wrap_guides;
20257 }
20258
20259 let settings = self.buffer.read(cx).language_settings(cx);
20260 if settings.show_wrap_guides {
20261 match self.soft_wrap_mode(cx) {
20262 SoftWrap::Column(soft_wrap) => {
20263 wrap_guides.push((soft_wrap as usize, true));
20264 }
20265 SoftWrap::Bounded(soft_wrap) => {
20266 wrap_guides.push((soft_wrap as usize, true));
20267 }
20268 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20269 }
20270 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20271 }
20272
20273 wrap_guides
20274 }
20275
20276 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20277 let settings = self.buffer.read(cx).language_settings(cx);
20278 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20279 match mode {
20280 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20281 SoftWrap::None
20282 }
20283 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20284 language_settings::SoftWrap::PreferredLineLength => {
20285 SoftWrap::Column(settings.preferred_line_length)
20286 }
20287 language_settings::SoftWrap::Bounded => {
20288 SoftWrap::Bounded(settings.preferred_line_length)
20289 }
20290 }
20291 }
20292
20293 pub fn set_soft_wrap_mode(
20294 &mut self,
20295 mode: language_settings::SoftWrap,
20296
20297 cx: &mut Context<Self>,
20298 ) {
20299 self.soft_wrap_mode_override = Some(mode);
20300 cx.notify();
20301 }
20302
20303 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20304 self.hard_wrap = hard_wrap;
20305 cx.notify();
20306 }
20307
20308 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20309 self.text_style_refinement = Some(style);
20310 }
20311
20312 /// called by the Element so we know what style we were most recently rendered with.
20313 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20314 // We intentionally do not inform the display map about the minimap style
20315 // so that wrapping is not recalculated and stays consistent for the editor
20316 // and its linked minimap.
20317 if !self.mode.is_minimap() {
20318 let font = style.text.font();
20319 let font_size = style.text.font_size.to_pixels(window.rem_size());
20320 let display_map = self
20321 .placeholder_display_map
20322 .as_ref()
20323 .filter(|_| self.is_empty(cx))
20324 .unwrap_or(&self.display_map);
20325
20326 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20327 }
20328 self.style = Some(style);
20329 }
20330
20331 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20332 if self.style.is_none() {
20333 self.style = Some(self.create_style(cx));
20334 }
20335 self.style.as_ref().unwrap()
20336 }
20337
20338 // Called by the element. This method is not designed to be called outside of the editor
20339 // element's layout code because it does not notify when rewrapping is computed synchronously.
20340 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20341 if self.is_empty(cx) {
20342 self.placeholder_display_map
20343 .as_ref()
20344 .map_or(false, |display_map| {
20345 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20346 })
20347 } else {
20348 self.display_map
20349 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20350 }
20351 }
20352
20353 pub fn set_soft_wrap(&mut self) {
20354 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20355 }
20356
20357 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20358 if self.soft_wrap_mode_override.is_some() {
20359 self.soft_wrap_mode_override.take();
20360 } else {
20361 let soft_wrap = match self.soft_wrap_mode(cx) {
20362 SoftWrap::GitDiff => return,
20363 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20364 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20365 language_settings::SoftWrap::None
20366 }
20367 };
20368 self.soft_wrap_mode_override = Some(soft_wrap);
20369 }
20370 cx.notify();
20371 }
20372
20373 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20374 let Some(workspace) = self.workspace() else {
20375 return;
20376 };
20377 let fs = workspace.read(cx).app_state().fs.clone();
20378 let current_show = TabBarSettings::get_global(cx).show;
20379 update_settings_file(fs, cx, move |setting, _| {
20380 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20381 });
20382 }
20383
20384 pub fn toggle_indent_guides(
20385 &mut self,
20386 _: &ToggleIndentGuides,
20387 _: &mut Window,
20388 cx: &mut Context<Self>,
20389 ) {
20390 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20391 self.buffer
20392 .read(cx)
20393 .language_settings(cx)
20394 .indent_guides
20395 .enabled
20396 });
20397 self.show_indent_guides = Some(!currently_enabled);
20398 cx.notify();
20399 }
20400
20401 fn should_show_indent_guides(&self) -> Option<bool> {
20402 self.show_indent_guides
20403 }
20404
20405 pub fn disable_indent_guides_for_buffer(
20406 &mut self,
20407 buffer_id: BufferId,
20408 cx: &mut Context<Self>,
20409 ) {
20410 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20411 cx.notify();
20412 }
20413
20414 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20415 self.buffers_with_disabled_indent_guides
20416 .contains(&buffer_id)
20417 }
20418
20419 pub fn toggle_line_numbers(
20420 &mut self,
20421 _: &ToggleLineNumbers,
20422 _: &mut Window,
20423 cx: &mut Context<Self>,
20424 ) {
20425 let mut editor_settings = EditorSettings::get_global(cx).clone();
20426 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20427 EditorSettings::override_global(editor_settings, cx);
20428 }
20429
20430 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20431 if let Some(show_line_numbers) = self.show_line_numbers {
20432 return show_line_numbers;
20433 }
20434 EditorSettings::get_global(cx).gutter.line_numbers
20435 }
20436
20437 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20438 match (
20439 self.use_relative_line_numbers,
20440 EditorSettings::get_global(cx).relative_line_numbers,
20441 ) {
20442 (None, setting) => setting,
20443 (Some(false), _) => RelativeLineNumbers::Disabled,
20444 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20445 (Some(true), _) => RelativeLineNumbers::Enabled,
20446 }
20447 }
20448
20449 pub fn toggle_relative_line_numbers(
20450 &mut self,
20451 _: &ToggleRelativeLineNumbers,
20452 _: &mut Window,
20453 cx: &mut Context<Self>,
20454 ) {
20455 let is_relative = self.relative_line_numbers(cx);
20456 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20457 }
20458
20459 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20460 self.use_relative_line_numbers = is_relative;
20461 cx.notify();
20462 }
20463
20464 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20465 self.show_gutter = show_gutter;
20466 cx.notify();
20467 }
20468
20469 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20470 self.show_scrollbars = ScrollbarAxes {
20471 horizontal: show,
20472 vertical: show,
20473 };
20474 cx.notify();
20475 }
20476
20477 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20478 self.show_scrollbars.vertical = show;
20479 cx.notify();
20480 }
20481
20482 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20483 self.show_scrollbars.horizontal = show;
20484 cx.notify();
20485 }
20486
20487 pub fn set_minimap_visibility(
20488 &mut self,
20489 minimap_visibility: MinimapVisibility,
20490 window: &mut Window,
20491 cx: &mut Context<Self>,
20492 ) {
20493 if self.minimap_visibility != minimap_visibility {
20494 if minimap_visibility.visible() && self.minimap.is_none() {
20495 let minimap_settings = EditorSettings::get_global(cx).minimap;
20496 self.minimap =
20497 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20498 }
20499 self.minimap_visibility = minimap_visibility;
20500 cx.notify();
20501 }
20502 }
20503
20504 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20505 self.set_show_scrollbars(false, cx);
20506 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20507 }
20508
20509 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20510 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20511 }
20512
20513 /// Normally the text in full mode and auto height editors is padded on the
20514 /// left side by roughly half a character width for improved hit testing.
20515 ///
20516 /// Use this method to disable this for cases where this is not wanted (e.g.
20517 /// if you want to align the editor text with some other text above or below)
20518 /// or if you want to add this padding to single-line editors.
20519 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20520 self.offset_content = offset_content;
20521 cx.notify();
20522 }
20523
20524 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20525 self.show_line_numbers = Some(show_line_numbers);
20526 cx.notify();
20527 }
20528
20529 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20530 self.disable_expand_excerpt_buttons = true;
20531 cx.notify();
20532 }
20533
20534 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20535 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20536 cx.notify();
20537 }
20538
20539 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20540 self.show_code_actions = Some(show_code_actions);
20541 cx.notify();
20542 }
20543
20544 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20545 self.show_runnables = Some(show_runnables);
20546 cx.notify();
20547 }
20548
20549 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20550 self.show_breakpoints = Some(show_breakpoints);
20551 cx.notify();
20552 }
20553
20554 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20555 if self.display_map.read(cx).masked != masked {
20556 self.display_map.update(cx, |map, _| map.masked = masked);
20557 }
20558 cx.notify()
20559 }
20560
20561 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20562 self.show_wrap_guides = Some(show_wrap_guides);
20563 cx.notify();
20564 }
20565
20566 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20567 self.show_indent_guides = Some(show_indent_guides);
20568 cx.notify();
20569 }
20570
20571 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20572 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20573 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20574 && let Some(dir) = file.abs_path(cx).parent()
20575 {
20576 return Some(dir.to_owned());
20577 }
20578 }
20579
20580 None
20581 }
20582
20583 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20584 self.active_excerpt(cx)?
20585 .1
20586 .read(cx)
20587 .file()
20588 .and_then(|f| f.as_local())
20589 }
20590
20591 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20592 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20593 let buffer = buffer.read(cx);
20594 if let Some(project_path) = buffer.project_path(cx) {
20595 let project = self.project()?.read(cx);
20596 project.absolute_path(&project_path, cx)
20597 } else {
20598 buffer
20599 .file()
20600 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20601 }
20602 })
20603 }
20604
20605 pub fn reveal_in_finder(
20606 &mut self,
20607 _: &RevealInFileManager,
20608 _window: &mut Window,
20609 cx: &mut Context<Self>,
20610 ) {
20611 if let Some(target) = self.target_file(cx) {
20612 cx.reveal_path(&target.abs_path(cx));
20613 }
20614 }
20615
20616 pub fn copy_path(
20617 &mut self,
20618 _: &zed_actions::workspace::CopyPath,
20619 _window: &mut Window,
20620 cx: &mut Context<Self>,
20621 ) {
20622 if let Some(path) = self.target_file_abs_path(cx)
20623 && let Some(path) = path.to_str()
20624 {
20625 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20626 } else {
20627 cx.propagate();
20628 }
20629 }
20630
20631 pub fn copy_relative_path(
20632 &mut self,
20633 _: &zed_actions::workspace::CopyRelativePath,
20634 _window: &mut Window,
20635 cx: &mut Context<Self>,
20636 ) {
20637 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20638 let project = self.project()?.read(cx);
20639 let path = buffer.read(cx).file()?.path();
20640 let path = path.display(project.path_style(cx));
20641 Some(path)
20642 }) {
20643 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20644 } else {
20645 cx.propagate();
20646 }
20647 }
20648
20649 /// Returns the project path for the editor's buffer, if any buffer is
20650 /// opened in the editor.
20651 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20652 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20653 buffer.read(cx).project_path(cx)
20654 } else {
20655 None
20656 }
20657 }
20658
20659 // Returns true if the editor handled a go-to-line request
20660 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20661 maybe!({
20662 let breakpoint_store = self.breakpoint_store.as_ref()?;
20663
20664 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20665 else {
20666 self.clear_row_highlights::<ActiveDebugLine>();
20667 return None;
20668 };
20669
20670 let position = active_stack_frame.position;
20671 let buffer_id = position.buffer_id?;
20672 let snapshot = self
20673 .project
20674 .as_ref()?
20675 .read(cx)
20676 .buffer_for_id(buffer_id, cx)?
20677 .read(cx)
20678 .snapshot();
20679
20680 let mut handled = false;
20681 for (id, ExcerptRange { context, .. }) in
20682 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20683 {
20684 if context.start.cmp(&position, &snapshot).is_ge()
20685 || context.end.cmp(&position, &snapshot).is_lt()
20686 {
20687 continue;
20688 }
20689 let snapshot = self.buffer.read(cx).snapshot(cx);
20690 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20691
20692 handled = true;
20693 self.clear_row_highlights::<ActiveDebugLine>();
20694
20695 self.go_to_line::<ActiveDebugLine>(
20696 multibuffer_anchor,
20697 Some(cx.theme().colors().editor_debugger_active_line_background),
20698 window,
20699 cx,
20700 );
20701
20702 cx.notify();
20703 }
20704
20705 handled.then_some(())
20706 })
20707 .is_some()
20708 }
20709
20710 pub fn copy_file_name_without_extension(
20711 &mut self,
20712 _: &CopyFileNameWithoutExtension,
20713 _: &mut Window,
20714 cx: &mut Context<Self>,
20715 ) {
20716 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20717 let file = buffer.read(cx).file()?;
20718 file.path().file_stem()
20719 }) {
20720 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20721 }
20722 }
20723
20724 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20725 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20726 let file = buffer.read(cx).file()?;
20727 Some(file.file_name(cx))
20728 }) {
20729 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20730 }
20731 }
20732
20733 pub fn toggle_git_blame(
20734 &mut self,
20735 _: &::git::Blame,
20736 window: &mut Window,
20737 cx: &mut Context<Self>,
20738 ) {
20739 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20740
20741 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20742 self.start_git_blame(true, window, cx);
20743 }
20744
20745 cx.notify();
20746 }
20747
20748 pub fn toggle_git_blame_inline(
20749 &mut self,
20750 _: &ToggleGitBlameInline,
20751 window: &mut Window,
20752 cx: &mut Context<Self>,
20753 ) {
20754 self.toggle_git_blame_inline_internal(true, window, cx);
20755 cx.notify();
20756 }
20757
20758 pub fn open_git_blame_commit(
20759 &mut self,
20760 _: &OpenGitBlameCommit,
20761 window: &mut Window,
20762 cx: &mut Context<Self>,
20763 ) {
20764 self.open_git_blame_commit_internal(window, cx);
20765 }
20766
20767 fn open_git_blame_commit_internal(
20768 &mut self,
20769 window: &mut Window,
20770 cx: &mut Context<Self>,
20771 ) -> Option<()> {
20772 let blame = self.blame.as_ref()?;
20773 let snapshot = self.snapshot(window, cx);
20774 let cursor = self
20775 .selections
20776 .newest::<Point>(&snapshot.display_snapshot)
20777 .head();
20778 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20779 let (_, blame_entry) = blame
20780 .update(cx, |blame, cx| {
20781 blame
20782 .blame_for_rows(
20783 &[RowInfo {
20784 buffer_id: Some(buffer.remote_id()),
20785 buffer_row: Some(point.row),
20786 ..Default::default()
20787 }],
20788 cx,
20789 )
20790 .next()
20791 })
20792 .flatten()?;
20793 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20794 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20795 let workspace = self.workspace()?.downgrade();
20796 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20797 None
20798 }
20799
20800 pub fn git_blame_inline_enabled(&self) -> bool {
20801 self.git_blame_inline_enabled
20802 }
20803
20804 pub fn toggle_selection_menu(
20805 &mut self,
20806 _: &ToggleSelectionMenu,
20807 _: &mut Window,
20808 cx: &mut Context<Self>,
20809 ) {
20810 self.show_selection_menu = self
20811 .show_selection_menu
20812 .map(|show_selections_menu| !show_selections_menu)
20813 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20814
20815 cx.notify();
20816 }
20817
20818 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20819 self.show_selection_menu
20820 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20821 }
20822
20823 fn start_git_blame(
20824 &mut self,
20825 user_triggered: bool,
20826 window: &mut Window,
20827 cx: &mut Context<Self>,
20828 ) {
20829 if let Some(project) = self.project() {
20830 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20831 && buffer.read(cx).file().is_none()
20832 {
20833 return;
20834 }
20835
20836 let focused = self.focus_handle(cx).contains_focused(window, cx);
20837
20838 let project = project.clone();
20839 let blame = cx
20840 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20841 self.blame_subscription =
20842 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20843 self.blame = Some(blame);
20844 }
20845 }
20846
20847 fn toggle_git_blame_inline_internal(
20848 &mut self,
20849 user_triggered: bool,
20850 window: &mut Window,
20851 cx: &mut Context<Self>,
20852 ) {
20853 if self.git_blame_inline_enabled {
20854 self.git_blame_inline_enabled = false;
20855 self.show_git_blame_inline = false;
20856 self.show_git_blame_inline_delay_task.take();
20857 } else {
20858 self.git_blame_inline_enabled = true;
20859 self.start_git_blame_inline(user_triggered, window, cx);
20860 }
20861
20862 cx.notify();
20863 }
20864
20865 fn start_git_blame_inline(
20866 &mut self,
20867 user_triggered: bool,
20868 window: &mut Window,
20869 cx: &mut Context<Self>,
20870 ) {
20871 self.start_git_blame(user_triggered, window, cx);
20872
20873 if ProjectSettings::get_global(cx)
20874 .git
20875 .inline_blame_delay()
20876 .is_some()
20877 {
20878 self.start_inline_blame_timer(window, cx);
20879 } else {
20880 self.show_git_blame_inline = true
20881 }
20882 }
20883
20884 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20885 self.blame.as_ref()
20886 }
20887
20888 pub fn show_git_blame_gutter(&self) -> bool {
20889 self.show_git_blame_gutter
20890 }
20891
20892 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20893 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20894 }
20895
20896 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20897 self.show_git_blame_inline
20898 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20899 && !self.newest_selection_head_on_empty_line(cx)
20900 && self.has_blame_entries(cx)
20901 }
20902
20903 fn has_blame_entries(&self, cx: &App) -> bool {
20904 self.blame()
20905 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20906 }
20907
20908 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20909 let cursor_anchor = self.selections.newest_anchor().head();
20910
20911 let snapshot = self.buffer.read(cx).snapshot(cx);
20912 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20913
20914 snapshot.line_len(buffer_row) == 0
20915 }
20916
20917 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20918 let buffer_and_selection = maybe!({
20919 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20920 let selection_range = selection.range();
20921
20922 let multi_buffer = self.buffer().read(cx);
20923 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20924 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20925
20926 let (buffer, range, _) = if selection.reversed {
20927 buffer_ranges.first()
20928 } else {
20929 buffer_ranges.last()
20930 }?;
20931
20932 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
20933 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
20934
20935 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
20936 let selection = start_row_in_buffer..end_row_in_buffer;
20937
20938 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
20939 };
20940
20941 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
20942
20943 Some((
20944 multi_buffer.buffer(buffer.remote_id()).unwrap(),
20945 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
20946 ..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
20947 ))
20948 });
20949
20950 let Some((buffer, selection)) = buffer_and_selection else {
20951 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20952 };
20953
20954 let Some(project) = self.project() else {
20955 return Task::ready(Err(anyhow!("editor does not have project")));
20956 };
20957
20958 project.update(cx, |project, cx| {
20959 project.get_permalink_to_line(&buffer, selection, cx)
20960 })
20961 }
20962
20963 pub fn copy_permalink_to_line(
20964 &mut self,
20965 _: &CopyPermalinkToLine,
20966 window: &mut Window,
20967 cx: &mut Context<Self>,
20968 ) {
20969 let permalink_task = self.get_permalink_to_line(cx);
20970 let workspace = self.workspace();
20971
20972 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20973 Ok(permalink) => {
20974 cx.update(|_, cx| {
20975 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20976 })
20977 .ok();
20978 }
20979 Err(err) => {
20980 let message = format!("Failed to copy permalink: {err}");
20981
20982 anyhow::Result::<()>::Err(err).log_err();
20983
20984 if let Some(workspace) = workspace {
20985 workspace
20986 .update_in(cx, |workspace, _, cx| {
20987 struct CopyPermalinkToLine;
20988
20989 workspace.show_toast(
20990 Toast::new(
20991 NotificationId::unique::<CopyPermalinkToLine>(),
20992 message,
20993 ),
20994 cx,
20995 )
20996 })
20997 .ok();
20998 }
20999 }
21000 })
21001 .detach();
21002 }
21003
21004 pub fn copy_file_location(
21005 &mut self,
21006 _: &CopyFileLocation,
21007 _: &mut Window,
21008 cx: &mut Context<Self>,
21009 ) {
21010 let selection = self
21011 .selections
21012 .newest::<Point>(&self.display_snapshot(cx))
21013 .start
21014 .row
21015 + 1;
21016 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21017 let project = self.project()?.read(cx);
21018 let file = buffer.read(cx).file()?;
21019 let path = file.path().display(project.path_style(cx));
21020
21021 Some(format!("{path}:{selection}"))
21022 }) {
21023 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21024 }
21025 }
21026
21027 pub fn open_permalink_to_line(
21028 &mut self,
21029 _: &OpenPermalinkToLine,
21030 window: &mut Window,
21031 cx: &mut Context<Self>,
21032 ) {
21033 let permalink_task = self.get_permalink_to_line(cx);
21034 let workspace = self.workspace();
21035
21036 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21037 Ok(permalink) => {
21038 cx.update(|_, cx| {
21039 cx.open_url(permalink.as_ref());
21040 })
21041 .ok();
21042 }
21043 Err(err) => {
21044 let message = format!("Failed to open permalink: {err}");
21045
21046 anyhow::Result::<()>::Err(err).log_err();
21047
21048 if let Some(workspace) = workspace {
21049 workspace
21050 .update(cx, |workspace, cx| {
21051 struct OpenPermalinkToLine;
21052
21053 workspace.show_toast(
21054 Toast::new(
21055 NotificationId::unique::<OpenPermalinkToLine>(),
21056 message,
21057 ),
21058 cx,
21059 )
21060 })
21061 .ok();
21062 }
21063 }
21064 })
21065 .detach();
21066 }
21067
21068 pub fn insert_uuid_v4(
21069 &mut self,
21070 _: &InsertUuidV4,
21071 window: &mut Window,
21072 cx: &mut Context<Self>,
21073 ) {
21074 self.insert_uuid(UuidVersion::V4, window, cx);
21075 }
21076
21077 pub fn insert_uuid_v7(
21078 &mut self,
21079 _: &InsertUuidV7,
21080 window: &mut Window,
21081 cx: &mut Context<Self>,
21082 ) {
21083 self.insert_uuid(UuidVersion::V7, window, cx);
21084 }
21085
21086 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21087 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21088 self.transact(window, cx, |this, window, cx| {
21089 let edits = this
21090 .selections
21091 .all::<Point>(&this.display_snapshot(cx))
21092 .into_iter()
21093 .map(|selection| {
21094 let uuid = match version {
21095 UuidVersion::V4 => uuid::Uuid::new_v4(),
21096 UuidVersion::V7 => uuid::Uuid::now_v7(),
21097 };
21098
21099 (selection.range(), uuid.to_string())
21100 });
21101 this.edit(edits, cx);
21102 this.refresh_edit_prediction(true, false, window, cx);
21103 });
21104 }
21105
21106 pub fn open_selections_in_multibuffer(
21107 &mut self,
21108 _: &OpenSelectionsInMultibuffer,
21109 window: &mut Window,
21110 cx: &mut Context<Self>,
21111 ) {
21112 let multibuffer = self.buffer.read(cx);
21113
21114 let Some(buffer) = multibuffer.as_singleton() else {
21115 return;
21116 };
21117
21118 let Some(workspace) = self.workspace() else {
21119 return;
21120 };
21121
21122 let title = multibuffer.title(cx).to_string();
21123
21124 let locations = self
21125 .selections
21126 .all_anchors(&self.display_snapshot(cx))
21127 .iter()
21128 .map(|selection| {
21129 (
21130 buffer.clone(),
21131 (selection.start.text_anchor..selection.end.text_anchor)
21132 .to_point(buffer.read(cx)),
21133 )
21134 })
21135 .into_group_map();
21136
21137 cx.spawn_in(window, async move |_, cx| {
21138 workspace.update_in(cx, |workspace, window, cx| {
21139 Self::open_locations_in_multibuffer(
21140 workspace,
21141 locations,
21142 format!("Selections for '{title}'"),
21143 false,
21144 false,
21145 MultibufferSelectionMode::All,
21146 window,
21147 cx,
21148 );
21149 })
21150 })
21151 .detach();
21152 }
21153
21154 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21155 /// last highlight added will be used.
21156 ///
21157 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21158 pub fn highlight_rows<T: 'static>(
21159 &mut self,
21160 range: Range<Anchor>,
21161 color: Hsla,
21162 options: RowHighlightOptions,
21163 cx: &mut Context<Self>,
21164 ) {
21165 let snapshot = self.buffer().read(cx).snapshot(cx);
21166 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21167 let ix = row_highlights.binary_search_by(|highlight| {
21168 Ordering::Equal
21169 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21170 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21171 });
21172
21173 if let Err(mut ix) = ix {
21174 let index = post_inc(&mut self.highlight_order);
21175
21176 // If this range intersects with the preceding highlight, then merge it with
21177 // the preceding highlight. Otherwise insert a new highlight.
21178 let mut merged = false;
21179 if ix > 0 {
21180 let prev_highlight = &mut row_highlights[ix - 1];
21181 if prev_highlight
21182 .range
21183 .end
21184 .cmp(&range.start, &snapshot)
21185 .is_ge()
21186 {
21187 ix -= 1;
21188 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21189 prev_highlight.range.end = range.end;
21190 }
21191 merged = true;
21192 prev_highlight.index = index;
21193 prev_highlight.color = color;
21194 prev_highlight.options = options;
21195 }
21196 }
21197
21198 if !merged {
21199 row_highlights.insert(
21200 ix,
21201 RowHighlight {
21202 range,
21203 index,
21204 color,
21205 options,
21206 type_id: TypeId::of::<T>(),
21207 },
21208 );
21209 }
21210
21211 // If any of the following highlights intersect with this one, merge them.
21212 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21213 let highlight = &row_highlights[ix];
21214 if next_highlight
21215 .range
21216 .start
21217 .cmp(&highlight.range.end, &snapshot)
21218 .is_le()
21219 {
21220 if next_highlight
21221 .range
21222 .end
21223 .cmp(&highlight.range.end, &snapshot)
21224 .is_gt()
21225 {
21226 row_highlights[ix].range.end = next_highlight.range.end;
21227 }
21228 row_highlights.remove(ix + 1);
21229 } else {
21230 break;
21231 }
21232 }
21233 }
21234 }
21235
21236 /// Remove any highlighted row ranges of the given type that intersect the
21237 /// given ranges.
21238 pub fn remove_highlighted_rows<T: 'static>(
21239 &mut self,
21240 ranges_to_remove: Vec<Range<Anchor>>,
21241 cx: &mut Context<Self>,
21242 ) {
21243 let snapshot = self.buffer().read(cx).snapshot(cx);
21244 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21245 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21246 row_highlights.retain(|highlight| {
21247 while let Some(range_to_remove) = ranges_to_remove.peek() {
21248 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21249 Ordering::Less | Ordering::Equal => {
21250 ranges_to_remove.next();
21251 }
21252 Ordering::Greater => {
21253 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21254 Ordering::Less | Ordering::Equal => {
21255 return false;
21256 }
21257 Ordering::Greater => break,
21258 }
21259 }
21260 }
21261 }
21262
21263 true
21264 })
21265 }
21266
21267 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21268 pub fn clear_row_highlights<T: 'static>(&mut self) {
21269 self.highlighted_rows.remove(&TypeId::of::<T>());
21270 }
21271
21272 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21273 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21274 self.highlighted_rows
21275 .get(&TypeId::of::<T>())
21276 .map_or(&[] as &[_], |vec| vec.as_slice())
21277 .iter()
21278 .map(|highlight| (highlight.range.clone(), highlight.color))
21279 }
21280
21281 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21282 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21283 /// Allows to ignore certain kinds of highlights.
21284 pub fn highlighted_display_rows(
21285 &self,
21286 window: &mut Window,
21287 cx: &mut App,
21288 ) -> BTreeMap<DisplayRow, LineHighlight> {
21289 let snapshot = self.snapshot(window, cx);
21290 let mut used_highlight_orders = HashMap::default();
21291 self.highlighted_rows
21292 .iter()
21293 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21294 .fold(
21295 BTreeMap::<DisplayRow, LineHighlight>::new(),
21296 |mut unique_rows, highlight| {
21297 let start = highlight.range.start.to_display_point(&snapshot);
21298 let end = highlight.range.end.to_display_point(&snapshot);
21299 let start_row = start.row().0;
21300 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21301 {
21302 end.row().0.saturating_sub(1)
21303 } else {
21304 end.row().0
21305 };
21306 for row in start_row..=end_row {
21307 let used_index =
21308 used_highlight_orders.entry(row).or_insert(highlight.index);
21309 if highlight.index >= *used_index {
21310 *used_index = highlight.index;
21311 unique_rows.insert(
21312 DisplayRow(row),
21313 LineHighlight {
21314 include_gutter: highlight.options.include_gutter,
21315 border: None,
21316 background: highlight.color.into(),
21317 type_id: Some(highlight.type_id),
21318 },
21319 );
21320 }
21321 }
21322 unique_rows
21323 },
21324 )
21325 }
21326
21327 pub fn highlighted_display_row_for_autoscroll(
21328 &self,
21329 snapshot: &DisplaySnapshot,
21330 ) -> Option<DisplayRow> {
21331 self.highlighted_rows
21332 .values()
21333 .flat_map(|highlighted_rows| highlighted_rows.iter())
21334 .filter_map(|highlight| {
21335 if highlight.options.autoscroll {
21336 Some(highlight.range.start.to_display_point(snapshot).row())
21337 } else {
21338 None
21339 }
21340 })
21341 .min()
21342 }
21343
21344 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21345 self.highlight_background::<SearchWithinRange>(
21346 ranges,
21347 |_, colors| colors.colors().editor_document_highlight_read_background,
21348 cx,
21349 )
21350 }
21351
21352 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21353 self.breadcrumb_header = Some(new_header);
21354 }
21355
21356 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21357 self.clear_background_highlights::<SearchWithinRange>(cx);
21358 }
21359
21360 pub fn highlight_background<T: 'static>(
21361 &mut self,
21362 ranges: &[Range<Anchor>],
21363 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21364 cx: &mut Context<Self>,
21365 ) {
21366 self.background_highlights.insert(
21367 HighlightKey::Type(TypeId::of::<T>()),
21368 (Arc::new(color_fetcher), Arc::from(ranges)),
21369 );
21370 self.scrollbar_marker_state.dirty = true;
21371 cx.notify();
21372 }
21373
21374 pub fn highlight_background_key<T: 'static>(
21375 &mut self,
21376 key: usize,
21377 ranges: &[Range<Anchor>],
21378 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21379 cx: &mut Context<Self>,
21380 ) {
21381 self.background_highlights.insert(
21382 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21383 (Arc::new(color_fetcher), Arc::from(ranges)),
21384 );
21385 self.scrollbar_marker_state.dirty = true;
21386 cx.notify();
21387 }
21388
21389 pub fn clear_background_highlights<T: 'static>(
21390 &mut self,
21391 cx: &mut Context<Self>,
21392 ) -> Option<BackgroundHighlight> {
21393 let text_highlights = self
21394 .background_highlights
21395 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21396 if !text_highlights.1.is_empty() {
21397 self.scrollbar_marker_state.dirty = true;
21398 cx.notify();
21399 }
21400 Some(text_highlights)
21401 }
21402
21403 pub fn highlight_gutter<T: 'static>(
21404 &mut self,
21405 ranges: impl Into<Vec<Range<Anchor>>>,
21406 color_fetcher: fn(&App) -> Hsla,
21407 cx: &mut Context<Self>,
21408 ) {
21409 self.gutter_highlights
21410 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21411 cx.notify();
21412 }
21413
21414 pub fn clear_gutter_highlights<T: 'static>(
21415 &mut self,
21416 cx: &mut Context<Self>,
21417 ) -> Option<GutterHighlight> {
21418 cx.notify();
21419 self.gutter_highlights.remove(&TypeId::of::<T>())
21420 }
21421
21422 pub fn insert_gutter_highlight<T: 'static>(
21423 &mut self,
21424 range: Range<Anchor>,
21425 color_fetcher: fn(&App) -> Hsla,
21426 cx: &mut Context<Self>,
21427 ) {
21428 let snapshot = self.buffer().read(cx).snapshot(cx);
21429 let mut highlights = self
21430 .gutter_highlights
21431 .remove(&TypeId::of::<T>())
21432 .map(|(_, highlights)| highlights)
21433 .unwrap_or_default();
21434 let ix = highlights.binary_search_by(|highlight| {
21435 Ordering::Equal
21436 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21437 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21438 });
21439 if let Err(ix) = ix {
21440 highlights.insert(ix, range);
21441 }
21442 self.gutter_highlights
21443 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21444 }
21445
21446 pub fn remove_gutter_highlights<T: 'static>(
21447 &mut self,
21448 ranges_to_remove: Vec<Range<Anchor>>,
21449 cx: &mut Context<Self>,
21450 ) {
21451 let snapshot = self.buffer().read(cx).snapshot(cx);
21452 let Some((color_fetcher, mut gutter_highlights)) =
21453 self.gutter_highlights.remove(&TypeId::of::<T>())
21454 else {
21455 return;
21456 };
21457 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21458 gutter_highlights.retain(|highlight| {
21459 while let Some(range_to_remove) = ranges_to_remove.peek() {
21460 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21461 Ordering::Less | Ordering::Equal => {
21462 ranges_to_remove.next();
21463 }
21464 Ordering::Greater => {
21465 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21466 Ordering::Less | Ordering::Equal => {
21467 return false;
21468 }
21469 Ordering::Greater => break,
21470 }
21471 }
21472 }
21473 }
21474
21475 true
21476 });
21477 self.gutter_highlights
21478 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21479 }
21480
21481 #[cfg(feature = "test-support")]
21482 pub fn all_text_highlights(
21483 &self,
21484 window: &mut Window,
21485 cx: &mut Context<Self>,
21486 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21487 let snapshot = self.snapshot(window, cx);
21488 self.display_map.update(cx, |display_map, _| {
21489 display_map
21490 .all_text_highlights()
21491 .map(|highlight| {
21492 let (style, ranges) = highlight.as_ref();
21493 (
21494 *style,
21495 ranges
21496 .iter()
21497 .map(|range| range.clone().to_display_points(&snapshot))
21498 .collect(),
21499 )
21500 })
21501 .collect()
21502 })
21503 }
21504
21505 #[cfg(feature = "test-support")]
21506 pub fn all_text_background_highlights(
21507 &self,
21508 window: &mut Window,
21509 cx: &mut Context<Self>,
21510 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21511 let snapshot = self.snapshot(window, cx);
21512 let buffer = &snapshot.buffer_snapshot();
21513 let start = buffer.anchor_before(MultiBufferOffset(0));
21514 let end = buffer.anchor_after(buffer.len());
21515 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21516 }
21517
21518 #[cfg(any(test, feature = "test-support"))]
21519 pub fn sorted_background_highlights_in_range(
21520 &self,
21521 search_range: Range<Anchor>,
21522 display_snapshot: &DisplaySnapshot,
21523 theme: &Theme,
21524 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21525 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21526 res.sort_by(|a, b| {
21527 a.0.start
21528 .cmp(&b.0.start)
21529 .then_with(|| a.0.end.cmp(&b.0.end))
21530 .then_with(|| a.1.cmp(&b.1))
21531 });
21532 res
21533 }
21534
21535 #[cfg(feature = "test-support")]
21536 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21537 let snapshot = self.buffer().read(cx).snapshot(cx);
21538
21539 let highlights = self
21540 .background_highlights
21541 .get(&HighlightKey::Type(TypeId::of::<
21542 items::BufferSearchHighlights,
21543 >()));
21544
21545 if let Some((_color, ranges)) = highlights {
21546 ranges
21547 .iter()
21548 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21549 .collect_vec()
21550 } else {
21551 vec![]
21552 }
21553 }
21554
21555 fn document_highlights_for_position<'a>(
21556 &'a self,
21557 position: Anchor,
21558 buffer: &'a MultiBufferSnapshot,
21559 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21560 let read_highlights = self
21561 .background_highlights
21562 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21563 .map(|h| &h.1);
21564 let write_highlights = self
21565 .background_highlights
21566 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21567 .map(|h| &h.1);
21568 let left_position = position.bias_left(buffer);
21569 let right_position = position.bias_right(buffer);
21570 read_highlights
21571 .into_iter()
21572 .chain(write_highlights)
21573 .flat_map(move |ranges| {
21574 let start_ix = match ranges.binary_search_by(|probe| {
21575 let cmp = probe.end.cmp(&left_position, buffer);
21576 if cmp.is_ge() {
21577 Ordering::Greater
21578 } else {
21579 Ordering::Less
21580 }
21581 }) {
21582 Ok(i) | Err(i) => i,
21583 };
21584
21585 ranges[start_ix..]
21586 .iter()
21587 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21588 })
21589 }
21590
21591 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21592 self.background_highlights
21593 .get(&HighlightKey::Type(TypeId::of::<T>()))
21594 .is_some_and(|(_, highlights)| !highlights.is_empty())
21595 }
21596
21597 /// Returns all background highlights for a given range.
21598 ///
21599 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21600 pub fn background_highlights_in_range(
21601 &self,
21602 search_range: Range<Anchor>,
21603 display_snapshot: &DisplaySnapshot,
21604 theme: &Theme,
21605 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21606 let mut results = Vec::new();
21607 for (color_fetcher, ranges) in self.background_highlights.values() {
21608 let start_ix = match ranges.binary_search_by(|probe| {
21609 let cmp = probe
21610 .end
21611 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21612 if cmp.is_gt() {
21613 Ordering::Greater
21614 } else {
21615 Ordering::Less
21616 }
21617 }) {
21618 Ok(i) | Err(i) => i,
21619 };
21620 for (index, range) in ranges[start_ix..].iter().enumerate() {
21621 if range
21622 .start
21623 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21624 .is_ge()
21625 {
21626 break;
21627 }
21628
21629 let color = color_fetcher(&(start_ix + index), theme);
21630 let start = range.start.to_display_point(display_snapshot);
21631 let end = range.end.to_display_point(display_snapshot);
21632 results.push((start..end, color))
21633 }
21634 }
21635 results
21636 }
21637
21638 pub fn gutter_highlights_in_range(
21639 &self,
21640 search_range: Range<Anchor>,
21641 display_snapshot: &DisplaySnapshot,
21642 cx: &App,
21643 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21644 let mut results = Vec::new();
21645 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21646 let color = color_fetcher(cx);
21647 let start_ix = match ranges.binary_search_by(|probe| {
21648 let cmp = probe
21649 .end
21650 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21651 if cmp.is_gt() {
21652 Ordering::Greater
21653 } else {
21654 Ordering::Less
21655 }
21656 }) {
21657 Ok(i) | Err(i) => i,
21658 };
21659 for range in &ranges[start_ix..] {
21660 if range
21661 .start
21662 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21663 .is_ge()
21664 {
21665 break;
21666 }
21667
21668 let start = range.start.to_display_point(display_snapshot);
21669 let end = range.end.to_display_point(display_snapshot);
21670 results.push((start..end, color))
21671 }
21672 }
21673 results
21674 }
21675
21676 /// Get the text ranges corresponding to the redaction query
21677 pub fn redacted_ranges(
21678 &self,
21679 search_range: Range<Anchor>,
21680 display_snapshot: &DisplaySnapshot,
21681 cx: &App,
21682 ) -> Vec<Range<DisplayPoint>> {
21683 display_snapshot
21684 .buffer_snapshot()
21685 .redacted_ranges(search_range, |file| {
21686 if let Some(file) = file {
21687 file.is_private()
21688 && EditorSettings::get(
21689 Some(SettingsLocation {
21690 worktree_id: file.worktree_id(cx),
21691 path: file.path().as_ref(),
21692 }),
21693 cx,
21694 )
21695 .redact_private_values
21696 } else {
21697 false
21698 }
21699 })
21700 .map(|range| {
21701 range.start.to_display_point(display_snapshot)
21702 ..range.end.to_display_point(display_snapshot)
21703 })
21704 .collect()
21705 }
21706
21707 pub fn highlight_text_key<T: 'static>(
21708 &mut self,
21709 key: usize,
21710 ranges: Vec<Range<Anchor>>,
21711 style: HighlightStyle,
21712 merge: bool,
21713 cx: &mut Context<Self>,
21714 ) {
21715 self.display_map.update(cx, |map, cx| {
21716 map.highlight_text(
21717 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21718 ranges,
21719 style,
21720 merge,
21721 cx,
21722 );
21723 });
21724 cx.notify();
21725 }
21726
21727 pub fn highlight_text<T: 'static>(
21728 &mut self,
21729 ranges: Vec<Range<Anchor>>,
21730 style: HighlightStyle,
21731 cx: &mut Context<Self>,
21732 ) {
21733 self.display_map.update(cx, |map, cx| {
21734 map.highlight_text(
21735 HighlightKey::Type(TypeId::of::<T>()),
21736 ranges,
21737 style,
21738 false,
21739 cx,
21740 )
21741 });
21742 cx.notify();
21743 }
21744
21745 pub fn text_highlights<'a, T: 'static>(
21746 &'a self,
21747 cx: &'a App,
21748 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21749 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21750 }
21751
21752 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21753 let cleared = self
21754 .display_map
21755 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21756 if cleared {
21757 cx.notify();
21758 }
21759 }
21760
21761 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21762 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21763 && self.focus_handle.is_focused(window)
21764 }
21765
21766 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21767 self.show_cursor_when_unfocused = is_enabled;
21768 cx.notify();
21769 }
21770
21771 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21772 cx.notify();
21773 }
21774
21775 fn on_debug_session_event(
21776 &mut self,
21777 _session: Entity<Session>,
21778 event: &SessionEvent,
21779 cx: &mut Context<Self>,
21780 ) {
21781 if let SessionEvent::InvalidateInlineValue = event {
21782 self.refresh_inline_values(cx);
21783 }
21784 }
21785
21786 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21787 let Some(project) = self.project.clone() else {
21788 return;
21789 };
21790
21791 if !self.inline_value_cache.enabled {
21792 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21793 self.splice_inlays(&inlays, Vec::new(), cx);
21794 return;
21795 }
21796
21797 let current_execution_position = self
21798 .highlighted_rows
21799 .get(&TypeId::of::<ActiveDebugLine>())
21800 .and_then(|lines| lines.last().map(|line| line.range.end));
21801
21802 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21803 let inline_values = editor
21804 .update(cx, |editor, cx| {
21805 let Some(current_execution_position) = current_execution_position else {
21806 return Some(Task::ready(Ok(Vec::new())));
21807 };
21808
21809 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21810 let snapshot = buffer.snapshot(cx);
21811
21812 let excerpt = snapshot.excerpt_containing(
21813 current_execution_position..current_execution_position,
21814 )?;
21815
21816 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21817 })?;
21818
21819 let range =
21820 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21821
21822 project.inline_values(buffer, range, cx)
21823 })
21824 .ok()
21825 .flatten()?
21826 .await
21827 .context("refreshing debugger inlays")
21828 .log_err()?;
21829
21830 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21831
21832 for (buffer_id, inline_value) in inline_values
21833 .into_iter()
21834 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21835 {
21836 buffer_inline_values
21837 .entry(buffer_id)
21838 .or_default()
21839 .push(inline_value);
21840 }
21841
21842 editor
21843 .update(cx, |editor, cx| {
21844 let snapshot = editor.buffer.read(cx).snapshot(cx);
21845 let mut new_inlays = Vec::default();
21846
21847 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21848 let buffer_id = buffer_snapshot.remote_id();
21849 buffer_inline_values
21850 .get(&buffer_id)
21851 .into_iter()
21852 .flatten()
21853 .for_each(|hint| {
21854 let inlay = Inlay::debugger(
21855 post_inc(&mut editor.next_inlay_id),
21856 Anchor::in_buffer(excerpt_id, hint.position),
21857 hint.text(),
21858 );
21859 if !inlay.text().chars().contains(&'\n') {
21860 new_inlays.push(inlay);
21861 }
21862 });
21863 }
21864
21865 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21866 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21867
21868 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21869 })
21870 .ok()?;
21871 Some(())
21872 });
21873 }
21874
21875 fn on_buffer_event(
21876 &mut self,
21877 multibuffer: &Entity<MultiBuffer>,
21878 event: &multi_buffer::Event,
21879 window: &mut Window,
21880 cx: &mut Context<Self>,
21881 ) {
21882 match event {
21883 multi_buffer::Event::Edited { edited_buffer } => {
21884 self.scrollbar_marker_state.dirty = true;
21885 self.active_indent_guides_state.dirty = true;
21886 self.refresh_active_diagnostics(cx);
21887 self.refresh_code_actions(window, cx);
21888 self.refresh_single_line_folds(window, cx);
21889 self.refresh_matching_bracket_highlights(window, cx);
21890 if self.has_active_edit_prediction() {
21891 self.update_visible_edit_prediction(window, cx);
21892 }
21893
21894 if let Some(buffer) = edited_buffer {
21895 if buffer.read(cx).file().is_none() {
21896 cx.emit(EditorEvent::TitleChanged);
21897 }
21898
21899 if self.project.is_some() {
21900 let buffer_id = buffer.read(cx).remote_id();
21901 self.register_buffer(buffer_id, cx);
21902 self.update_lsp_data(Some(buffer_id), window, cx);
21903 self.refresh_inlay_hints(
21904 InlayHintRefreshReason::BufferEdited(buffer_id),
21905 cx,
21906 );
21907 }
21908 }
21909
21910 cx.emit(EditorEvent::BufferEdited);
21911 cx.emit(SearchEvent::MatchesInvalidated);
21912
21913 let Some(project) = &self.project else { return };
21914 let (telemetry, is_via_ssh) = {
21915 let project = project.read(cx);
21916 let telemetry = project.client().telemetry().clone();
21917 let is_via_ssh = project.is_via_remote_server();
21918 (telemetry, is_via_ssh)
21919 };
21920 telemetry.log_edit_event("editor", is_via_ssh);
21921 }
21922 multi_buffer::Event::ExcerptsAdded {
21923 buffer,
21924 predecessor,
21925 excerpts,
21926 } => {
21927 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21928 let buffer_id = buffer.read(cx).remote_id();
21929 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21930 && let Some(project) = &self.project
21931 {
21932 update_uncommitted_diff_for_buffer(
21933 cx.entity(),
21934 project,
21935 [buffer.clone()],
21936 self.buffer.clone(),
21937 cx,
21938 )
21939 .detach();
21940 }
21941 self.update_lsp_data(Some(buffer_id), window, cx);
21942 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21943 self.colorize_brackets(false, cx);
21944 cx.emit(EditorEvent::ExcerptsAdded {
21945 buffer: buffer.clone(),
21946 predecessor: *predecessor,
21947 excerpts: excerpts.clone(),
21948 });
21949 }
21950 multi_buffer::Event::ExcerptsRemoved {
21951 ids,
21952 removed_buffer_ids,
21953 } => {
21954 if let Some(inlay_hints) = &mut self.inlay_hints {
21955 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21956 }
21957 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21958 for buffer_id in removed_buffer_ids {
21959 self.registered_buffers.remove(buffer_id);
21960 }
21961 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21962 cx.emit(EditorEvent::ExcerptsRemoved {
21963 ids: ids.clone(),
21964 removed_buffer_ids: removed_buffer_ids.clone(),
21965 });
21966 }
21967 multi_buffer::Event::ExcerptsEdited {
21968 excerpt_ids,
21969 buffer_ids,
21970 } => {
21971 self.display_map.update(cx, |map, cx| {
21972 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21973 });
21974 cx.emit(EditorEvent::ExcerptsEdited {
21975 ids: excerpt_ids.clone(),
21976 });
21977 }
21978 multi_buffer::Event::ExcerptsExpanded { ids } => {
21979 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21980 self.refresh_document_highlights(cx);
21981 for id in ids {
21982 self.fetched_tree_sitter_chunks.remove(id);
21983 }
21984 self.colorize_brackets(false, cx);
21985 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21986 }
21987 multi_buffer::Event::Reparsed(buffer_id) => {
21988 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21989 self.refresh_selected_text_highlights(true, window, cx);
21990 self.colorize_brackets(true, cx);
21991 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21992
21993 cx.emit(EditorEvent::Reparsed(*buffer_id));
21994 }
21995 multi_buffer::Event::DiffHunksToggled => {
21996 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21997 }
21998 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
21999 if !is_fresh_language {
22000 self.registered_buffers.remove(&buffer_id);
22001 }
22002 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22003 cx.emit(EditorEvent::Reparsed(*buffer_id));
22004 cx.notify();
22005 }
22006 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22007 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22008 multi_buffer::Event::FileHandleChanged
22009 | multi_buffer::Event::Reloaded
22010 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22011 multi_buffer::Event::DiagnosticsUpdated => {
22012 self.update_diagnostics_state(window, cx);
22013 }
22014 _ => {}
22015 };
22016 }
22017
22018 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22019 if !self.diagnostics_enabled() {
22020 return;
22021 }
22022 self.refresh_active_diagnostics(cx);
22023 self.refresh_inline_diagnostics(true, window, cx);
22024 self.scrollbar_marker_state.dirty = true;
22025 cx.notify();
22026 }
22027
22028 pub fn start_temporary_diff_override(&mut self) {
22029 self.load_diff_task.take();
22030 self.temporary_diff_override = true;
22031 }
22032
22033 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22034 self.temporary_diff_override = false;
22035 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22036 self.buffer.update(cx, |buffer, cx| {
22037 buffer.set_all_diff_hunks_collapsed(cx);
22038 });
22039
22040 if let Some(project) = self.project.clone() {
22041 self.load_diff_task = Some(
22042 update_uncommitted_diff_for_buffer(
22043 cx.entity(),
22044 &project,
22045 self.buffer.read(cx).all_buffers(),
22046 self.buffer.clone(),
22047 cx,
22048 )
22049 .shared(),
22050 );
22051 }
22052 }
22053
22054 fn on_display_map_changed(
22055 &mut self,
22056 _: Entity<DisplayMap>,
22057 _: &mut Window,
22058 cx: &mut Context<Self>,
22059 ) {
22060 cx.notify();
22061 }
22062
22063 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22064 if !self.mode.is_full() {
22065 return None;
22066 }
22067
22068 let theme_settings = theme::ThemeSettings::get_global(cx);
22069 let theme = cx.theme();
22070 let accent_colors = theme.accents().clone();
22071
22072 let accent_overrides = theme_settings
22073 .theme_overrides
22074 .get(theme.name.as_ref())
22075 .map(|theme_style| &theme_style.accents)
22076 .into_iter()
22077 .flatten()
22078 .chain(
22079 theme_settings
22080 .experimental_theme_overrides
22081 .as_ref()
22082 .map(|overrides| &overrides.accents)
22083 .into_iter()
22084 .flatten(),
22085 )
22086 .flat_map(|accent| accent.0.clone())
22087 .collect();
22088
22089 Some(AccentData {
22090 colors: accent_colors,
22091 overrides: accent_overrides,
22092 })
22093 }
22094
22095 fn fetch_applicable_language_settings(
22096 &self,
22097 cx: &App,
22098 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22099 if !self.mode.is_full() {
22100 return HashMap::default();
22101 }
22102
22103 self.buffer().read(cx).all_buffers().into_iter().fold(
22104 HashMap::default(),
22105 |mut acc, buffer| {
22106 let buffer = buffer.read(cx);
22107 let language = buffer.language().map(|language| language.name());
22108 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22109 let file = buffer.file();
22110 v.insert(language_settings(language, file, cx).into_owned());
22111 }
22112 acc
22113 },
22114 )
22115 }
22116
22117 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22118 let new_language_settings = self.fetch_applicable_language_settings(cx);
22119 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22120 self.applicable_language_settings = new_language_settings;
22121
22122 let new_accents = self.fetch_accent_data(cx);
22123 let accents_changed = new_accents != self.accent_data;
22124 self.accent_data = new_accents;
22125
22126 if self.diagnostics_enabled() {
22127 let new_severity = EditorSettings::get_global(cx)
22128 .diagnostics_max_severity
22129 .unwrap_or(DiagnosticSeverity::Hint);
22130 self.set_max_diagnostics_severity(new_severity, cx);
22131 }
22132 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22133 self.update_edit_prediction_settings(cx);
22134 self.refresh_edit_prediction(true, false, window, cx);
22135 self.refresh_inline_values(cx);
22136 self.refresh_inlay_hints(
22137 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22138 self.selections.newest_anchor().head(),
22139 &self.buffer.read(cx).snapshot(cx),
22140 cx,
22141 )),
22142 cx,
22143 );
22144
22145 let old_cursor_shape = self.cursor_shape;
22146 let old_show_breadcrumbs = self.show_breadcrumbs;
22147
22148 {
22149 let editor_settings = EditorSettings::get_global(cx);
22150 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22151 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22152 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22153 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22154 }
22155
22156 if old_cursor_shape != self.cursor_shape {
22157 cx.emit(EditorEvent::CursorShapeChanged);
22158 }
22159
22160 if old_show_breadcrumbs != self.show_breadcrumbs {
22161 cx.emit(EditorEvent::BreadcrumbsChanged);
22162 }
22163
22164 let project_settings = ProjectSettings::get_global(cx);
22165 self.buffer_serialization = self
22166 .should_serialize_buffer()
22167 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22168
22169 if self.mode.is_full() {
22170 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22171 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22172 if self.show_inline_diagnostics != show_inline_diagnostics {
22173 self.show_inline_diagnostics = show_inline_diagnostics;
22174 self.refresh_inline_diagnostics(false, window, cx);
22175 }
22176
22177 if self.git_blame_inline_enabled != inline_blame_enabled {
22178 self.toggle_git_blame_inline_internal(false, window, cx);
22179 }
22180
22181 let minimap_settings = EditorSettings::get_global(cx).minimap;
22182 if self.minimap_visibility != MinimapVisibility::Disabled {
22183 if self.minimap_visibility.settings_visibility()
22184 != minimap_settings.minimap_enabled()
22185 {
22186 self.set_minimap_visibility(
22187 MinimapVisibility::for_mode(self.mode(), cx),
22188 window,
22189 cx,
22190 );
22191 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22192 minimap_entity.update(cx, |minimap_editor, cx| {
22193 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22194 })
22195 }
22196 }
22197
22198 if language_settings_changed || accents_changed {
22199 self.colorize_brackets(true, cx);
22200 }
22201
22202 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22203 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22204 }) {
22205 if !inlay_splice.is_empty() {
22206 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22207 }
22208 self.refresh_colors_for_visible_range(None, window, cx);
22209 }
22210 }
22211
22212 cx.notify();
22213 }
22214
22215 pub fn set_searchable(&mut self, searchable: bool) {
22216 self.searchable = searchable;
22217 }
22218
22219 pub fn searchable(&self) -> bool {
22220 self.searchable
22221 }
22222
22223 pub fn open_excerpts_in_split(
22224 &mut self,
22225 _: &OpenExcerptsSplit,
22226 window: &mut Window,
22227 cx: &mut Context<Self>,
22228 ) {
22229 self.open_excerpts_common(None, true, window, cx)
22230 }
22231
22232 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22233 self.open_excerpts_common(None, false, window, cx)
22234 }
22235
22236 fn open_excerpts_common(
22237 &mut self,
22238 jump_data: Option<JumpData>,
22239 split: bool,
22240 window: &mut Window,
22241 cx: &mut Context<Self>,
22242 ) {
22243 let Some(workspace) = self.workspace() else {
22244 cx.propagate();
22245 return;
22246 };
22247
22248 if self.buffer.read(cx).is_singleton() {
22249 cx.propagate();
22250 return;
22251 }
22252
22253 let mut new_selections_by_buffer = HashMap::default();
22254 match &jump_data {
22255 Some(JumpData::MultiBufferPoint {
22256 excerpt_id,
22257 position,
22258 anchor,
22259 line_offset_from_top,
22260 }) => {
22261 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22262 if let Some(buffer) = multi_buffer_snapshot
22263 .buffer_id_for_excerpt(*excerpt_id)
22264 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22265 {
22266 let buffer_snapshot = buffer.read(cx).snapshot();
22267 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22268 language::ToPoint::to_point(anchor, &buffer_snapshot)
22269 } else {
22270 buffer_snapshot.clip_point(*position, Bias::Left)
22271 };
22272 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22273 new_selections_by_buffer.insert(
22274 buffer,
22275 (
22276 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22277 Some(*line_offset_from_top),
22278 ),
22279 );
22280 }
22281 }
22282 Some(JumpData::MultiBufferRow {
22283 row,
22284 line_offset_from_top,
22285 }) => {
22286 let point = MultiBufferPoint::new(row.0, 0);
22287 if let Some((buffer, buffer_point, _)) =
22288 self.buffer.read(cx).point_to_buffer_point(point, cx)
22289 {
22290 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22291 new_selections_by_buffer
22292 .entry(buffer)
22293 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22294 .0
22295 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22296 }
22297 }
22298 None => {
22299 let selections = self
22300 .selections
22301 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22302 let multi_buffer = self.buffer.read(cx);
22303 for selection in selections {
22304 for (snapshot, range, _, anchor) in multi_buffer
22305 .snapshot(cx)
22306 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22307 {
22308 if let Some(anchor) = anchor {
22309 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22310 else {
22311 continue;
22312 };
22313 let offset = text::ToOffset::to_offset(
22314 &anchor.text_anchor,
22315 &buffer_handle.read(cx).snapshot(),
22316 );
22317 let range = BufferOffset(offset)..BufferOffset(offset);
22318 new_selections_by_buffer
22319 .entry(buffer_handle)
22320 .or_insert((Vec::new(), None))
22321 .0
22322 .push(range)
22323 } else {
22324 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22325 else {
22326 continue;
22327 };
22328 new_selections_by_buffer
22329 .entry(buffer_handle)
22330 .or_insert((Vec::new(), None))
22331 .0
22332 .push(range)
22333 }
22334 }
22335 }
22336 }
22337 }
22338
22339 new_selections_by_buffer
22340 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22341
22342 if new_selections_by_buffer.is_empty() {
22343 return;
22344 }
22345
22346 // We defer the pane interaction because we ourselves are a workspace item
22347 // and activating a new item causes the pane to call a method on us reentrantly,
22348 // which panics if we're on the stack.
22349 window.defer(cx, move |window, cx| {
22350 workspace.update(cx, |workspace, cx| {
22351 let pane = if split {
22352 workspace.adjacent_pane(window, cx)
22353 } else {
22354 workspace.active_pane().clone()
22355 };
22356
22357 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22358 let buffer_read = buffer.read(cx);
22359 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22360 (true, project::File::from_dyn(Some(file)).is_some())
22361 } else {
22362 (false, false)
22363 };
22364
22365 // If project file is none workspace.open_project_item will fail to open the excerpt
22366 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22367 // so we check if there's a tab match in that case first
22368 let editor = (!has_file || !is_project_file)
22369 .then(|| {
22370 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22371 // so `workspace.open_project_item` will never find them, always opening a new editor.
22372 // Instead, we try to activate the existing editor in the pane first.
22373 let (editor, pane_item_index, pane_item_id) =
22374 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22375 let editor = item.downcast::<Editor>()?;
22376 let singleton_buffer =
22377 editor.read(cx).buffer().read(cx).as_singleton()?;
22378 if singleton_buffer == buffer {
22379 Some((editor, i, item.item_id()))
22380 } else {
22381 None
22382 }
22383 })?;
22384 pane.update(cx, |pane, cx| {
22385 pane.activate_item(pane_item_index, true, true, window, cx);
22386 if !PreviewTabsSettings::get_global(cx)
22387 .enable_preview_from_multibuffer
22388 {
22389 pane.unpreview_item_if_preview(pane_item_id);
22390 }
22391 });
22392 Some(editor)
22393 })
22394 .flatten()
22395 .unwrap_or_else(|| {
22396 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22397 .enable_keep_preview_on_code_navigation;
22398 let allow_new_preview =
22399 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22400 workspace.open_project_item::<Self>(
22401 pane.clone(),
22402 buffer,
22403 true,
22404 true,
22405 keep_old_preview,
22406 allow_new_preview,
22407 window,
22408 cx,
22409 )
22410 });
22411
22412 editor.update(cx, |editor, cx| {
22413 if has_file && !is_project_file {
22414 editor.set_read_only(true);
22415 }
22416 let autoscroll = match scroll_offset {
22417 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22418 None => Autoscroll::newest(),
22419 };
22420 let nav_history = editor.nav_history.take();
22421 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22422 let Some((&excerpt_id, _, buffer_snapshot)) =
22423 multibuffer_snapshot.as_singleton()
22424 else {
22425 return;
22426 };
22427 editor.change_selections(
22428 SelectionEffects::scroll(autoscroll),
22429 window,
22430 cx,
22431 |s| {
22432 s.select_ranges(ranges.into_iter().map(|range| {
22433 let range = buffer_snapshot.anchor_before(range.start)
22434 ..buffer_snapshot.anchor_after(range.end);
22435 multibuffer_snapshot
22436 .anchor_range_in_excerpt(excerpt_id, range)
22437 .unwrap()
22438 }));
22439 },
22440 );
22441 editor.nav_history = nav_history;
22442 });
22443 }
22444 })
22445 });
22446 }
22447
22448 // Allow opening excerpts for buffers that either belong to the current project
22449 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22450 // are also supported so tests and other in-memory views keep working.
22451 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22452 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22453 }
22454
22455 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22456 let snapshot = self.buffer.read(cx).read(cx);
22457 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22458 Some(
22459 ranges
22460 .iter()
22461 .map(move |range| {
22462 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22463 })
22464 .collect(),
22465 )
22466 }
22467
22468 fn selection_replacement_ranges(
22469 &self,
22470 range: Range<MultiBufferOffsetUtf16>,
22471 cx: &mut App,
22472 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22473 let selections = self
22474 .selections
22475 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22476 let newest_selection = selections
22477 .iter()
22478 .max_by_key(|selection| selection.id)
22479 .unwrap();
22480 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22481 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22482 let snapshot = self.buffer.read(cx).read(cx);
22483 selections
22484 .into_iter()
22485 .map(|mut selection| {
22486 selection.start.0.0 =
22487 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22488 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22489 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22490 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22491 })
22492 .collect()
22493 }
22494
22495 fn report_editor_event(
22496 &self,
22497 reported_event: ReportEditorEvent,
22498 file_extension: Option<String>,
22499 cx: &App,
22500 ) {
22501 if cfg!(any(test, feature = "test-support")) {
22502 return;
22503 }
22504
22505 let Some(project) = &self.project else { return };
22506
22507 // If None, we are in a file without an extension
22508 let file = self
22509 .buffer
22510 .read(cx)
22511 .as_singleton()
22512 .and_then(|b| b.read(cx).file());
22513 let file_extension = file_extension.or(file
22514 .as_ref()
22515 .and_then(|file| Path::new(file.file_name(cx)).extension())
22516 .and_then(|e| e.to_str())
22517 .map(|a| a.to_string()));
22518
22519 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22520 .map(|vim_mode| vim_mode.0)
22521 .unwrap_or(false);
22522
22523 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22524 let copilot_enabled = edit_predictions_provider
22525 == language::language_settings::EditPredictionProvider::Copilot;
22526 let copilot_enabled_for_language = self
22527 .buffer
22528 .read(cx)
22529 .language_settings(cx)
22530 .show_edit_predictions;
22531
22532 let project = project.read(cx);
22533 let event_type = reported_event.event_type();
22534
22535 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22536 telemetry::event!(
22537 event_type,
22538 type = if auto_saved {"autosave"} else {"manual"},
22539 file_extension,
22540 vim_mode,
22541 copilot_enabled,
22542 copilot_enabled_for_language,
22543 edit_predictions_provider,
22544 is_via_ssh = project.is_via_remote_server(),
22545 );
22546 } else {
22547 telemetry::event!(
22548 event_type,
22549 file_extension,
22550 vim_mode,
22551 copilot_enabled,
22552 copilot_enabled_for_language,
22553 edit_predictions_provider,
22554 is_via_ssh = project.is_via_remote_server(),
22555 );
22556 };
22557 }
22558
22559 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22560 /// with each line being an array of {text, highlight} objects.
22561 fn copy_highlight_json(
22562 &mut self,
22563 _: &CopyHighlightJson,
22564 window: &mut Window,
22565 cx: &mut Context<Self>,
22566 ) {
22567 #[derive(Serialize)]
22568 struct Chunk<'a> {
22569 text: String,
22570 highlight: Option<&'a str>,
22571 }
22572
22573 let snapshot = self.buffer.read(cx).snapshot(cx);
22574 let range = self
22575 .selected_text_range(false, window, cx)
22576 .and_then(|selection| {
22577 if selection.range.is_empty() {
22578 None
22579 } else {
22580 Some(
22581 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22582 selection.range.start,
22583 )))
22584 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22585 selection.range.end,
22586 ))),
22587 )
22588 }
22589 })
22590 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22591
22592 let chunks = snapshot.chunks(range, true);
22593 let mut lines = Vec::new();
22594 let mut line: VecDeque<Chunk> = VecDeque::new();
22595
22596 let Some(style) = self.style.as_ref() else {
22597 return;
22598 };
22599
22600 for chunk in chunks {
22601 let highlight = chunk
22602 .syntax_highlight_id
22603 .and_then(|id| id.name(&style.syntax));
22604 let mut chunk_lines = chunk.text.split('\n').peekable();
22605 while let Some(text) = chunk_lines.next() {
22606 let mut merged_with_last_token = false;
22607 if let Some(last_token) = line.back_mut()
22608 && last_token.highlight == highlight
22609 {
22610 last_token.text.push_str(text);
22611 merged_with_last_token = true;
22612 }
22613
22614 if !merged_with_last_token {
22615 line.push_back(Chunk {
22616 text: text.into(),
22617 highlight,
22618 });
22619 }
22620
22621 if chunk_lines.peek().is_some() {
22622 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22623 line.pop_front();
22624 }
22625 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22626 line.pop_back();
22627 }
22628
22629 lines.push(mem::take(&mut line));
22630 }
22631 }
22632 }
22633
22634 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22635 return;
22636 };
22637 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22638 }
22639
22640 pub fn open_context_menu(
22641 &mut self,
22642 _: &OpenContextMenu,
22643 window: &mut Window,
22644 cx: &mut Context<Self>,
22645 ) {
22646 self.request_autoscroll(Autoscroll::newest(), cx);
22647 let position = self
22648 .selections
22649 .newest_display(&self.display_snapshot(cx))
22650 .start;
22651 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22652 }
22653
22654 pub fn replay_insert_event(
22655 &mut self,
22656 text: &str,
22657 relative_utf16_range: Option<Range<isize>>,
22658 window: &mut Window,
22659 cx: &mut Context<Self>,
22660 ) {
22661 if !self.input_enabled {
22662 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22663 return;
22664 }
22665 if let Some(relative_utf16_range) = relative_utf16_range {
22666 let selections = self
22667 .selections
22668 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22669 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22670 let new_ranges = selections.into_iter().map(|range| {
22671 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22672 range
22673 .head()
22674 .0
22675 .0
22676 .saturating_add_signed(relative_utf16_range.start),
22677 ));
22678 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22679 range
22680 .head()
22681 .0
22682 .0
22683 .saturating_add_signed(relative_utf16_range.end),
22684 ));
22685 start..end
22686 });
22687 s.select_ranges(new_ranges);
22688 });
22689 }
22690
22691 self.handle_input(text, window, cx);
22692 }
22693
22694 pub fn is_focused(&self, window: &Window) -> bool {
22695 self.focus_handle.is_focused(window)
22696 }
22697
22698 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22699 cx.emit(EditorEvent::Focused);
22700
22701 if let Some(descendant) = self
22702 .last_focused_descendant
22703 .take()
22704 .and_then(|descendant| descendant.upgrade())
22705 {
22706 window.focus(&descendant, cx);
22707 } else {
22708 if let Some(blame) = self.blame.as_ref() {
22709 blame.update(cx, GitBlame::focus)
22710 }
22711
22712 self.blink_manager.update(cx, BlinkManager::enable);
22713 self.show_cursor_names(window, cx);
22714 self.buffer.update(cx, |buffer, cx| {
22715 buffer.finalize_last_transaction(cx);
22716 if self.leader_id.is_none() {
22717 buffer.set_active_selections(
22718 &self.selections.disjoint_anchors_arc(),
22719 self.selections.line_mode(),
22720 self.cursor_shape,
22721 cx,
22722 );
22723 }
22724 });
22725
22726 if let Some(position_map) = self.last_position_map.clone() {
22727 EditorElement::mouse_moved(
22728 self,
22729 &MouseMoveEvent {
22730 position: window.mouse_position(),
22731 pressed_button: None,
22732 modifiers: window.modifiers(),
22733 },
22734 &position_map,
22735 window,
22736 cx,
22737 );
22738 }
22739 }
22740 }
22741
22742 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22743 cx.emit(EditorEvent::FocusedIn)
22744 }
22745
22746 fn handle_focus_out(
22747 &mut self,
22748 event: FocusOutEvent,
22749 _window: &mut Window,
22750 cx: &mut Context<Self>,
22751 ) {
22752 if event.blurred != self.focus_handle {
22753 self.last_focused_descendant = Some(event.blurred);
22754 }
22755 self.selection_drag_state = SelectionDragState::None;
22756 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22757 }
22758
22759 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22760 self.blink_manager.update(cx, BlinkManager::disable);
22761 self.buffer
22762 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22763
22764 if let Some(blame) = self.blame.as_ref() {
22765 blame.update(cx, GitBlame::blur)
22766 }
22767 if !self.hover_state.focused(window, cx) {
22768 hide_hover(self, cx);
22769 }
22770 if !self
22771 .context_menu
22772 .borrow()
22773 .as_ref()
22774 .is_some_and(|context_menu| context_menu.focused(window, cx))
22775 {
22776 self.hide_context_menu(window, cx);
22777 }
22778 self.take_active_edit_prediction(cx);
22779 cx.emit(EditorEvent::Blurred);
22780 cx.notify();
22781 }
22782
22783 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22784 let mut pending: String = window
22785 .pending_input_keystrokes()
22786 .into_iter()
22787 .flatten()
22788 .filter_map(|keystroke| keystroke.key_char.clone())
22789 .collect();
22790
22791 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22792 pending = "".to_string();
22793 }
22794
22795 let existing_pending = self
22796 .text_highlights::<PendingInput>(cx)
22797 .map(|(_, ranges)| ranges.to_vec());
22798 if existing_pending.is_none() && pending.is_empty() {
22799 return;
22800 }
22801 let transaction =
22802 self.transact(window, cx, |this, window, cx| {
22803 let selections = this
22804 .selections
22805 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22806 let edits = selections
22807 .iter()
22808 .map(|selection| (selection.end..selection.end, pending.clone()));
22809 this.edit(edits, cx);
22810 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22811 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22812 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22813 }));
22814 });
22815 if let Some(existing_ranges) = existing_pending {
22816 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22817 this.edit(edits, cx);
22818 }
22819 });
22820
22821 let snapshot = self.snapshot(window, cx);
22822 let ranges = self
22823 .selections
22824 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22825 .into_iter()
22826 .map(|selection| {
22827 snapshot.buffer_snapshot().anchor_after(selection.end)
22828 ..snapshot
22829 .buffer_snapshot()
22830 .anchor_before(selection.end + pending.len())
22831 })
22832 .collect();
22833
22834 if pending.is_empty() {
22835 self.clear_highlights::<PendingInput>(cx);
22836 } else {
22837 self.highlight_text::<PendingInput>(
22838 ranges,
22839 HighlightStyle {
22840 underline: Some(UnderlineStyle {
22841 thickness: px(1.),
22842 color: None,
22843 wavy: false,
22844 }),
22845 ..Default::default()
22846 },
22847 cx,
22848 );
22849 }
22850
22851 self.ime_transaction = self.ime_transaction.or(transaction);
22852 if let Some(transaction) = self.ime_transaction {
22853 self.buffer.update(cx, |buffer, cx| {
22854 buffer.group_until_transaction(transaction, cx);
22855 });
22856 }
22857
22858 if self.text_highlights::<PendingInput>(cx).is_none() {
22859 self.ime_transaction.take();
22860 }
22861 }
22862
22863 pub fn register_action_renderer(
22864 &mut self,
22865 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22866 ) -> Subscription {
22867 let id = self.next_editor_action_id.post_inc();
22868 self.editor_actions
22869 .borrow_mut()
22870 .insert(id, Box::new(listener));
22871
22872 let editor_actions = self.editor_actions.clone();
22873 Subscription::new(move || {
22874 editor_actions.borrow_mut().remove(&id);
22875 })
22876 }
22877
22878 pub fn register_action<A: Action>(
22879 &mut self,
22880 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22881 ) -> Subscription {
22882 let id = self.next_editor_action_id.post_inc();
22883 let listener = Arc::new(listener);
22884 self.editor_actions.borrow_mut().insert(
22885 id,
22886 Box::new(move |_, window, _| {
22887 let listener = listener.clone();
22888 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22889 let action = action.downcast_ref().unwrap();
22890 if phase == DispatchPhase::Bubble {
22891 listener(action, window, cx)
22892 }
22893 })
22894 }),
22895 );
22896
22897 let editor_actions = self.editor_actions.clone();
22898 Subscription::new(move || {
22899 editor_actions.borrow_mut().remove(&id);
22900 })
22901 }
22902
22903 pub fn file_header_size(&self) -> u32 {
22904 FILE_HEADER_HEIGHT
22905 }
22906
22907 pub fn restore(
22908 &mut self,
22909 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22910 window: &mut Window,
22911 cx: &mut Context<Self>,
22912 ) {
22913 self.buffer().update(cx, |multi_buffer, cx| {
22914 for (buffer_id, changes) in revert_changes {
22915 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22916 buffer.update(cx, |buffer, cx| {
22917 buffer.edit(
22918 changes
22919 .into_iter()
22920 .map(|(range, text)| (range, text.to_string())),
22921 None,
22922 cx,
22923 );
22924 });
22925 }
22926 }
22927 });
22928 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22929 selections.refresh()
22930 });
22931 }
22932
22933 pub fn to_pixel_point(
22934 &mut self,
22935 source: multi_buffer::Anchor,
22936 editor_snapshot: &EditorSnapshot,
22937 window: &mut Window,
22938 cx: &App,
22939 ) -> Option<gpui::Point<Pixels>> {
22940 let source_point = source.to_display_point(editor_snapshot);
22941 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
22942 }
22943
22944 pub fn display_to_pixel_point(
22945 &mut self,
22946 source: DisplayPoint,
22947 editor_snapshot: &EditorSnapshot,
22948 window: &mut Window,
22949 cx: &App,
22950 ) -> Option<gpui::Point<Pixels>> {
22951 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
22952 let text_layout_details = self.text_layout_details(window);
22953 let scroll_top = text_layout_details
22954 .scroll_anchor
22955 .scroll_position(editor_snapshot)
22956 .y;
22957
22958 if source.row().as_f64() < scroll_top.floor() {
22959 return None;
22960 }
22961 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22962 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22963 Some(gpui::Point::new(source_x, source_y))
22964 }
22965
22966 pub fn has_visible_completions_menu(&self) -> bool {
22967 !self.edit_prediction_preview_is_active()
22968 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22969 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22970 })
22971 }
22972
22973 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22974 if self.mode.is_minimap() {
22975 return;
22976 }
22977 self.addons
22978 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22979 }
22980
22981 pub fn unregister_addon<T: Addon>(&mut self) {
22982 self.addons.remove(&std::any::TypeId::of::<T>());
22983 }
22984
22985 pub fn addon<T: Addon>(&self) -> Option<&T> {
22986 let type_id = std::any::TypeId::of::<T>();
22987 self.addons
22988 .get(&type_id)
22989 .and_then(|item| item.to_any().downcast_ref::<T>())
22990 }
22991
22992 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22993 let type_id = std::any::TypeId::of::<T>();
22994 self.addons
22995 .get_mut(&type_id)
22996 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22997 }
22998
22999 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23000 let text_layout_details = self.text_layout_details(window);
23001 let style = &text_layout_details.editor_style;
23002 let font_id = window.text_system().resolve_font(&style.text.font());
23003 let font_size = style.text.font_size.to_pixels(window.rem_size());
23004 let line_height = style.text.line_height_in_pixels(window.rem_size());
23005 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23006 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23007
23008 CharacterDimensions {
23009 em_width,
23010 em_advance,
23011 line_height,
23012 }
23013 }
23014
23015 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23016 self.load_diff_task.clone()
23017 }
23018
23019 fn read_metadata_from_db(
23020 &mut self,
23021 item_id: u64,
23022 workspace_id: WorkspaceId,
23023 window: &mut Window,
23024 cx: &mut Context<Editor>,
23025 ) {
23026 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23027 && !self.mode.is_minimap()
23028 && WorkspaceSettings::get(None, cx).restore_on_startup
23029 != RestoreOnStartupBehavior::EmptyTab
23030 {
23031 let buffer_snapshot = OnceCell::new();
23032
23033 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23034 && !folds.is_empty()
23035 {
23036 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23037 self.fold_ranges(
23038 folds
23039 .into_iter()
23040 .map(|(start, end)| {
23041 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23042 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23043 })
23044 .collect(),
23045 false,
23046 window,
23047 cx,
23048 );
23049 }
23050
23051 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23052 && !selections.is_empty()
23053 {
23054 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23055 // skip adding the initial selection to selection history
23056 self.selection_history.mode = SelectionHistoryMode::Skipping;
23057 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23058 s.select_ranges(selections.into_iter().map(|(start, end)| {
23059 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23060 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23061 }));
23062 });
23063 self.selection_history.mode = SelectionHistoryMode::Normal;
23064 };
23065 }
23066
23067 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23068 }
23069
23070 fn update_lsp_data(
23071 &mut self,
23072 for_buffer: Option<BufferId>,
23073 window: &mut Window,
23074 cx: &mut Context<'_, Self>,
23075 ) {
23076 self.pull_diagnostics(for_buffer, window, cx);
23077 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23078 }
23079
23080 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23081 if self.ignore_lsp_data() {
23082 return;
23083 }
23084 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23085 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23086 }
23087 }
23088
23089 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23090 if self.ignore_lsp_data() {
23091 return;
23092 }
23093
23094 if !self.registered_buffers.contains_key(&buffer_id)
23095 && let Some(project) = self.project.as_ref()
23096 {
23097 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23098 project.update(cx, |project, cx| {
23099 self.registered_buffers.insert(
23100 buffer_id,
23101 project.register_buffer_with_language_servers(&buffer, cx),
23102 );
23103 });
23104 } else {
23105 self.registered_buffers.remove(&buffer_id);
23106 }
23107 }
23108 }
23109
23110 fn ignore_lsp_data(&self) -> bool {
23111 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23112 // skip any LSP updates for it.
23113 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23114 }
23115
23116 fn create_style(&self, cx: &App) -> EditorStyle {
23117 let settings = ThemeSettings::get_global(cx);
23118
23119 let mut text_style = match self.mode {
23120 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23121 color: cx.theme().colors().editor_foreground,
23122 font_family: settings.ui_font.family.clone(),
23123 font_features: settings.ui_font.features.clone(),
23124 font_fallbacks: settings.ui_font.fallbacks.clone(),
23125 font_size: rems(0.875).into(),
23126 font_weight: settings.ui_font.weight,
23127 line_height: relative(settings.buffer_line_height.value()),
23128 ..Default::default()
23129 },
23130 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23131 color: cx.theme().colors().editor_foreground,
23132 font_family: settings.buffer_font.family.clone(),
23133 font_features: settings.buffer_font.features.clone(),
23134 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23135 font_size: settings.buffer_font_size(cx).into(),
23136 font_weight: settings.buffer_font.weight,
23137 line_height: relative(settings.buffer_line_height.value()),
23138 ..Default::default()
23139 },
23140 };
23141 if let Some(text_style_refinement) = &self.text_style_refinement {
23142 text_style.refine(text_style_refinement)
23143 }
23144
23145 let background = match self.mode {
23146 EditorMode::SingleLine => cx.theme().system().transparent,
23147 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23148 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23149 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23150 };
23151
23152 EditorStyle {
23153 background,
23154 border: cx.theme().colors().border,
23155 local_player: cx.theme().players().local(),
23156 text: text_style,
23157 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23158 syntax: cx.theme().syntax().clone(),
23159 status: cx.theme().status().clone(),
23160 inlay_hints_style: make_inlay_hints_style(cx),
23161 edit_prediction_styles: make_suggestion_styles(cx),
23162 unnecessary_code_fade: settings.unnecessary_code_fade,
23163 show_underlines: self.diagnostics_enabled(),
23164 }
23165 }
23166}
23167
23168fn edit_for_markdown_paste<'a>(
23169 buffer: &MultiBufferSnapshot,
23170 range: Range<MultiBufferOffset>,
23171 to_insert: &'a str,
23172 url: Option<url::Url>,
23173) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23174 if url.is_none() {
23175 return (range, Cow::Borrowed(to_insert));
23176 };
23177
23178 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23179
23180 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23181 Cow::Borrowed(to_insert)
23182 } else {
23183 Cow::Owned(format!("[{old_text}]({to_insert})"))
23184 };
23185 (range, new_text)
23186}
23187
23188fn process_completion_for_edit(
23189 completion: &Completion,
23190 intent: CompletionIntent,
23191 buffer: &Entity<Buffer>,
23192 cursor_position: &text::Anchor,
23193 cx: &mut Context<Editor>,
23194) -> CompletionEdit {
23195 let buffer = buffer.read(cx);
23196 let buffer_snapshot = buffer.snapshot();
23197 let (snippet, new_text) = if completion.is_snippet() {
23198 let mut snippet_source = completion.new_text.clone();
23199 // Workaround for typescript language server issues so that methods don't expand within
23200 // strings and functions with type expressions. The previous point is used because the query
23201 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23202 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23203 let previous_point = if previous_point.column > 0 {
23204 cursor_position.to_previous_offset(&buffer_snapshot)
23205 } else {
23206 cursor_position.to_offset(&buffer_snapshot)
23207 };
23208 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23209 && scope.prefers_label_for_snippet_in_completion()
23210 && let Some(label) = completion.label()
23211 && matches!(
23212 completion.kind(),
23213 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23214 )
23215 {
23216 snippet_source = label;
23217 }
23218 match Snippet::parse(&snippet_source).log_err() {
23219 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23220 None => (None, completion.new_text.clone()),
23221 }
23222 } else {
23223 (None, completion.new_text.clone())
23224 };
23225
23226 let mut range_to_replace = {
23227 let replace_range = &completion.replace_range;
23228 if let CompletionSource::Lsp {
23229 insert_range: Some(insert_range),
23230 ..
23231 } = &completion.source
23232 {
23233 debug_assert_eq!(
23234 insert_range.start, replace_range.start,
23235 "insert_range and replace_range should start at the same position"
23236 );
23237 debug_assert!(
23238 insert_range
23239 .start
23240 .cmp(cursor_position, &buffer_snapshot)
23241 .is_le(),
23242 "insert_range should start before or at cursor position"
23243 );
23244 debug_assert!(
23245 replace_range
23246 .start
23247 .cmp(cursor_position, &buffer_snapshot)
23248 .is_le(),
23249 "replace_range should start before or at cursor position"
23250 );
23251
23252 let should_replace = match intent {
23253 CompletionIntent::CompleteWithInsert => false,
23254 CompletionIntent::CompleteWithReplace => true,
23255 CompletionIntent::Complete | CompletionIntent::Compose => {
23256 let insert_mode =
23257 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23258 .completions
23259 .lsp_insert_mode;
23260 match insert_mode {
23261 LspInsertMode::Insert => false,
23262 LspInsertMode::Replace => true,
23263 LspInsertMode::ReplaceSubsequence => {
23264 let mut text_to_replace = buffer.chars_for_range(
23265 buffer.anchor_before(replace_range.start)
23266 ..buffer.anchor_after(replace_range.end),
23267 );
23268 let mut current_needle = text_to_replace.next();
23269 for haystack_ch in completion.label.text.chars() {
23270 if let Some(needle_ch) = current_needle
23271 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23272 {
23273 current_needle = text_to_replace.next();
23274 }
23275 }
23276 current_needle.is_none()
23277 }
23278 LspInsertMode::ReplaceSuffix => {
23279 if replace_range
23280 .end
23281 .cmp(cursor_position, &buffer_snapshot)
23282 .is_gt()
23283 {
23284 let range_after_cursor = *cursor_position..replace_range.end;
23285 let text_after_cursor = buffer
23286 .text_for_range(
23287 buffer.anchor_before(range_after_cursor.start)
23288 ..buffer.anchor_after(range_after_cursor.end),
23289 )
23290 .collect::<String>()
23291 .to_ascii_lowercase();
23292 completion
23293 .label
23294 .text
23295 .to_ascii_lowercase()
23296 .ends_with(&text_after_cursor)
23297 } else {
23298 true
23299 }
23300 }
23301 }
23302 }
23303 };
23304
23305 if should_replace {
23306 replace_range.clone()
23307 } else {
23308 insert_range.clone()
23309 }
23310 } else {
23311 replace_range.clone()
23312 }
23313 };
23314
23315 if range_to_replace
23316 .end
23317 .cmp(cursor_position, &buffer_snapshot)
23318 .is_lt()
23319 {
23320 range_to_replace.end = *cursor_position;
23321 }
23322
23323 let replace_range = range_to_replace.to_offset(buffer);
23324 CompletionEdit {
23325 new_text,
23326 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23327 snippet,
23328 }
23329}
23330
23331struct CompletionEdit {
23332 new_text: String,
23333 replace_range: Range<BufferOffset>,
23334 snippet: Option<Snippet>,
23335}
23336
23337fn comment_delimiter_for_newline(
23338 start_point: &Point,
23339 buffer: &MultiBufferSnapshot,
23340 language: &LanguageScope,
23341) -> Option<Arc<str>> {
23342 let delimiters = language.line_comment_prefixes();
23343 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
23344 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23345
23346 let num_of_whitespaces = snapshot
23347 .chars_for_range(range.clone())
23348 .take_while(|c| c.is_whitespace())
23349 .count();
23350 let comment_candidate = snapshot
23351 .chars_for_range(range.clone())
23352 .skip(num_of_whitespaces)
23353 .take(max_len_of_delimiter)
23354 .collect::<String>();
23355 let (delimiter, trimmed_len) = delimiters
23356 .iter()
23357 .filter_map(|delimiter| {
23358 let prefix = delimiter.trim_end();
23359 if comment_candidate.starts_with(prefix) {
23360 Some((delimiter, prefix.len()))
23361 } else {
23362 None
23363 }
23364 })
23365 .max_by_key(|(_, len)| *len)?;
23366
23367 if let Some(BlockCommentConfig {
23368 start: block_start, ..
23369 }) = language.block_comment()
23370 {
23371 let block_start_trimmed = block_start.trim_end();
23372 if block_start_trimmed.starts_with(delimiter.trim_end()) {
23373 let line_content = snapshot
23374 .chars_for_range(range)
23375 .skip(num_of_whitespaces)
23376 .take(block_start_trimmed.len())
23377 .collect::<String>();
23378
23379 if line_content.starts_with(block_start_trimmed) {
23380 return None;
23381 }
23382 }
23383 }
23384
23385 let cursor_is_placed_after_comment_marker =
23386 num_of_whitespaces + trimmed_len <= start_point.column as usize;
23387 if cursor_is_placed_after_comment_marker {
23388 Some(delimiter.clone())
23389 } else {
23390 None
23391 }
23392}
23393
23394fn documentation_delimiter_for_newline(
23395 start_point: &Point,
23396 buffer: &MultiBufferSnapshot,
23397 language: &LanguageScope,
23398 newline_formatting: &mut NewlineFormatting,
23399) -> Option<Arc<str>> {
23400 let BlockCommentConfig {
23401 start: start_tag,
23402 end: end_tag,
23403 prefix: delimiter,
23404 tab_size: len,
23405 } = language.documentation_comment()?;
23406 let is_within_block_comment = buffer
23407 .language_scope_at(*start_point)
23408 .is_some_and(|scope| scope.override_name() == Some("comment"));
23409 if !is_within_block_comment {
23410 return None;
23411 }
23412
23413 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23414
23415 let num_of_whitespaces = snapshot
23416 .chars_for_range(range.clone())
23417 .take_while(|c| c.is_whitespace())
23418 .count();
23419
23420 // 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.
23421 let column = start_point.column;
23422 let cursor_is_after_start_tag = {
23423 let start_tag_len = start_tag.len();
23424 let start_tag_line = snapshot
23425 .chars_for_range(range.clone())
23426 .skip(num_of_whitespaces)
23427 .take(start_tag_len)
23428 .collect::<String>();
23429 if start_tag_line.starts_with(start_tag.as_ref()) {
23430 num_of_whitespaces + start_tag_len <= column as usize
23431 } else {
23432 false
23433 }
23434 };
23435
23436 let cursor_is_after_delimiter = {
23437 let delimiter_trim = delimiter.trim_end();
23438 let delimiter_line = snapshot
23439 .chars_for_range(range.clone())
23440 .skip(num_of_whitespaces)
23441 .take(delimiter_trim.len())
23442 .collect::<String>();
23443 if delimiter_line.starts_with(delimiter_trim) {
23444 num_of_whitespaces + delimiter_trim.len() <= column as usize
23445 } else {
23446 false
23447 }
23448 };
23449
23450 let cursor_is_before_end_tag_if_exists = {
23451 let mut char_position = 0u32;
23452 let mut end_tag_offset = None;
23453
23454 'outer: for chunk in snapshot.text_for_range(range) {
23455 if let Some(byte_pos) = chunk.find(&**end_tag) {
23456 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
23457 end_tag_offset = Some(char_position + chars_before_match);
23458 break 'outer;
23459 }
23460 char_position += chunk.chars().count() as u32;
23461 }
23462
23463 if let Some(end_tag_offset) = end_tag_offset {
23464 let cursor_is_before_end_tag = column <= end_tag_offset;
23465 if cursor_is_after_start_tag {
23466 if cursor_is_before_end_tag {
23467 newline_formatting.insert_extra_newline = true;
23468 }
23469 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
23470 if cursor_is_at_start_of_end_tag {
23471 newline_formatting.indent_on_extra_newline.len = *len;
23472 }
23473 }
23474 cursor_is_before_end_tag
23475 } else {
23476 true
23477 }
23478 };
23479
23480 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
23481 && cursor_is_before_end_tag_if_exists
23482 {
23483 if cursor_is_after_start_tag {
23484 newline_formatting.indent_on_newline.len = *len;
23485 }
23486 Some(delimiter.clone())
23487 } else {
23488 None
23489 }
23490}
23491
23492#[derive(Debug, Default)]
23493struct NewlineFormatting {
23494 insert_extra_newline: bool,
23495 indent_on_newline: IndentSize,
23496 indent_on_extra_newline: IndentSize,
23497}
23498
23499impl NewlineFormatting {
23500 fn new(
23501 buffer: &MultiBufferSnapshot,
23502 range: Range<MultiBufferOffset>,
23503 language: &LanguageScope,
23504 ) -> Self {
23505 Self {
23506 insert_extra_newline: Self::insert_extra_newline_brackets(
23507 buffer,
23508 range.clone(),
23509 language,
23510 ) || Self::insert_extra_newline_tree_sitter(buffer, range),
23511 indent_on_newline: IndentSize::spaces(0),
23512 indent_on_extra_newline: IndentSize::spaces(0),
23513 }
23514 }
23515
23516 fn insert_extra_newline_brackets(
23517 buffer: &MultiBufferSnapshot,
23518 range: Range<MultiBufferOffset>,
23519 language: &language::LanguageScope,
23520 ) -> bool {
23521 let leading_whitespace_len = buffer
23522 .reversed_chars_at(range.start)
23523 .take_while(|c| c.is_whitespace() && *c != '\n')
23524 .map(|c| c.len_utf8())
23525 .sum::<usize>();
23526 let trailing_whitespace_len = buffer
23527 .chars_at(range.end)
23528 .take_while(|c| c.is_whitespace() && *c != '\n')
23529 .map(|c| c.len_utf8())
23530 .sum::<usize>();
23531 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23532
23533 language.brackets().any(|(pair, enabled)| {
23534 let pair_start = pair.start.trim_end();
23535 let pair_end = pair.end.trim_start();
23536
23537 enabled
23538 && pair.newline
23539 && buffer.contains_str_at(range.end, pair_end)
23540 && buffer.contains_str_at(
23541 range.start.saturating_sub_usize(pair_start.len()),
23542 pair_start,
23543 )
23544 })
23545 }
23546
23547 fn insert_extra_newline_tree_sitter(
23548 buffer: &MultiBufferSnapshot,
23549 range: Range<MultiBufferOffset>,
23550 ) -> bool {
23551 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23552 [(buffer, range, _)] => (*buffer, range.clone()),
23553 _ => return false,
23554 };
23555 let pair = {
23556 let mut result: Option<BracketMatch<usize>> = None;
23557
23558 for pair in buffer
23559 .all_bracket_ranges(range.start.0..range.end.0)
23560 .filter(move |pair| {
23561 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23562 })
23563 {
23564 let len = pair.close_range.end - pair.open_range.start;
23565
23566 if let Some(existing) = &result {
23567 let existing_len = existing.close_range.end - existing.open_range.start;
23568 if len > existing_len {
23569 continue;
23570 }
23571 }
23572
23573 result = Some(pair);
23574 }
23575
23576 result
23577 };
23578 let Some(pair) = pair else {
23579 return false;
23580 };
23581 pair.newline_only
23582 && buffer
23583 .chars_for_range(pair.open_range.end..range.start.0)
23584 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23585 .all(|c| c.is_whitespace() && c != '\n')
23586 }
23587}
23588
23589fn update_uncommitted_diff_for_buffer(
23590 editor: Entity<Editor>,
23591 project: &Entity<Project>,
23592 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23593 buffer: Entity<MultiBuffer>,
23594 cx: &mut App,
23595) -> Task<()> {
23596 let mut tasks = Vec::new();
23597 project.update(cx, |project, cx| {
23598 for buffer in buffers {
23599 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23600 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23601 }
23602 }
23603 });
23604 cx.spawn(async move |cx| {
23605 let diffs = future::join_all(tasks).await;
23606 if editor
23607 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23608 .unwrap_or(false)
23609 {
23610 return;
23611 }
23612
23613 buffer
23614 .update(cx, |buffer, cx| {
23615 for diff in diffs.into_iter().flatten() {
23616 buffer.add_diff(diff, cx);
23617 }
23618 })
23619 .ok();
23620 })
23621}
23622
23623fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23624 let tab_size = tab_size.get() as usize;
23625 let mut width = offset;
23626
23627 for ch in text.chars() {
23628 width += if ch == '\t' {
23629 tab_size - (width % tab_size)
23630 } else {
23631 1
23632 };
23633 }
23634
23635 width - offset
23636}
23637
23638#[cfg(test)]
23639mod tests {
23640 use super::*;
23641
23642 #[test]
23643 fn test_string_size_with_expanded_tabs() {
23644 let nz = |val| NonZeroU32::new(val).unwrap();
23645 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23646 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23647 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23648 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23649 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23650 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23651 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23652 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23653 }
23654}
23655
23656/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23657struct WordBreakingTokenizer<'a> {
23658 input: &'a str,
23659}
23660
23661impl<'a> WordBreakingTokenizer<'a> {
23662 fn new(input: &'a str) -> Self {
23663 Self { input }
23664 }
23665}
23666
23667fn is_char_ideographic(ch: char) -> bool {
23668 use unicode_script::Script::*;
23669 use unicode_script::UnicodeScript;
23670 matches!(ch.script(), Han | Tangut | Yi)
23671}
23672
23673fn is_grapheme_ideographic(text: &str) -> bool {
23674 text.chars().any(is_char_ideographic)
23675}
23676
23677fn is_grapheme_whitespace(text: &str) -> bool {
23678 text.chars().any(|x| x.is_whitespace())
23679}
23680
23681fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23682 text.chars()
23683 .next()
23684 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23685}
23686
23687#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23688enum WordBreakToken<'a> {
23689 Word { token: &'a str, grapheme_len: usize },
23690 InlineWhitespace { token: &'a str, grapheme_len: usize },
23691 Newline,
23692}
23693
23694impl<'a> Iterator for WordBreakingTokenizer<'a> {
23695 /// Yields a span, the count of graphemes in the token, and whether it was
23696 /// whitespace. Note that it also breaks at word boundaries.
23697 type Item = WordBreakToken<'a>;
23698
23699 fn next(&mut self) -> Option<Self::Item> {
23700 use unicode_segmentation::UnicodeSegmentation;
23701 if self.input.is_empty() {
23702 return None;
23703 }
23704
23705 let mut iter = self.input.graphemes(true).peekable();
23706 let mut offset = 0;
23707 let mut grapheme_len = 0;
23708 if let Some(first_grapheme) = iter.next() {
23709 let is_newline = first_grapheme == "\n";
23710 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23711 offset += first_grapheme.len();
23712 grapheme_len += 1;
23713 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23714 if let Some(grapheme) = iter.peek().copied()
23715 && should_stay_with_preceding_ideograph(grapheme)
23716 {
23717 offset += grapheme.len();
23718 grapheme_len += 1;
23719 }
23720 } else {
23721 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23722 let mut next_word_bound = words.peek().copied();
23723 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23724 next_word_bound = words.next();
23725 }
23726 while let Some(grapheme) = iter.peek().copied() {
23727 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23728 break;
23729 };
23730 if is_grapheme_whitespace(grapheme) != is_whitespace
23731 || (grapheme == "\n") != is_newline
23732 {
23733 break;
23734 };
23735 offset += grapheme.len();
23736 grapheme_len += 1;
23737 iter.next();
23738 }
23739 }
23740 let token = &self.input[..offset];
23741 self.input = &self.input[offset..];
23742 if token == "\n" {
23743 Some(WordBreakToken::Newline)
23744 } else if is_whitespace {
23745 Some(WordBreakToken::InlineWhitespace {
23746 token,
23747 grapheme_len,
23748 })
23749 } else {
23750 Some(WordBreakToken::Word {
23751 token,
23752 grapheme_len,
23753 })
23754 }
23755 } else {
23756 None
23757 }
23758 }
23759}
23760
23761#[test]
23762fn test_word_breaking_tokenizer() {
23763 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23764 ("", &[]),
23765 (" ", &[whitespace(" ", 2)]),
23766 ("Ʒ", &[word("Ʒ", 1)]),
23767 ("Ǽ", &[word("Ǽ", 1)]),
23768 ("⋑", &[word("⋑", 1)]),
23769 ("⋑⋑", &[word("⋑⋑", 2)]),
23770 (
23771 "原理,进而",
23772 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23773 ),
23774 (
23775 "hello world",
23776 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23777 ),
23778 (
23779 "hello, world",
23780 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23781 ),
23782 (
23783 " hello world",
23784 &[
23785 whitespace(" ", 2),
23786 word("hello", 5),
23787 whitespace(" ", 1),
23788 word("world", 5),
23789 ],
23790 ),
23791 (
23792 "这是什么 \n 钢笔",
23793 &[
23794 word("这", 1),
23795 word("是", 1),
23796 word("什", 1),
23797 word("么", 1),
23798 whitespace(" ", 1),
23799 newline(),
23800 whitespace(" ", 1),
23801 word("钢", 1),
23802 word("笔", 1),
23803 ],
23804 ),
23805 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23806 ];
23807
23808 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23809 WordBreakToken::Word {
23810 token,
23811 grapheme_len,
23812 }
23813 }
23814
23815 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23816 WordBreakToken::InlineWhitespace {
23817 token,
23818 grapheme_len,
23819 }
23820 }
23821
23822 fn newline() -> WordBreakToken<'static> {
23823 WordBreakToken::Newline
23824 }
23825
23826 for (input, result) in tests {
23827 assert_eq!(
23828 WordBreakingTokenizer::new(input)
23829 .collect::<Vec<_>>()
23830 .as_slice(),
23831 *result,
23832 );
23833 }
23834}
23835
23836fn wrap_with_prefix(
23837 first_line_prefix: String,
23838 subsequent_lines_prefix: String,
23839 unwrapped_text: String,
23840 wrap_column: usize,
23841 tab_size: NonZeroU32,
23842 preserve_existing_whitespace: bool,
23843) -> String {
23844 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23845 let subsequent_lines_prefix_len =
23846 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23847 let mut wrapped_text = String::new();
23848 let mut current_line = first_line_prefix;
23849 let mut is_first_line = true;
23850
23851 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23852 let mut current_line_len = first_line_prefix_len;
23853 let mut in_whitespace = false;
23854 for token in tokenizer {
23855 let have_preceding_whitespace = in_whitespace;
23856 match token {
23857 WordBreakToken::Word {
23858 token,
23859 grapheme_len,
23860 } => {
23861 in_whitespace = false;
23862 let current_prefix_len = if is_first_line {
23863 first_line_prefix_len
23864 } else {
23865 subsequent_lines_prefix_len
23866 };
23867 if current_line_len + grapheme_len > wrap_column
23868 && current_line_len != current_prefix_len
23869 {
23870 wrapped_text.push_str(current_line.trim_end());
23871 wrapped_text.push('\n');
23872 is_first_line = false;
23873 current_line = subsequent_lines_prefix.clone();
23874 current_line_len = subsequent_lines_prefix_len;
23875 }
23876 current_line.push_str(token);
23877 current_line_len += grapheme_len;
23878 }
23879 WordBreakToken::InlineWhitespace {
23880 mut token,
23881 mut grapheme_len,
23882 } => {
23883 in_whitespace = true;
23884 if have_preceding_whitespace && !preserve_existing_whitespace {
23885 continue;
23886 }
23887 if !preserve_existing_whitespace {
23888 // Keep a single whitespace grapheme as-is
23889 if let Some(first) =
23890 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23891 {
23892 token = first;
23893 } else {
23894 token = " ";
23895 }
23896 grapheme_len = 1;
23897 }
23898 let current_prefix_len = if is_first_line {
23899 first_line_prefix_len
23900 } else {
23901 subsequent_lines_prefix_len
23902 };
23903 if current_line_len + grapheme_len > wrap_column {
23904 wrapped_text.push_str(current_line.trim_end());
23905 wrapped_text.push('\n');
23906 is_first_line = false;
23907 current_line = subsequent_lines_prefix.clone();
23908 current_line_len = subsequent_lines_prefix_len;
23909 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23910 current_line.push_str(token);
23911 current_line_len += grapheme_len;
23912 }
23913 }
23914 WordBreakToken::Newline => {
23915 in_whitespace = true;
23916 let current_prefix_len = if is_first_line {
23917 first_line_prefix_len
23918 } else {
23919 subsequent_lines_prefix_len
23920 };
23921 if preserve_existing_whitespace {
23922 wrapped_text.push_str(current_line.trim_end());
23923 wrapped_text.push('\n');
23924 is_first_line = false;
23925 current_line = subsequent_lines_prefix.clone();
23926 current_line_len = subsequent_lines_prefix_len;
23927 } else if have_preceding_whitespace {
23928 continue;
23929 } else if current_line_len + 1 > wrap_column
23930 && current_line_len != current_prefix_len
23931 {
23932 wrapped_text.push_str(current_line.trim_end());
23933 wrapped_text.push('\n');
23934 is_first_line = false;
23935 current_line = subsequent_lines_prefix.clone();
23936 current_line_len = subsequent_lines_prefix_len;
23937 } else if current_line_len != current_prefix_len {
23938 current_line.push(' ');
23939 current_line_len += 1;
23940 }
23941 }
23942 }
23943 }
23944
23945 if !current_line.is_empty() {
23946 wrapped_text.push_str(¤t_line);
23947 }
23948 wrapped_text
23949}
23950
23951#[test]
23952fn test_wrap_with_prefix() {
23953 assert_eq!(
23954 wrap_with_prefix(
23955 "# ".to_string(),
23956 "# ".to_string(),
23957 "abcdefg".to_string(),
23958 4,
23959 NonZeroU32::new(4).unwrap(),
23960 false,
23961 ),
23962 "# abcdefg"
23963 );
23964 assert_eq!(
23965 wrap_with_prefix(
23966 "".to_string(),
23967 "".to_string(),
23968 "\thello world".to_string(),
23969 8,
23970 NonZeroU32::new(4).unwrap(),
23971 false,
23972 ),
23973 "hello\nworld"
23974 );
23975 assert_eq!(
23976 wrap_with_prefix(
23977 "// ".to_string(),
23978 "// ".to_string(),
23979 "xx \nyy zz aa bb cc".to_string(),
23980 12,
23981 NonZeroU32::new(4).unwrap(),
23982 false,
23983 ),
23984 "// xx yy zz\n// aa bb cc"
23985 );
23986 assert_eq!(
23987 wrap_with_prefix(
23988 String::new(),
23989 String::new(),
23990 "这是什么 \n 钢笔".to_string(),
23991 3,
23992 NonZeroU32::new(4).unwrap(),
23993 false,
23994 ),
23995 "这是什\n么 钢\n笔"
23996 );
23997 assert_eq!(
23998 wrap_with_prefix(
23999 String::new(),
24000 String::new(),
24001 format!("foo{}bar", '\u{2009}'), // thin space
24002 80,
24003 NonZeroU32::new(4).unwrap(),
24004 false,
24005 ),
24006 format!("foo{}bar", '\u{2009}')
24007 );
24008}
24009
24010pub trait CollaborationHub {
24011 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
24012 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
24013 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
24014}
24015
24016impl CollaborationHub for Entity<Project> {
24017 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
24018 self.read(cx).collaborators()
24019 }
24020
24021 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
24022 self.read(cx).user_store().read(cx).participant_indices()
24023 }
24024
24025 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
24026 let this = self.read(cx);
24027 let user_ids = this.collaborators().values().map(|c| c.user_id);
24028 this.user_store().read(cx).participant_names(user_ids, cx)
24029 }
24030}
24031
24032pub trait SemanticsProvider {
24033 fn hover(
24034 &self,
24035 buffer: &Entity<Buffer>,
24036 position: text::Anchor,
24037 cx: &mut App,
24038 ) -> Option<Task<Option<Vec<project::Hover>>>>;
24039
24040 fn inline_values(
24041 &self,
24042 buffer_handle: Entity<Buffer>,
24043 range: Range<text::Anchor>,
24044 cx: &mut App,
24045 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24046
24047 fn applicable_inlay_chunks(
24048 &self,
24049 buffer: &Entity<Buffer>,
24050 ranges: &[Range<text::Anchor>],
24051 cx: &mut App,
24052 ) -> Vec<Range<BufferRow>>;
24053
24054 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24055
24056 fn inlay_hints(
24057 &self,
24058 invalidate: InvalidationStrategy,
24059 buffer: Entity<Buffer>,
24060 ranges: Vec<Range<text::Anchor>>,
24061 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24062 cx: &mut App,
24063 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24064
24065 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24066
24067 fn document_highlights(
24068 &self,
24069 buffer: &Entity<Buffer>,
24070 position: text::Anchor,
24071 cx: &mut App,
24072 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24073
24074 fn definitions(
24075 &self,
24076 buffer: &Entity<Buffer>,
24077 position: text::Anchor,
24078 kind: GotoDefinitionKind,
24079 cx: &mut App,
24080 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24081
24082 fn range_for_rename(
24083 &self,
24084 buffer: &Entity<Buffer>,
24085 position: text::Anchor,
24086 cx: &mut App,
24087 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24088
24089 fn perform_rename(
24090 &self,
24091 buffer: &Entity<Buffer>,
24092 position: text::Anchor,
24093 new_name: String,
24094 cx: &mut App,
24095 ) -> Option<Task<Result<ProjectTransaction>>>;
24096}
24097
24098pub trait CompletionProvider {
24099 fn completions(
24100 &self,
24101 excerpt_id: ExcerptId,
24102 buffer: &Entity<Buffer>,
24103 buffer_position: text::Anchor,
24104 trigger: CompletionContext,
24105 window: &mut Window,
24106 cx: &mut Context<Editor>,
24107 ) -> Task<Result<Vec<CompletionResponse>>>;
24108
24109 fn resolve_completions(
24110 &self,
24111 _buffer: Entity<Buffer>,
24112 _completion_indices: Vec<usize>,
24113 _completions: Rc<RefCell<Box<[Completion]>>>,
24114 _cx: &mut Context<Editor>,
24115 ) -> Task<Result<bool>> {
24116 Task::ready(Ok(false))
24117 }
24118
24119 fn apply_additional_edits_for_completion(
24120 &self,
24121 _buffer: Entity<Buffer>,
24122 _completions: Rc<RefCell<Box<[Completion]>>>,
24123 _completion_index: usize,
24124 _push_to_history: bool,
24125 _cx: &mut Context<Editor>,
24126 ) -> Task<Result<Option<language::Transaction>>> {
24127 Task::ready(Ok(None))
24128 }
24129
24130 fn is_completion_trigger(
24131 &self,
24132 buffer: &Entity<Buffer>,
24133 position: language::Anchor,
24134 text: &str,
24135 trigger_in_words: bool,
24136 cx: &mut Context<Editor>,
24137 ) -> bool;
24138
24139 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24140
24141 fn sort_completions(&self) -> bool {
24142 true
24143 }
24144
24145 fn filter_completions(&self) -> bool {
24146 true
24147 }
24148
24149 fn show_snippets(&self) -> bool {
24150 false
24151 }
24152}
24153
24154pub trait CodeActionProvider {
24155 fn id(&self) -> Arc<str>;
24156
24157 fn code_actions(
24158 &self,
24159 buffer: &Entity<Buffer>,
24160 range: Range<text::Anchor>,
24161 window: &mut Window,
24162 cx: &mut App,
24163 ) -> Task<Result<Vec<CodeAction>>>;
24164
24165 fn apply_code_action(
24166 &self,
24167 buffer_handle: Entity<Buffer>,
24168 action: CodeAction,
24169 excerpt_id: ExcerptId,
24170 push_to_history: bool,
24171 window: &mut Window,
24172 cx: &mut App,
24173 ) -> Task<Result<ProjectTransaction>>;
24174}
24175
24176impl CodeActionProvider for Entity<Project> {
24177 fn id(&self) -> Arc<str> {
24178 "project".into()
24179 }
24180
24181 fn code_actions(
24182 &self,
24183 buffer: &Entity<Buffer>,
24184 range: Range<text::Anchor>,
24185 _window: &mut Window,
24186 cx: &mut App,
24187 ) -> Task<Result<Vec<CodeAction>>> {
24188 self.update(cx, |project, cx| {
24189 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24190 let code_actions = project.code_actions(buffer, range, None, cx);
24191 cx.background_spawn(async move {
24192 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24193 Ok(code_lens_actions
24194 .context("code lens fetch")?
24195 .into_iter()
24196 .flatten()
24197 .chain(
24198 code_actions
24199 .context("code action fetch")?
24200 .into_iter()
24201 .flatten(),
24202 )
24203 .collect())
24204 })
24205 })
24206 }
24207
24208 fn apply_code_action(
24209 &self,
24210 buffer_handle: Entity<Buffer>,
24211 action: CodeAction,
24212 _excerpt_id: ExcerptId,
24213 push_to_history: bool,
24214 _window: &mut Window,
24215 cx: &mut App,
24216 ) -> Task<Result<ProjectTransaction>> {
24217 self.update(cx, |project, cx| {
24218 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24219 })
24220 }
24221}
24222
24223fn snippet_completions(
24224 project: &Project,
24225 buffer: &Entity<Buffer>,
24226 buffer_anchor: text::Anchor,
24227 classifier: CharClassifier,
24228 cx: &mut App,
24229) -> Task<Result<CompletionResponse>> {
24230 let languages = buffer.read(cx).languages_at(buffer_anchor);
24231 let snippet_store = project.snippets().read(cx);
24232
24233 let scopes: Vec<_> = languages
24234 .iter()
24235 .filter_map(|language| {
24236 let language_name = language.lsp_id();
24237 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24238
24239 if snippets.is_empty() {
24240 None
24241 } else {
24242 Some((language.default_scope(), snippets))
24243 }
24244 })
24245 .collect();
24246
24247 if scopes.is_empty() {
24248 return Task::ready(Ok(CompletionResponse {
24249 completions: vec![],
24250 display_options: CompletionDisplayOptions::default(),
24251 is_incomplete: false,
24252 }));
24253 }
24254
24255 let snapshot = buffer.read(cx).text_snapshot();
24256 let executor = cx.background_executor().clone();
24257
24258 cx.background_spawn(async move {
24259 let is_word_char = |c| classifier.is_word(c);
24260
24261 let mut is_incomplete = false;
24262 let mut completions: Vec<Completion> = Vec::new();
24263
24264 const MAX_PREFIX_LEN: usize = 128;
24265 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24266 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24267 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24268
24269 let max_buffer_window: String = snapshot
24270 .text_for_range(window_start..buffer_offset)
24271 .collect();
24272
24273 if max_buffer_window.is_empty() {
24274 return Ok(CompletionResponse {
24275 completions: vec![],
24276 display_options: CompletionDisplayOptions::default(),
24277 is_incomplete: true,
24278 });
24279 }
24280
24281 for (_scope, snippets) in scopes.into_iter() {
24282 // Sort snippets by word count to match longer snippet prefixes first.
24283 let mut sorted_snippet_candidates = snippets
24284 .iter()
24285 .enumerate()
24286 .flat_map(|(snippet_ix, snippet)| {
24287 snippet
24288 .prefix
24289 .iter()
24290 .enumerate()
24291 .map(move |(prefix_ix, prefix)| {
24292 let word_count =
24293 snippet_candidate_suffixes(prefix, is_word_char).count();
24294 ((snippet_ix, prefix_ix), prefix, word_count)
24295 })
24296 })
24297 .collect_vec();
24298 sorted_snippet_candidates
24299 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24300
24301 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24302
24303 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24304 .take(
24305 sorted_snippet_candidates
24306 .first()
24307 .map(|(_, _, word_count)| *word_count)
24308 .unwrap_or_default(),
24309 )
24310 .collect_vec();
24311
24312 const MAX_RESULTS: usize = 100;
24313 // Each match also remembers how many characters from the buffer it consumed
24314 let mut matches: Vec<(StringMatch, usize)> = vec![];
24315
24316 let mut snippet_list_cutoff_index = 0;
24317 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24318 let word_count = buffer_index + 1;
24319 // Increase `snippet_list_cutoff_index` until we have all of the
24320 // snippets with sufficiently many words.
24321 while sorted_snippet_candidates
24322 .get(snippet_list_cutoff_index)
24323 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24324 *snippet_word_count >= word_count
24325 })
24326 {
24327 snippet_list_cutoff_index += 1;
24328 }
24329
24330 // Take only the candidates with at least `word_count` many words
24331 let snippet_candidates_at_word_len =
24332 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24333
24334 let candidates = snippet_candidates_at_word_len
24335 .iter()
24336 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24337 .enumerate() // index in `sorted_snippet_candidates`
24338 // First char must match
24339 .filter(|(_ix, prefix)| {
24340 itertools::equal(
24341 prefix
24342 .chars()
24343 .next()
24344 .into_iter()
24345 .flat_map(|c| c.to_lowercase()),
24346 buffer_window
24347 .chars()
24348 .next()
24349 .into_iter()
24350 .flat_map(|c| c.to_lowercase()),
24351 )
24352 })
24353 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24354 .collect::<Vec<StringMatchCandidate>>();
24355
24356 matches.extend(
24357 fuzzy::match_strings(
24358 &candidates,
24359 &buffer_window,
24360 buffer_window.chars().any(|c| c.is_uppercase()),
24361 true,
24362 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24363 &Default::default(),
24364 executor.clone(),
24365 )
24366 .await
24367 .into_iter()
24368 .map(|string_match| (string_match, buffer_window.len())),
24369 );
24370
24371 if matches.len() >= MAX_RESULTS {
24372 break;
24373 }
24374 }
24375
24376 let to_lsp = |point: &text::Anchor| {
24377 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24378 point_to_lsp(end)
24379 };
24380 let lsp_end = to_lsp(&buffer_anchor);
24381
24382 if matches.len() >= MAX_RESULTS {
24383 is_incomplete = true;
24384 }
24385
24386 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24387 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24388 sorted_snippet_candidates[string_match.candidate_id];
24389 let snippet = &snippets[snippet_index];
24390 let start = buffer_offset - buffer_window_len;
24391 let start = snapshot.anchor_before(start);
24392 let range = start..buffer_anchor;
24393 let lsp_start = to_lsp(&start);
24394 let lsp_range = lsp::Range {
24395 start: lsp_start,
24396 end: lsp_end,
24397 };
24398 Completion {
24399 replace_range: range,
24400 new_text: snippet.body.clone(),
24401 source: CompletionSource::Lsp {
24402 insert_range: None,
24403 server_id: LanguageServerId(usize::MAX),
24404 resolved: true,
24405 lsp_completion: Box::new(lsp::CompletionItem {
24406 label: snippet.prefix.first().unwrap().clone(),
24407 kind: Some(CompletionItemKind::SNIPPET),
24408 label_details: snippet.description.as_ref().map(|description| {
24409 lsp::CompletionItemLabelDetails {
24410 detail: Some(description.clone()),
24411 description: None,
24412 }
24413 }),
24414 insert_text_format: Some(InsertTextFormat::SNIPPET),
24415 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24416 lsp::InsertReplaceEdit {
24417 new_text: snippet.body.clone(),
24418 insert: lsp_range,
24419 replace: lsp_range,
24420 },
24421 )),
24422 filter_text: Some(snippet.body.clone()),
24423 sort_text: Some(char::MAX.to_string()),
24424 ..lsp::CompletionItem::default()
24425 }),
24426 lsp_defaults: None,
24427 },
24428 label: CodeLabel {
24429 text: matching_prefix.clone(),
24430 runs: Vec::new(),
24431 filter_range: 0..matching_prefix.len(),
24432 },
24433 icon_path: None,
24434 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24435 single_line: snippet.name.clone().into(),
24436 plain_text: snippet
24437 .description
24438 .clone()
24439 .map(|description| description.into()),
24440 }),
24441 insert_text_mode: None,
24442 confirm: None,
24443 match_start: Some(start),
24444 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24445 }
24446 }));
24447 }
24448
24449 Ok(CompletionResponse {
24450 completions,
24451 display_options: CompletionDisplayOptions::default(),
24452 is_incomplete,
24453 })
24454 })
24455}
24456
24457impl CompletionProvider for Entity<Project> {
24458 fn completions(
24459 &self,
24460 _excerpt_id: ExcerptId,
24461 buffer: &Entity<Buffer>,
24462 buffer_position: text::Anchor,
24463 options: CompletionContext,
24464 _window: &mut Window,
24465 cx: &mut Context<Editor>,
24466 ) -> Task<Result<Vec<CompletionResponse>>> {
24467 self.update(cx, |project, cx| {
24468 let task = project.completions(buffer, buffer_position, options, cx);
24469 cx.background_spawn(task)
24470 })
24471 }
24472
24473 fn resolve_completions(
24474 &self,
24475 buffer: Entity<Buffer>,
24476 completion_indices: Vec<usize>,
24477 completions: Rc<RefCell<Box<[Completion]>>>,
24478 cx: &mut Context<Editor>,
24479 ) -> Task<Result<bool>> {
24480 self.update(cx, |project, cx| {
24481 project.lsp_store().update(cx, |lsp_store, cx| {
24482 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24483 })
24484 })
24485 }
24486
24487 fn apply_additional_edits_for_completion(
24488 &self,
24489 buffer: Entity<Buffer>,
24490 completions: Rc<RefCell<Box<[Completion]>>>,
24491 completion_index: usize,
24492 push_to_history: bool,
24493 cx: &mut Context<Editor>,
24494 ) -> Task<Result<Option<language::Transaction>>> {
24495 self.update(cx, |project, cx| {
24496 project.lsp_store().update(cx, |lsp_store, cx| {
24497 lsp_store.apply_additional_edits_for_completion(
24498 buffer,
24499 completions,
24500 completion_index,
24501 push_to_history,
24502 cx,
24503 )
24504 })
24505 })
24506 }
24507
24508 fn is_completion_trigger(
24509 &self,
24510 buffer: &Entity<Buffer>,
24511 position: language::Anchor,
24512 text: &str,
24513 trigger_in_words: bool,
24514 cx: &mut Context<Editor>,
24515 ) -> bool {
24516 let mut chars = text.chars();
24517 let char = if let Some(char) = chars.next() {
24518 char
24519 } else {
24520 return false;
24521 };
24522 if chars.next().is_some() {
24523 return false;
24524 }
24525
24526 let buffer = buffer.read(cx);
24527 let snapshot = buffer.snapshot();
24528 let classifier = snapshot
24529 .char_classifier_at(position)
24530 .scope_context(Some(CharScopeContext::Completion));
24531 if trigger_in_words && classifier.is_word(char) {
24532 return true;
24533 }
24534
24535 buffer.completion_triggers().contains(text)
24536 }
24537
24538 fn show_snippets(&self) -> bool {
24539 true
24540 }
24541}
24542
24543impl SemanticsProvider for Entity<Project> {
24544 fn hover(
24545 &self,
24546 buffer: &Entity<Buffer>,
24547 position: text::Anchor,
24548 cx: &mut App,
24549 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24550 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24551 }
24552
24553 fn document_highlights(
24554 &self,
24555 buffer: &Entity<Buffer>,
24556 position: text::Anchor,
24557 cx: &mut App,
24558 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24559 Some(self.update(cx, |project, cx| {
24560 project.document_highlights(buffer, position, cx)
24561 }))
24562 }
24563
24564 fn definitions(
24565 &self,
24566 buffer: &Entity<Buffer>,
24567 position: text::Anchor,
24568 kind: GotoDefinitionKind,
24569 cx: &mut App,
24570 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24571 Some(self.update(cx, |project, cx| match kind {
24572 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24573 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24574 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24575 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24576 }))
24577 }
24578
24579 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24580 self.update(cx, |project, cx| {
24581 if project
24582 .active_debug_session(cx)
24583 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24584 {
24585 return true;
24586 }
24587
24588 buffer.update(cx, |buffer, cx| {
24589 project.any_language_server_supports_inlay_hints(buffer, cx)
24590 })
24591 })
24592 }
24593
24594 fn inline_values(
24595 &self,
24596 buffer_handle: Entity<Buffer>,
24597 range: Range<text::Anchor>,
24598 cx: &mut App,
24599 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24600 self.update(cx, |project, cx| {
24601 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24602
24603 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24604 })
24605 }
24606
24607 fn applicable_inlay_chunks(
24608 &self,
24609 buffer: &Entity<Buffer>,
24610 ranges: &[Range<text::Anchor>],
24611 cx: &mut App,
24612 ) -> Vec<Range<BufferRow>> {
24613 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24614 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24615 })
24616 }
24617
24618 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24619 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24620 lsp_store.invalidate_inlay_hints(for_buffers)
24621 });
24622 }
24623
24624 fn inlay_hints(
24625 &self,
24626 invalidate: InvalidationStrategy,
24627 buffer: Entity<Buffer>,
24628 ranges: Vec<Range<text::Anchor>>,
24629 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24630 cx: &mut App,
24631 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24632 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24633 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24634 }))
24635 }
24636
24637 fn range_for_rename(
24638 &self,
24639 buffer: &Entity<Buffer>,
24640 position: text::Anchor,
24641 cx: &mut App,
24642 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24643 Some(self.update(cx, |project, cx| {
24644 let buffer = buffer.clone();
24645 let task = project.prepare_rename(buffer.clone(), position, cx);
24646 cx.spawn(async move |_, cx| {
24647 Ok(match task.await? {
24648 PrepareRenameResponse::Success(range) => Some(range),
24649 PrepareRenameResponse::InvalidPosition => None,
24650 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24651 // Fallback on using TreeSitter info to determine identifier range
24652 buffer.read_with(cx, |buffer, _| {
24653 let snapshot = buffer.snapshot();
24654 let (range, kind) = snapshot.surrounding_word(position, None);
24655 if kind != Some(CharKind::Word) {
24656 return None;
24657 }
24658 Some(
24659 snapshot.anchor_before(range.start)
24660 ..snapshot.anchor_after(range.end),
24661 )
24662 })?
24663 }
24664 })
24665 })
24666 }))
24667 }
24668
24669 fn perform_rename(
24670 &self,
24671 buffer: &Entity<Buffer>,
24672 position: text::Anchor,
24673 new_name: String,
24674 cx: &mut App,
24675 ) -> Option<Task<Result<ProjectTransaction>>> {
24676 Some(self.update(cx, |project, cx| {
24677 project.perform_rename(buffer.clone(), position, new_name, cx)
24678 }))
24679 }
24680}
24681
24682fn consume_contiguous_rows(
24683 contiguous_row_selections: &mut Vec<Selection<Point>>,
24684 selection: &Selection<Point>,
24685 display_map: &DisplaySnapshot,
24686 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24687) -> (MultiBufferRow, MultiBufferRow) {
24688 contiguous_row_selections.push(selection.clone());
24689 let start_row = starting_row(selection, display_map);
24690 let mut end_row = ending_row(selection, display_map);
24691
24692 while let Some(next_selection) = selections.peek() {
24693 if next_selection.start.row <= end_row.0 {
24694 end_row = ending_row(next_selection, display_map);
24695 contiguous_row_selections.push(selections.next().unwrap().clone());
24696 } else {
24697 break;
24698 }
24699 }
24700 (start_row, end_row)
24701}
24702
24703fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24704 if selection.start.column > 0 {
24705 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24706 } else {
24707 MultiBufferRow(selection.start.row)
24708 }
24709}
24710
24711fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24712 if next_selection.end.column > 0 || next_selection.is_empty() {
24713 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24714 } else {
24715 MultiBufferRow(next_selection.end.row)
24716 }
24717}
24718
24719impl EditorSnapshot {
24720 pub fn remote_selections_in_range<'a>(
24721 &'a self,
24722 range: &'a Range<Anchor>,
24723 collaboration_hub: &dyn CollaborationHub,
24724 cx: &'a App,
24725 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24726 let participant_names = collaboration_hub.user_names(cx);
24727 let participant_indices = collaboration_hub.user_participant_indices(cx);
24728 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24729 let collaborators_by_replica_id = collaborators_by_peer_id
24730 .values()
24731 .map(|collaborator| (collaborator.replica_id, collaborator))
24732 .collect::<HashMap<_, _>>();
24733 self.buffer_snapshot()
24734 .selections_in_range(range, false)
24735 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24736 if replica_id == ReplicaId::AGENT {
24737 Some(RemoteSelection {
24738 replica_id,
24739 selection,
24740 cursor_shape,
24741 line_mode,
24742 collaborator_id: CollaboratorId::Agent,
24743 user_name: Some("Agent".into()),
24744 color: cx.theme().players().agent(),
24745 })
24746 } else {
24747 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24748 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24749 let user_name = participant_names.get(&collaborator.user_id).cloned();
24750 Some(RemoteSelection {
24751 replica_id,
24752 selection,
24753 cursor_shape,
24754 line_mode,
24755 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24756 user_name,
24757 color: if let Some(index) = participant_index {
24758 cx.theme().players().color_for_participant(index.0)
24759 } else {
24760 cx.theme().players().absent()
24761 },
24762 })
24763 }
24764 })
24765 }
24766
24767 pub fn hunks_for_ranges(
24768 &self,
24769 ranges: impl IntoIterator<Item = Range<Point>>,
24770 ) -> Vec<MultiBufferDiffHunk> {
24771 let mut hunks = Vec::new();
24772 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24773 HashMap::default();
24774 for query_range in ranges {
24775 let query_rows =
24776 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24777 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24778 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24779 ) {
24780 // Include deleted hunks that are adjacent to the query range, because
24781 // otherwise they would be missed.
24782 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24783 if hunk.status().is_deleted() {
24784 intersects_range |= hunk.row_range.start == query_rows.end;
24785 intersects_range |= hunk.row_range.end == query_rows.start;
24786 }
24787 if intersects_range {
24788 if !processed_buffer_rows
24789 .entry(hunk.buffer_id)
24790 .or_default()
24791 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24792 {
24793 continue;
24794 }
24795 hunks.push(hunk);
24796 }
24797 }
24798 }
24799
24800 hunks
24801 }
24802
24803 fn display_diff_hunks_for_rows<'a>(
24804 &'a self,
24805 display_rows: Range<DisplayRow>,
24806 folded_buffers: &'a HashSet<BufferId>,
24807 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24808 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24809 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24810
24811 self.buffer_snapshot()
24812 .diff_hunks_in_range(buffer_start..buffer_end)
24813 .filter_map(|hunk| {
24814 if folded_buffers.contains(&hunk.buffer_id) {
24815 return None;
24816 }
24817
24818 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24819 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24820
24821 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24822 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24823
24824 let display_hunk = if hunk_display_start.column() != 0 {
24825 DisplayDiffHunk::Folded {
24826 display_row: hunk_display_start.row(),
24827 }
24828 } else {
24829 let mut end_row = hunk_display_end.row();
24830 if hunk_display_end.column() > 0 {
24831 end_row.0 += 1;
24832 }
24833 let is_created_file = hunk.is_created_file();
24834
24835 DisplayDiffHunk::Unfolded {
24836 status: hunk.status(),
24837 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24838 ..hunk.diff_base_byte_range.end.0,
24839 word_diffs: hunk.word_diffs,
24840 display_row_range: hunk_display_start.row()..end_row,
24841 multi_buffer_range: Anchor::range_in_buffer(
24842 hunk.excerpt_id,
24843 hunk.buffer_range,
24844 ),
24845 is_created_file,
24846 }
24847 };
24848
24849 Some(display_hunk)
24850 })
24851 }
24852
24853 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24854 self.display_snapshot
24855 .buffer_snapshot()
24856 .language_at(position)
24857 }
24858
24859 pub fn is_focused(&self) -> bool {
24860 self.is_focused
24861 }
24862
24863 pub fn placeholder_text(&self) -> Option<String> {
24864 self.placeholder_display_snapshot
24865 .as_ref()
24866 .map(|display_map| display_map.text())
24867 }
24868
24869 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24870 self.scroll_anchor.scroll_position(&self.display_snapshot)
24871 }
24872
24873 pub fn gutter_dimensions(
24874 &self,
24875 font_id: FontId,
24876 font_size: Pixels,
24877 style: &EditorStyle,
24878 window: &mut Window,
24879 cx: &App,
24880 ) -> GutterDimensions {
24881 if self.show_gutter
24882 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
24883 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
24884 {
24885 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24886 matches!(
24887 ProjectSettings::get_global(cx).git.git_gutter,
24888 GitGutterSetting::TrackedFiles
24889 )
24890 });
24891 let gutter_settings = EditorSettings::get_global(cx).gutter;
24892 let show_line_numbers = self
24893 .show_line_numbers
24894 .unwrap_or(gutter_settings.line_numbers);
24895 let line_gutter_width = if show_line_numbers {
24896 // Avoid flicker-like gutter resizes when the line number gains another digit by
24897 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24898 let min_width_for_number_on_gutter =
24899 ch_advance * gutter_settings.min_line_number_digits as f32;
24900 self.max_line_number_width(style, window)
24901 .max(min_width_for_number_on_gutter)
24902 } else {
24903 0.0.into()
24904 };
24905
24906 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24907 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24908
24909 let git_blame_entries_width =
24910 self.git_blame_gutter_max_author_length
24911 .map(|max_author_length| {
24912 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24913 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24914
24915 /// The number of characters to dedicate to gaps and margins.
24916 const SPACING_WIDTH: usize = 4;
24917
24918 let max_char_count = max_author_length.min(renderer.max_author_length())
24919 + ::git::SHORT_SHA_LENGTH
24920 + MAX_RELATIVE_TIMESTAMP.len()
24921 + SPACING_WIDTH;
24922
24923 ch_advance * max_char_count
24924 });
24925
24926 let is_singleton = self.buffer_snapshot().is_singleton();
24927
24928 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24929 left_padding += if !is_singleton {
24930 ch_width * 4.0
24931 } else if show_runnables || show_breakpoints {
24932 ch_width * 3.0
24933 } else if show_git_gutter && show_line_numbers {
24934 ch_width * 2.0
24935 } else if show_git_gutter || show_line_numbers {
24936 ch_width
24937 } else {
24938 px(0.)
24939 };
24940
24941 let shows_folds = is_singleton && gutter_settings.folds;
24942
24943 let right_padding = if shows_folds && show_line_numbers {
24944 ch_width * 4.0
24945 } else if shows_folds || (!is_singleton && show_line_numbers) {
24946 ch_width * 3.0
24947 } else if show_line_numbers {
24948 ch_width
24949 } else {
24950 px(0.)
24951 };
24952
24953 GutterDimensions {
24954 left_padding,
24955 right_padding,
24956 width: line_gutter_width + left_padding + right_padding,
24957 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24958 git_blame_entries_width,
24959 }
24960 } else if self.offset_content {
24961 GutterDimensions::default_with_margin(font_id, font_size, cx)
24962 } else {
24963 GutterDimensions::default()
24964 }
24965 }
24966
24967 pub fn render_crease_toggle(
24968 &self,
24969 buffer_row: MultiBufferRow,
24970 row_contains_cursor: bool,
24971 editor: Entity<Editor>,
24972 window: &mut Window,
24973 cx: &mut App,
24974 ) -> Option<AnyElement> {
24975 let folded = self.is_line_folded(buffer_row);
24976 let mut is_foldable = false;
24977
24978 if let Some(crease) = self
24979 .crease_snapshot
24980 .query_row(buffer_row, self.buffer_snapshot())
24981 {
24982 is_foldable = true;
24983 match crease {
24984 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24985 if let Some(render_toggle) = render_toggle {
24986 let toggle_callback =
24987 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24988 if folded {
24989 editor.update(cx, |editor, cx| {
24990 editor.fold_at(buffer_row, window, cx)
24991 });
24992 } else {
24993 editor.update(cx, |editor, cx| {
24994 editor.unfold_at(buffer_row, window, cx)
24995 });
24996 }
24997 });
24998 return Some((render_toggle)(
24999 buffer_row,
25000 folded,
25001 toggle_callback,
25002 window,
25003 cx,
25004 ));
25005 }
25006 }
25007 }
25008 }
25009
25010 is_foldable |= self.starts_indent(buffer_row);
25011
25012 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
25013 Some(
25014 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
25015 .toggle_state(folded)
25016 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
25017 if folded {
25018 this.unfold_at(buffer_row, window, cx);
25019 } else {
25020 this.fold_at(buffer_row, window, cx);
25021 }
25022 }))
25023 .into_any_element(),
25024 )
25025 } else {
25026 None
25027 }
25028 }
25029
25030 pub fn render_crease_trailer(
25031 &self,
25032 buffer_row: MultiBufferRow,
25033 window: &mut Window,
25034 cx: &mut App,
25035 ) -> Option<AnyElement> {
25036 let folded = self.is_line_folded(buffer_row);
25037 if let Crease::Inline { render_trailer, .. } = self
25038 .crease_snapshot
25039 .query_row(buffer_row, self.buffer_snapshot())?
25040 {
25041 let render_trailer = render_trailer.as_ref()?;
25042 Some(render_trailer(buffer_row, folded, window, cx))
25043 } else {
25044 None
25045 }
25046 }
25047
25048 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25049 let digit_count = self.widest_line_number().ilog10() + 1;
25050 column_pixels(style, digit_count as usize, window)
25051 }
25052}
25053
25054pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25055 let font_size = style.text.font_size.to_pixels(window.rem_size());
25056 let layout = window.text_system().shape_line(
25057 SharedString::from(" ".repeat(column)),
25058 font_size,
25059 &[TextRun {
25060 len: column,
25061 font: style.text.font(),
25062 color: Hsla::default(),
25063 ..Default::default()
25064 }],
25065 None,
25066 );
25067
25068 layout.width
25069}
25070
25071impl Deref for EditorSnapshot {
25072 type Target = DisplaySnapshot;
25073
25074 fn deref(&self) -> &Self::Target {
25075 &self.display_snapshot
25076 }
25077}
25078
25079#[derive(Clone, Debug, PartialEq, Eq)]
25080pub enum EditorEvent {
25081 InputIgnored {
25082 text: Arc<str>,
25083 },
25084 InputHandled {
25085 utf16_range_to_replace: Option<Range<isize>>,
25086 text: Arc<str>,
25087 },
25088 ExcerptsAdded {
25089 buffer: Entity<Buffer>,
25090 predecessor: ExcerptId,
25091 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25092 },
25093 ExcerptsRemoved {
25094 ids: Vec<ExcerptId>,
25095 removed_buffer_ids: Vec<BufferId>,
25096 },
25097 BufferFoldToggled {
25098 ids: Vec<ExcerptId>,
25099 folded: bool,
25100 },
25101 ExcerptsEdited {
25102 ids: Vec<ExcerptId>,
25103 },
25104 ExcerptsExpanded {
25105 ids: Vec<ExcerptId>,
25106 },
25107 BufferEdited,
25108 Edited {
25109 transaction_id: clock::Lamport,
25110 },
25111 Reparsed(BufferId),
25112 Focused,
25113 FocusedIn,
25114 Blurred,
25115 DirtyChanged,
25116 Saved,
25117 TitleChanged,
25118 SelectionsChanged {
25119 local: bool,
25120 },
25121 ScrollPositionChanged {
25122 local: bool,
25123 autoscroll: bool,
25124 },
25125 TransactionUndone {
25126 transaction_id: clock::Lamport,
25127 },
25128 TransactionBegun {
25129 transaction_id: clock::Lamport,
25130 },
25131 CursorShapeChanged,
25132 BreadcrumbsChanged,
25133 PushedToNavHistory {
25134 anchor: Anchor,
25135 is_deactivate: bool,
25136 },
25137}
25138
25139impl EventEmitter<EditorEvent> for Editor {}
25140
25141impl Focusable for Editor {
25142 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25143 self.focus_handle.clone()
25144 }
25145}
25146
25147impl Render for Editor {
25148 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25149 EditorElement::new(&cx.entity(), self.create_style(cx))
25150 }
25151}
25152
25153impl EntityInputHandler for Editor {
25154 fn text_for_range(
25155 &mut self,
25156 range_utf16: Range<usize>,
25157 adjusted_range: &mut Option<Range<usize>>,
25158 _: &mut Window,
25159 cx: &mut Context<Self>,
25160 ) -> Option<String> {
25161 let snapshot = self.buffer.read(cx).read(cx);
25162 let start = snapshot.clip_offset_utf16(
25163 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25164 Bias::Left,
25165 );
25166 let end = snapshot.clip_offset_utf16(
25167 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25168 Bias::Right,
25169 );
25170 if (start.0.0..end.0.0) != range_utf16 {
25171 adjusted_range.replace(start.0.0..end.0.0);
25172 }
25173 Some(snapshot.text_for_range(start..end).collect())
25174 }
25175
25176 fn selected_text_range(
25177 &mut self,
25178 ignore_disabled_input: bool,
25179 _: &mut Window,
25180 cx: &mut Context<Self>,
25181 ) -> Option<UTF16Selection> {
25182 // Prevent the IME menu from appearing when holding down an alphabetic key
25183 // while input is disabled.
25184 if !ignore_disabled_input && !self.input_enabled {
25185 return None;
25186 }
25187
25188 let selection = self
25189 .selections
25190 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25191 let range = selection.range();
25192
25193 Some(UTF16Selection {
25194 range: range.start.0.0..range.end.0.0,
25195 reversed: selection.reversed,
25196 })
25197 }
25198
25199 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25200 let snapshot = self.buffer.read(cx).read(cx);
25201 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25202 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25203 }
25204
25205 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25206 self.clear_highlights::<InputComposition>(cx);
25207 self.ime_transaction.take();
25208 }
25209
25210 fn replace_text_in_range(
25211 &mut self,
25212 range_utf16: Option<Range<usize>>,
25213 text: &str,
25214 window: &mut Window,
25215 cx: &mut Context<Self>,
25216 ) {
25217 if !self.input_enabled {
25218 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25219 return;
25220 }
25221
25222 self.transact(window, cx, |this, window, cx| {
25223 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25224 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25225 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25226 Some(this.selection_replacement_ranges(range_utf16, cx))
25227 } else {
25228 this.marked_text_ranges(cx)
25229 };
25230
25231 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25232 let newest_selection_id = this.selections.newest_anchor().id;
25233 this.selections
25234 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25235 .iter()
25236 .zip(ranges_to_replace.iter())
25237 .find_map(|(selection, range)| {
25238 if selection.id == newest_selection_id {
25239 Some(
25240 (range.start.0.0 as isize - selection.head().0.0 as isize)
25241 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25242 )
25243 } else {
25244 None
25245 }
25246 })
25247 });
25248
25249 cx.emit(EditorEvent::InputHandled {
25250 utf16_range_to_replace: range_to_replace,
25251 text: text.into(),
25252 });
25253
25254 if let Some(new_selected_ranges) = new_selected_ranges {
25255 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25256 selections.select_ranges(new_selected_ranges)
25257 });
25258 this.backspace(&Default::default(), window, cx);
25259 }
25260
25261 this.handle_input(text, window, cx);
25262 });
25263
25264 if let Some(transaction) = self.ime_transaction {
25265 self.buffer.update(cx, |buffer, cx| {
25266 buffer.group_until_transaction(transaction, cx);
25267 });
25268 }
25269
25270 self.unmark_text(window, cx);
25271 }
25272
25273 fn replace_and_mark_text_in_range(
25274 &mut self,
25275 range_utf16: Option<Range<usize>>,
25276 text: &str,
25277 new_selected_range_utf16: Option<Range<usize>>,
25278 window: &mut Window,
25279 cx: &mut Context<Self>,
25280 ) {
25281 if !self.input_enabled {
25282 return;
25283 }
25284
25285 let transaction = self.transact(window, cx, |this, window, cx| {
25286 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25287 let snapshot = this.buffer.read(cx).read(cx);
25288 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25289 for marked_range in &mut marked_ranges {
25290 marked_range.end = marked_range.start + relative_range_utf16.end;
25291 marked_range.start += relative_range_utf16.start;
25292 marked_range.start =
25293 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25294 marked_range.end =
25295 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25296 }
25297 }
25298 Some(marked_ranges)
25299 } else if let Some(range_utf16) = range_utf16 {
25300 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25301 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25302 Some(this.selection_replacement_ranges(range_utf16, cx))
25303 } else {
25304 None
25305 };
25306
25307 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25308 let newest_selection_id = this.selections.newest_anchor().id;
25309 this.selections
25310 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25311 .iter()
25312 .zip(ranges_to_replace.iter())
25313 .find_map(|(selection, range)| {
25314 if selection.id == newest_selection_id {
25315 Some(
25316 (range.start.0.0 as isize - selection.head().0.0 as isize)
25317 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25318 )
25319 } else {
25320 None
25321 }
25322 })
25323 });
25324
25325 cx.emit(EditorEvent::InputHandled {
25326 utf16_range_to_replace: range_to_replace,
25327 text: text.into(),
25328 });
25329
25330 if let Some(ranges) = ranges_to_replace {
25331 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25332 s.select_ranges(ranges)
25333 });
25334 }
25335
25336 let marked_ranges = {
25337 let snapshot = this.buffer.read(cx).read(cx);
25338 this.selections
25339 .disjoint_anchors_arc()
25340 .iter()
25341 .map(|selection| {
25342 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25343 })
25344 .collect::<Vec<_>>()
25345 };
25346
25347 if text.is_empty() {
25348 this.unmark_text(window, cx);
25349 } else {
25350 this.highlight_text::<InputComposition>(
25351 marked_ranges.clone(),
25352 HighlightStyle {
25353 underline: Some(UnderlineStyle {
25354 thickness: px(1.),
25355 color: None,
25356 wavy: false,
25357 }),
25358 ..Default::default()
25359 },
25360 cx,
25361 );
25362 }
25363
25364 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25365 let use_autoclose = this.use_autoclose;
25366 let use_auto_surround = this.use_auto_surround;
25367 this.set_use_autoclose(false);
25368 this.set_use_auto_surround(false);
25369 this.handle_input(text, window, cx);
25370 this.set_use_autoclose(use_autoclose);
25371 this.set_use_auto_surround(use_auto_surround);
25372
25373 if let Some(new_selected_range) = new_selected_range_utf16 {
25374 let snapshot = this.buffer.read(cx).read(cx);
25375 let new_selected_ranges = marked_ranges
25376 .into_iter()
25377 .map(|marked_range| {
25378 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25379 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25380 insertion_start.0 + new_selected_range.start,
25381 ));
25382 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25383 insertion_start.0 + new_selected_range.end,
25384 ));
25385 snapshot.clip_offset_utf16(new_start, Bias::Left)
25386 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25387 })
25388 .collect::<Vec<_>>();
25389
25390 drop(snapshot);
25391 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25392 selections.select_ranges(new_selected_ranges)
25393 });
25394 }
25395 });
25396
25397 self.ime_transaction = self.ime_transaction.or(transaction);
25398 if let Some(transaction) = self.ime_transaction {
25399 self.buffer.update(cx, |buffer, cx| {
25400 buffer.group_until_transaction(transaction, cx);
25401 });
25402 }
25403
25404 if self.text_highlights::<InputComposition>(cx).is_none() {
25405 self.ime_transaction.take();
25406 }
25407 }
25408
25409 fn bounds_for_range(
25410 &mut self,
25411 range_utf16: Range<usize>,
25412 element_bounds: gpui::Bounds<Pixels>,
25413 window: &mut Window,
25414 cx: &mut Context<Self>,
25415 ) -> Option<gpui::Bounds<Pixels>> {
25416 let text_layout_details = self.text_layout_details(window);
25417 let CharacterDimensions {
25418 em_width,
25419 em_advance,
25420 line_height,
25421 } = self.character_dimensions(window);
25422
25423 let snapshot = self.snapshot(window, cx);
25424 let scroll_position = snapshot.scroll_position();
25425 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25426
25427 let start =
25428 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25429 let x = Pixels::from(
25430 ScrollOffset::from(
25431 snapshot.x_for_display_point(start, &text_layout_details)
25432 + self.gutter_dimensions.full_width(),
25433 ) - scroll_left,
25434 );
25435 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25436
25437 Some(Bounds {
25438 origin: element_bounds.origin + point(x, y),
25439 size: size(em_width, line_height),
25440 })
25441 }
25442
25443 fn character_index_for_point(
25444 &mut self,
25445 point: gpui::Point<Pixels>,
25446 _window: &mut Window,
25447 _cx: &mut Context<Self>,
25448 ) -> Option<usize> {
25449 let position_map = self.last_position_map.as_ref()?;
25450 if !position_map.text_hitbox.contains(&point) {
25451 return None;
25452 }
25453 let display_point = position_map.point_for_position(point).previous_valid;
25454 let anchor = position_map
25455 .snapshot
25456 .display_point_to_anchor(display_point, Bias::Left);
25457 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25458 Some(utf16_offset.0.0)
25459 }
25460
25461 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25462 self.input_enabled
25463 }
25464}
25465
25466trait SelectionExt {
25467 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25468 fn spanned_rows(
25469 &self,
25470 include_end_if_at_line_start: bool,
25471 map: &DisplaySnapshot,
25472 ) -> Range<MultiBufferRow>;
25473}
25474
25475impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25476 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25477 let start = self
25478 .start
25479 .to_point(map.buffer_snapshot())
25480 .to_display_point(map);
25481 let end = self
25482 .end
25483 .to_point(map.buffer_snapshot())
25484 .to_display_point(map);
25485 if self.reversed {
25486 end..start
25487 } else {
25488 start..end
25489 }
25490 }
25491
25492 fn spanned_rows(
25493 &self,
25494 include_end_if_at_line_start: bool,
25495 map: &DisplaySnapshot,
25496 ) -> Range<MultiBufferRow> {
25497 let start = self.start.to_point(map.buffer_snapshot());
25498 let mut end = self.end.to_point(map.buffer_snapshot());
25499 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25500 end.row -= 1;
25501 }
25502
25503 let buffer_start = map.prev_line_boundary(start).0;
25504 let buffer_end = map.next_line_boundary(end).0;
25505 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25506 }
25507}
25508
25509impl<T: InvalidationRegion> InvalidationStack<T> {
25510 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25511 where
25512 S: Clone + ToOffset,
25513 {
25514 while let Some(region) = self.last() {
25515 let all_selections_inside_invalidation_ranges =
25516 if selections.len() == region.ranges().len() {
25517 selections
25518 .iter()
25519 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25520 .all(|(selection, invalidation_range)| {
25521 let head = selection.head().to_offset(buffer);
25522 invalidation_range.start <= head && invalidation_range.end >= head
25523 })
25524 } else {
25525 false
25526 };
25527
25528 if all_selections_inside_invalidation_ranges {
25529 break;
25530 } else {
25531 self.pop();
25532 }
25533 }
25534 }
25535}
25536
25537impl<T> Default for InvalidationStack<T> {
25538 fn default() -> Self {
25539 Self(Default::default())
25540 }
25541}
25542
25543impl<T> Deref for InvalidationStack<T> {
25544 type Target = Vec<T>;
25545
25546 fn deref(&self) -> &Self::Target {
25547 &self.0
25548 }
25549}
25550
25551impl<T> DerefMut for InvalidationStack<T> {
25552 fn deref_mut(&mut self) -> &mut Self::Target {
25553 &mut self.0
25554 }
25555}
25556
25557impl InvalidationRegion for SnippetState {
25558 fn ranges(&self) -> &[Range<Anchor>] {
25559 &self.ranges[self.active_index]
25560 }
25561}
25562
25563fn edit_prediction_edit_text(
25564 current_snapshot: &BufferSnapshot,
25565 edits: &[(Range<Anchor>, impl AsRef<str>)],
25566 edit_preview: &EditPreview,
25567 include_deletions: bool,
25568 cx: &App,
25569) -> HighlightedText {
25570 let edits = edits
25571 .iter()
25572 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25573 .collect::<Vec<_>>();
25574
25575 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25576}
25577
25578fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25579 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25580 // Just show the raw edit text with basic styling
25581 let mut text = String::new();
25582 let mut highlights = Vec::new();
25583
25584 let insertion_highlight_style = HighlightStyle {
25585 color: Some(cx.theme().colors().text),
25586 ..Default::default()
25587 };
25588
25589 for (_, edit_text) in edits {
25590 let start_offset = text.len();
25591 text.push_str(edit_text);
25592 let end_offset = text.len();
25593
25594 if start_offset < end_offset {
25595 highlights.push((start_offset..end_offset, insertion_highlight_style));
25596 }
25597 }
25598
25599 HighlightedText {
25600 text: text.into(),
25601 highlights,
25602 }
25603}
25604
25605pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25606 match severity {
25607 lsp::DiagnosticSeverity::ERROR => colors.error,
25608 lsp::DiagnosticSeverity::WARNING => colors.warning,
25609 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25610 lsp::DiagnosticSeverity::HINT => colors.info,
25611 _ => colors.ignored,
25612 }
25613}
25614
25615pub fn styled_runs_for_code_label<'a>(
25616 label: &'a CodeLabel,
25617 syntax_theme: &'a theme::SyntaxTheme,
25618 local_player: &'a theme::PlayerColor,
25619) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25620 let fade_out = HighlightStyle {
25621 fade_out: Some(0.35),
25622 ..Default::default()
25623 };
25624
25625 let mut prev_end = label.filter_range.end;
25626 label
25627 .runs
25628 .iter()
25629 .enumerate()
25630 .flat_map(move |(ix, (range, highlight_id))| {
25631 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25632 HighlightStyle {
25633 color: Some(local_player.cursor),
25634 ..Default::default()
25635 }
25636 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25637 HighlightStyle {
25638 background_color: Some(local_player.selection),
25639 ..Default::default()
25640 }
25641 } else if let Some(style) = highlight_id.style(syntax_theme) {
25642 style
25643 } else {
25644 return Default::default();
25645 };
25646 let muted_style = style.highlight(fade_out);
25647
25648 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25649 if range.start >= label.filter_range.end {
25650 if range.start > prev_end {
25651 runs.push((prev_end..range.start, fade_out));
25652 }
25653 runs.push((range.clone(), muted_style));
25654 } else if range.end <= label.filter_range.end {
25655 runs.push((range.clone(), style));
25656 } else {
25657 runs.push((range.start..label.filter_range.end, style));
25658 runs.push((label.filter_range.end..range.end, muted_style));
25659 }
25660 prev_end = cmp::max(prev_end, range.end);
25661
25662 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25663 runs.push((prev_end..label.text.len(), fade_out));
25664 }
25665
25666 runs
25667 })
25668}
25669
25670pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25671 let mut prev_index = 0;
25672 let mut prev_codepoint: Option<char> = None;
25673 text.char_indices()
25674 .chain([(text.len(), '\0')])
25675 .filter_map(move |(index, codepoint)| {
25676 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25677 let is_boundary = index == text.len()
25678 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25679 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25680 if is_boundary {
25681 let chunk = &text[prev_index..index];
25682 prev_index = index;
25683 Some(chunk)
25684 } else {
25685 None
25686 }
25687 })
25688}
25689
25690/// Given a string of text immediately before the cursor, iterates over possible
25691/// strings a snippet could match to. More precisely: returns an iterator over
25692/// suffixes of `text` created by splitting at word boundaries (before & after
25693/// every non-word character).
25694///
25695/// Shorter suffixes are returned first.
25696pub(crate) fn snippet_candidate_suffixes(
25697 text: &str,
25698 is_word_char: impl Fn(char) -> bool,
25699) -> impl std::iter::Iterator<Item = &str> {
25700 let mut prev_index = text.len();
25701 let mut prev_codepoint = None;
25702 text.char_indices()
25703 .rev()
25704 .chain([(0, '\0')])
25705 .filter_map(move |(index, codepoint)| {
25706 let prev_index = std::mem::replace(&mut prev_index, index);
25707 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25708 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25709 None
25710 } else {
25711 let chunk = &text[prev_index..]; // go to end of string
25712 Some(chunk)
25713 }
25714 })
25715}
25716
25717pub trait RangeToAnchorExt: Sized {
25718 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25719
25720 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25721 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25722 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25723 }
25724}
25725
25726impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25727 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25728 let start_offset = self.start.to_offset(snapshot);
25729 let end_offset = self.end.to_offset(snapshot);
25730 if start_offset == end_offset {
25731 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25732 } else {
25733 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25734 }
25735 }
25736}
25737
25738pub trait RowExt {
25739 fn as_f64(&self) -> f64;
25740
25741 fn next_row(&self) -> Self;
25742
25743 fn previous_row(&self) -> Self;
25744
25745 fn minus(&self, other: Self) -> u32;
25746}
25747
25748impl RowExt for DisplayRow {
25749 fn as_f64(&self) -> f64 {
25750 self.0 as _
25751 }
25752
25753 fn next_row(&self) -> Self {
25754 Self(self.0 + 1)
25755 }
25756
25757 fn previous_row(&self) -> Self {
25758 Self(self.0.saturating_sub(1))
25759 }
25760
25761 fn minus(&self, other: Self) -> u32 {
25762 self.0 - other.0
25763 }
25764}
25765
25766impl RowExt for MultiBufferRow {
25767 fn as_f64(&self) -> f64 {
25768 self.0 as _
25769 }
25770
25771 fn next_row(&self) -> Self {
25772 Self(self.0 + 1)
25773 }
25774
25775 fn previous_row(&self) -> Self {
25776 Self(self.0.saturating_sub(1))
25777 }
25778
25779 fn minus(&self, other: Self) -> u32 {
25780 self.0 - other.0
25781 }
25782}
25783
25784trait RowRangeExt {
25785 type Row;
25786
25787 fn len(&self) -> usize;
25788
25789 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25790}
25791
25792impl RowRangeExt for Range<MultiBufferRow> {
25793 type Row = MultiBufferRow;
25794
25795 fn len(&self) -> usize {
25796 (self.end.0 - self.start.0) as usize
25797 }
25798
25799 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25800 (self.start.0..self.end.0).map(MultiBufferRow)
25801 }
25802}
25803
25804impl RowRangeExt for Range<DisplayRow> {
25805 type Row = DisplayRow;
25806
25807 fn len(&self) -> usize {
25808 (self.end.0 - self.start.0) as usize
25809 }
25810
25811 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25812 (self.start.0..self.end.0).map(DisplayRow)
25813 }
25814}
25815
25816/// If select range has more than one line, we
25817/// just point the cursor to range.start.
25818fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25819 if range.start.row == range.end.row {
25820 range
25821 } else {
25822 range.start..range.start
25823 }
25824}
25825pub struct KillRing(ClipboardItem);
25826impl Global for KillRing {}
25827
25828const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25829
25830enum BreakpointPromptEditAction {
25831 Log,
25832 Condition,
25833 HitCondition,
25834}
25835
25836struct BreakpointPromptEditor {
25837 pub(crate) prompt: Entity<Editor>,
25838 editor: WeakEntity<Editor>,
25839 breakpoint_anchor: Anchor,
25840 breakpoint: Breakpoint,
25841 edit_action: BreakpointPromptEditAction,
25842 block_ids: HashSet<CustomBlockId>,
25843 editor_margins: Arc<Mutex<EditorMargins>>,
25844 _subscriptions: Vec<Subscription>,
25845}
25846
25847impl BreakpointPromptEditor {
25848 const MAX_LINES: u8 = 4;
25849
25850 fn new(
25851 editor: WeakEntity<Editor>,
25852 breakpoint_anchor: Anchor,
25853 breakpoint: Breakpoint,
25854 edit_action: BreakpointPromptEditAction,
25855 window: &mut Window,
25856 cx: &mut Context<Self>,
25857 ) -> Self {
25858 let base_text = match edit_action {
25859 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25860 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25861 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25862 }
25863 .map(|msg| msg.to_string())
25864 .unwrap_or_default();
25865
25866 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25867 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25868
25869 let prompt = cx.new(|cx| {
25870 let mut prompt = Editor::new(
25871 EditorMode::AutoHeight {
25872 min_lines: 1,
25873 max_lines: Some(Self::MAX_LINES as usize),
25874 },
25875 buffer,
25876 None,
25877 window,
25878 cx,
25879 );
25880 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25881 prompt.set_show_cursor_when_unfocused(false, cx);
25882 prompt.set_placeholder_text(
25883 match edit_action {
25884 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25885 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25886 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25887 },
25888 window,
25889 cx,
25890 );
25891
25892 prompt
25893 });
25894
25895 Self {
25896 prompt,
25897 editor,
25898 breakpoint_anchor,
25899 breakpoint,
25900 edit_action,
25901 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25902 block_ids: Default::default(),
25903 _subscriptions: vec![],
25904 }
25905 }
25906
25907 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25908 self.block_ids.extend(block_ids)
25909 }
25910
25911 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25912 if let Some(editor) = self.editor.upgrade() {
25913 let message = self
25914 .prompt
25915 .read(cx)
25916 .buffer
25917 .read(cx)
25918 .as_singleton()
25919 .expect("A multi buffer in breakpoint prompt isn't possible")
25920 .read(cx)
25921 .as_rope()
25922 .to_string();
25923
25924 editor.update(cx, |editor, cx| {
25925 editor.edit_breakpoint_at_anchor(
25926 self.breakpoint_anchor,
25927 self.breakpoint.clone(),
25928 match self.edit_action {
25929 BreakpointPromptEditAction::Log => {
25930 BreakpointEditAction::EditLogMessage(message.into())
25931 }
25932 BreakpointPromptEditAction::Condition => {
25933 BreakpointEditAction::EditCondition(message.into())
25934 }
25935 BreakpointPromptEditAction::HitCondition => {
25936 BreakpointEditAction::EditHitCondition(message.into())
25937 }
25938 },
25939 cx,
25940 );
25941
25942 editor.remove_blocks(self.block_ids.clone(), None, cx);
25943 cx.focus_self(window);
25944 });
25945 }
25946 }
25947
25948 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25949 self.editor
25950 .update(cx, |editor, cx| {
25951 editor.remove_blocks(self.block_ids.clone(), None, cx);
25952 window.focus(&editor.focus_handle, cx);
25953 })
25954 .log_err();
25955 }
25956
25957 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25958 let settings = ThemeSettings::get_global(cx);
25959 let text_style = TextStyle {
25960 color: if self.prompt.read(cx).read_only(cx) {
25961 cx.theme().colors().text_disabled
25962 } else {
25963 cx.theme().colors().text
25964 },
25965 font_family: settings.buffer_font.family.clone(),
25966 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25967 font_size: settings.buffer_font_size(cx).into(),
25968 font_weight: settings.buffer_font.weight,
25969 line_height: relative(settings.buffer_line_height.value()),
25970 ..Default::default()
25971 };
25972 EditorElement::new(
25973 &self.prompt,
25974 EditorStyle {
25975 background: cx.theme().colors().editor_background,
25976 local_player: cx.theme().players().local(),
25977 text: text_style,
25978 ..Default::default()
25979 },
25980 )
25981 }
25982}
25983
25984impl Render for BreakpointPromptEditor {
25985 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25986 let editor_margins = *self.editor_margins.lock();
25987 let gutter_dimensions = editor_margins.gutter;
25988 h_flex()
25989 .key_context("Editor")
25990 .bg(cx.theme().colors().editor_background)
25991 .border_y_1()
25992 .border_color(cx.theme().status().info_border)
25993 .size_full()
25994 .py(window.line_height() / 2.5)
25995 .on_action(cx.listener(Self::confirm))
25996 .on_action(cx.listener(Self::cancel))
25997 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25998 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25999 }
26000}
26001
26002impl Focusable for BreakpointPromptEditor {
26003 fn focus_handle(&self, cx: &App) -> FocusHandle {
26004 self.prompt.focus_handle(cx)
26005 }
26006}
26007
26008fn all_edits_insertions_or_deletions(
26009 edits: &Vec<(Range<Anchor>, Arc<str>)>,
26010 snapshot: &MultiBufferSnapshot,
26011) -> bool {
26012 let mut all_insertions = true;
26013 let mut all_deletions = true;
26014
26015 for (range, new_text) in edits.iter() {
26016 let range_is_empty = range.to_offset(snapshot).is_empty();
26017 let text_is_empty = new_text.is_empty();
26018
26019 if range_is_empty != text_is_empty {
26020 if range_is_empty {
26021 all_deletions = false;
26022 } else {
26023 all_insertions = false;
26024 }
26025 } else {
26026 return false;
26027 }
26028
26029 if !all_insertions && !all_deletions {
26030 return false;
26031 }
26032 }
26033 all_insertions || all_deletions
26034}
26035
26036struct MissingEditPredictionKeybindingTooltip;
26037
26038impl Render for MissingEditPredictionKeybindingTooltip {
26039 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26040 ui::tooltip_container(cx, |container, cx| {
26041 container
26042 .flex_shrink_0()
26043 .max_w_80()
26044 .min_h(rems_from_px(124.))
26045 .justify_between()
26046 .child(
26047 v_flex()
26048 .flex_1()
26049 .text_ui_sm(cx)
26050 .child(Label::new("Conflict with Accept Keybinding"))
26051 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26052 )
26053 .child(
26054 h_flex()
26055 .pb_1()
26056 .gap_1()
26057 .items_end()
26058 .w_full()
26059 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26060 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26061 }))
26062 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26063 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26064 })),
26065 )
26066 })
26067 }
26068}
26069
26070#[derive(Debug, Clone, Copy, PartialEq)]
26071pub struct LineHighlight {
26072 pub background: Background,
26073 pub border: Option<gpui::Hsla>,
26074 pub include_gutter: bool,
26075 pub type_id: Option<TypeId>,
26076}
26077
26078struct LineManipulationResult {
26079 pub new_text: String,
26080 pub line_count_before: usize,
26081 pub line_count_after: usize,
26082}
26083
26084fn render_diff_hunk_controls(
26085 row: u32,
26086 status: &DiffHunkStatus,
26087 hunk_range: Range<Anchor>,
26088 is_created_file: bool,
26089 line_height: Pixels,
26090 editor: &Entity<Editor>,
26091 _window: &mut Window,
26092 cx: &mut App,
26093) -> AnyElement {
26094 h_flex()
26095 .h(line_height)
26096 .mr_1()
26097 .gap_1()
26098 .px_0p5()
26099 .pb_1()
26100 .border_x_1()
26101 .border_b_1()
26102 .border_color(cx.theme().colors().border_variant)
26103 .rounded_b_lg()
26104 .bg(cx.theme().colors().editor_background)
26105 .gap_1()
26106 .block_mouse_except_scroll()
26107 .shadow_md()
26108 .child(if status.has_secondary_hunk() {
26109 Button::new(("stage", row as u64), "Stage")
26110 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26111 .tooltip({
26112 let focus_handle = editor.focus_handle(cx);
26113 move |_window, cx| {
26114 Tooltip::for_action_in(
26115 "Stage Hunk",
26116 &::git::ToggleStaged,
26117 &focus_handle,
26118 cx,
26119 )
26120 }
26121 })
26122 .on_click({
26123 let editor = editor.clone();
26124 move |_event, _window, cx| {
26125 editor.update(cx, |editor, cx| {
26126 editor.stage_or_unstage_diff_hunks(
26127 true,
26128 vec![hunk_range.start..hunk_range.start],
26129 cx,
26130 );
26131 });
26132 }
26133 })
26134 } else {
26135 Button::new(("unstage", row as u64), "Unstage")
26136 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26137 .tooltip({
26138 let focus_handle = editor.focus_handle(cx);
26139 move |_window, cx| {
26140 Tooltip::for_action_in(
26141 "Unstage Hunk",
26142 &::git::ToggleStaged,
26143 &focus_handle,
26144 cx,
26145 )
26146 }
26147 })
26148 .on_click({
26149 let editor = editor.clone();
26150 move |_event, _window, cx| {
26151 editor.update(cx, |editor, cx| {
26152 editor.stage_or_unstage_diff_hunks(
26153 false,
26154 vec![hunk_range.start..hunk_range.start],
26155 cx,
26156 );
26157 });
26158 }
26159 })
26160 })
26161 .child(
26162 Button::new(("restore", row as u64), "Restore")
26163 .tooltip({
26164 let focus_handle = editor.focus_handle(cx);
26165 move |_window, cx| {
26166 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26167 }
26168 })
26169 .on_click({
26170 let editor = editor.clone();
26171 move |_event, window, cx| {
26172 editor.update(cx, |editor, cx| {
26173 let snapshot = editor.snapshot(window, cx);
26174 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26175 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26176 });
26177 }
26178 })
26179 .disabled(is_created_file),
26180 )
26181 .when(
26182 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26183 |el| {
26184 el.child(
26185 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26186 .shape(IconButtonShape::Square)
26187 .icon_size(IconSize::Small)
26188 // .disabled(!has_multiple_hunks)
26189 .tooltip({
26190 let focus_handle = editor.focus_handle(cx);
26191 move |_window, cx| {
26192 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26193 }
26194 })
26195 .on_click({
26196 let editor = editor.clone();
26197 move |_event, window, cx| {
26198 editor.update(cx, |editor, cx| {
26199 let snapshot = editor.snapshot(window, cx);
26200 let position =
26201 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26202 editor.go_to_hunk_before_or_after_position(
26203 &snapshot,
26204 position,
26205 Direction::Next,
26206 window,
26207 cx,
26208 );
26209 editor.expand_selected_diff_hunks(cx);
26210 });
26211 }
26212 }),
26213 )
26214 .child(
26215 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26216 .shape(IconButtonShape::Square)
26217 .icon_size(IconSize::Small)
26218 // .disabled(!has_multiple_hunks)
26219 .tooltip({
26220 let focus_handle = editor.focus_handle(cx);
26221 move |_window, cx| {
26222 Tooltip::for_action_in(
26223 "Previous Hunk",
26224 &GoToPreviousHunk,
26225 &focus_handle,
26226 cx,
26227 )
26228 }
26229 })
26230 .on_click({
26231 let editor = editor.clone();
26232 move |_event, window, cx| {
26233 editor.update(cx, |editor, cx| {
26234 let snapshot = editor.snapshot(window, cx);
26235 let point =
26236 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26237 editor.go_to_hunk_before_or_after_position(
26238 &snapshot,
26239 point,
26240 Direction::Prev,
26241 window,
26242 cx,
26243 );
26244 editor.expand_selected_diff_hunks(cx);
26245 });
26246 }
26247 }),
26248 )
26249 },
26250 )
26251 .into_any_element()
26252}
26253
26254pub fn multibuffer_context_lines(cx: &App) -> u32 {
26255 EditorSettings::try_get(cx)
26256 .map(|settings| settings.excerpt_context_lines)
26257 .unwrap_or(2)
26258 .min(32)
26259}