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;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod rust_analyzer_ext;
36pub mod scroll;
37mod selections_collection;
38pub mod tasks;
39
40#[cfg(test)]
41mod code_completion_tests;
42#[cfg(test)]
43mod edit_prediction_tests;
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
52pub use edit_prediction::Direction;
53pub use editor_settings::{
54 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
55 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
56};
57pub use element::{
58 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
59};
60pub use git::blame::BlameRenderer;
61pub use hover_popover::hover_markdown_style;
62pub use inlays::Inlay;
63pub use items::MAX_TAB_TITLE_LEN;
64pub use lsp::CompletionContext;
65pub use lsp_ext::lsp_tasks;
66pub use multi_buffer::{
67 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
68 RowInfo, ToOffset, ToPoint,
69};
70pub use text::Bias;
71
72use ::git::{
73 Restore,
74 blame::{BlameEntry, ParsedCommitMessage},
75 status::FileStatus,
76};
77use aho_corasick::AhoCorasick;
78use anyhow::{Context as _, Result, anyhow};
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::{EditPredictionProvider, EditPredictionProviderHandle};
92use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
93use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
94use futures::{
95 FutureExt, StreamExt as _,
96 future::{self, Shared, join},
97 stream::FuturesUnordered,
98};
99use fuzzy::{StringMatch, StringMatchCandidate};
100use git::blame::{GitBlame, GlobalBlameRenderer};
101use gpui::{
102 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
103 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
104 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
105 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
106 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
107 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
108 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
109 div, point, prelude::*, pulsating_between, px, relative, size,
110};
111use highlight_matching_bracket::refresh_matching_bracket_highlights;
112use hover_links::{HoverLink, HoveredLinkState, find_file};
113use hover_popover::{HoverState, hide_hover};
114use indent_guides::ActiveIndentGuidesState;
115use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
116use itertools::{Either, Itertools};
117use language::{
118 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
119 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
120 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
121 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
122 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
123 language_settings::{
124 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
125 language_settings,
126 },
127 point_from_lsp, point_to_lsp, text_diff_with_options,
128};
129use linked_editing_ranges::refresh_linked_ranges;
130use lsp::{
131 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
132 LanguageServerId,
133};
134use lsp_colors::LspColorData;
135use markdown::Markdown;
136use mouse_context_menu::MouseContextMenu;
137use movement::TextLayoutDetails;
138use multi_buffer::{
139 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
140};
141use parking_lot::Mutex;
142use persistence::DB;
143use project::{
144 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
145 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
146 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
147 ProjectPath, ProjectTransaction, TaskSourceKind,
148 debugger::{
149 breakpoint_store::{
150 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
151 BreakpointStore, BreakpointStoreEvent,
152 },
153 session::{Session, SessionEvent},
154 },
155 git_store::GitStoreEvent,
156 lsp_store::{
157 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
158 OpenLspBufferHandle,
159 },
160 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
161};
162use rand::seq::SliceRandom;
163use rpc::{ErrorCode, ErrorExt, proto::PeerId};
164use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
165use selections_collection::{
166 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
167};
168use serde::{Deserialize, Serialize};
169use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
170use smallvec::{SmallVec, smallvec};
171use snippet::Snippet;
172use std::{
173 any::{Any, TypeId},
174 borrow::Cow,
175 cell::{OnceCell, RefCell},
176 cmp::{self, Ordering, Reverse},
177 iter::{self, Peekable},
178 mem,
179 num::NonZeroU32,
180 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
181 path::{Path, PathBuf},
182 rc::Rc,
183 sync::Arc,
184 time::{Duration, Instant},
185};
186use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
187use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
188use theme::{
189 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
190 observe_buffer_font_size_adjustment,
191};
192use ui::{
193 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
194 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
195};
196use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
197use workspace::{
198 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
199 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
200 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
201 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
202 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
203 searchable::SearchEvent,
204};
205
206use crate::{
207 code_context_menus::CompletionsMenuSource,
208 editor_settings::MultiCursorModifier,
209 hover_links::{find_url, find_url_from_range},
210 inlays::{
211 InlineValueCache,
212 inlay_hints::{LspInlayHintData, inlay_hint_settings},
213 },
214 scroll::{ScrollOffset, ScrollPixelOffset},
215 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
216};
217
218pub const FILE_HEADER_HEIGHT: u32 = 2;
219pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
220const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
221const MAX_LINE_LEN: usize = 1024;
222const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
223const MAX_SELECTION_HISTORY_LEN: usize = 1024;
224pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
225#[doc(hidden)]
226pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
227pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
228
229pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
232pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
233
234pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
235pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
236pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
237
238pub type RenderDiffHunkControlsFn = Arc<
239 dyn Fn(
240 u32,
241 &DiffHunkStatus,
242 Range<Anchor>,
243 bool,
244 Pixels,
245 &Entity<Editor>,
246 &mut Window,
247 &mut App,
248 ) -> AnyElement,
249>;
250
251enum ReportEditorEvent {
252 Saved { auto_saved: bool },
253 EditorOpened,
254 Closed,
255}
256
257impl ReportEditorEvent {
258 pub fn event_type(&self) -> &'static str {
259 match self {
260 Self::Saved { .. } => "Editor Saved",
261 Self::EditorOpened => "Editor Opened",
262 Self::Closed => "Editor Closed",
263 }
264 }
265}
266
267pub enum ActiveDebugLine {}
268pub enum DebugStackFrameLine {}
269enum DocumentHighlightRead {}
270enum DocumentHighlightWrite {}
271enum InputComposition {}
272pub enum PendingInput {}
273enum SelectedTextHighlight {}
274
275pub enum ConflictsOuter {}
276pub enum ConflictsOurs {}
277pub enum ConflictsTheirs {}
278pub enum ConflictsOursMarker {}
279pub enum ConflictsTheirsMarker {}
280
281#[derive(Debug, Copy, Clone, PartialEq, Eq)]
282pub enum Navigated {
283 Yes,
284 No,
285}
286
287impl Navigated {
288 pub fn from_bool(yes: bool) -> Navigated {
289 if yes { Navigated::Yes } else { Navigated::No }
290 }
291}
292
293#[derive(Debug, Clone, PartialEq, Eq)]
294enum DisplayDiffHunk {
295 Folded {
296 display_row: DisplayRow,
297 },
298 Unfolded {
299 is_created_file: bool,
300 diff_base_byte_range: Range<usize>,
301 display_row_range: Range<DisplayRow>,
302 multi_buffer_range: Range<Anchor>,
303 status: DiffHunkStatus,
304 },
305}
306
307pub enum HideMouseCursorOrigin {
308 TypingAction,
309 MovementAction,
310}
311
312pub fn init_settings(cx: &mut App) {
313 EditorSettings::register(cx);
314}
315
316pub fn init(cx: &mut App) {
317 init_settings(cx);
318
319 cx.set_global(GlobalBlameRenderer(Arc::new(())));
320
321 workspace::register_project_item::<Editor>(cx);
322 workspace::FollowableViewRegistry::register::<Editor>(cx);
323 workspace::register_serializable_item::<Editor>(cx);
324
325 cx.observe_new(
326 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
327 workspace.register_action(Editor::new_file);
328 workspace.register_action(Editor::new_file_split);
329 workspace.register_action(Editor::new_file_vertical);
330 workspace.register_action(Editor::new_file_horizontal);
331 workspace.register_action(Editor::cancel_language_server_work);
332 workspace.register_action(Editor::toggle_focus);
333 },
334 )
335 .detach();
336
337 cx.on_action(move |_: &workspace::NewFile, cx| {
338 let app_state = workspace::AppState::global(cx);
339 if let Some(app_state) = app_state.upgrade() {
340 workspace::open_new(
341 Default::default(),
342 app_state,
343 cx,
344 |workspace, window, cx| {
345 Editor::new_file(workspace, &Default::default(), window, cx)
346 },
347 )
348 .detach();
349 }
350 });
351 cx.on_action(move |_: &workspace::NewWindow, cx| {
352 let app_state = workspace::AppState::global(cx);
353 if let Some(app_state) = app_state.upgrade() {
354 workspace::open_new(
355 Default::default(),
356 app_state,
357 cx,
358 |workspace, window, cx| {
359 cx.activate(true);
360 Editor::new_file(workspace, &Default::default(), window, cx)
361 },
362 )
363 .detach();
364 }
365 });
366}
367
368pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
369 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
370}
371
372pub trait DiagnosticRenderer {
373 fn render_group(
374 &self,
375 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
376 buffer_id: BufferId,
377 snapshot: EditorSnapshot,
378 editor: WeakEntity<Editor>,
379 cx: &mut App,
380 ) -> Vec<BlockProperties<Anchor>>;
381
382 fn render_hover(
383 &self,
384 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
385 range: Range<Point>,
386 buffer_id: BufferId,
387 cx: &mut App,
388 ) -> Option<Entity<markdown::Markdown>>;
389
390 fn open_link(
391 &self,
392 editor: &mut Editor,
393 link: SharedString,
394 window: &mut Window,
395 cx: &mut Context<Editor>,
396 );
397}
398
399pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
400
401impl GlobalDiagnosticRenderer {
402 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
403 cx.try_global::<Self>().map(|g| g.0.clone())
404 }
405}
406
407impl gpui::Global for GlobalDiagnosticRenderer {}
408pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
409 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
410}
411
412pub struct SearchWithinRange;
413
414trait InvalidationRegion {
415 fn ranges(&self) -> &[Range<Anchor>];
416}
417
418#[derive(Clone, Debug, PartialEq)]
419pub enum SelectPhase {
420 Begin {
421 position: DisplayPoint,
422 add: bool,
423 click_count: usize,
424 },
425 BeginColumnar {
426 position: DisplayPoint,
427 reset: bool,
428 mode: ColumnarMode,
429 goal_column: u32,
430 },
431 Extend {
432 position: DisplayPoint,
433 click_count: usize,
434 },
435 Update {
436 position: DisplayPoint,
437 goal_column: u32,
438 scroll_delta: gpui::Point<f32>,
439 },
440 End,
441}
442
443#[derive(Clone, Debug, PartialEq)]
444pub enum ColumnarMode {
445 FromMouse,
446 FromSelection,
447}
448
449#[derive(Clone, Debug)]
450pub enum SelectMode {
451 Character,
452 Word(Range<Anchor>),
453 Line(Range<Anchor>),
454 All,
455}
456
457#[derive(Clone, PartialEq, Eq, Debug)]
458pub enum EditorMode {
459 SingleLine,
460 AutoHeight {
461 min_lines: usize,
462 max_lines: Option<usize>,
463 },
464 Full {
465 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
466 scale_ui_elements_with_buffer_font_size: bool,
467 /// When set to `true`, the editor will render a background for the active line.
468 show_active_line_background: bool,
469 /// When set to `true`, the editor's height will be determined by its content.
470 sized_by_content: bool,
471 },
472 Minimap {
473 parent: WeakEntity<Editor>,
474 },
475}
476
477impl EditorMode {
478 pub fn full() -> Self {
479 Self::Full {
480 scale_ui_elements_with_buffer_font_size: true,
481 show_active_line_background: true,
482 sized_by_content: false,
483 }
484 }
485
486 #[inline]
487 pub fn is_full(&self) -> bool {
488 matches!(self, Self::Full { .. })
489 }
490
491 #[inline]
492 pub fn is_single_line(&self) -> bool {
493 matches!(self, Self::SingleLine { .. })
494 }
495
496 #[inline]
497 fn is_minimap(&self) -> bool {
498 matches!(self, Self::Minimap { .. })
499 }
500}
501
502#[derive(Copy, Clone, Debug)]
503pub enum SoftWrap {
504 /// Prefer not to wrap at all.
505 ///
506 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
507 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
508 GitDiff,
509 /// Prefer a single line generally, unless an overly long line is encountered.
510 None,
511 /// Soft wrap lines that exceed the editor width.
512 EditorWidth,
513 /// Soft wrap lines at the preferred line length.
514 Column(u32),
515 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
516 Bounded(u32),
517}
518
519#[derive(Clone)]
520pub struct EditorStyle {
521 pub background: Hsla,
522 pub border: Hsla,
523 pub local_player: PlayerColor,
524 pub text: TextStyle,
525 pub scrollbar_width: Pixels,
526 pub syntax: Arc<SyntaxTheme>,
527 pub status: StatusColors,
528 pub inlay_hints_style: HighlightStyle,
529 pub edit_prediction_styles: EditPredictionStyles,
530 pub unnecessary_code_fade: f32,
531 pub show_underlines: bool,
532}
533
534impl Default for EditorStyle {
535 fn default() -> Self {
536 Self {
537 background: Hsla::default(),
538 border: Hsla::default(),
539 local_player: PlayerColor::default(),
540 text: TextStyle::default(),
541 scrollbar_width: Pixels::default(),
542 syntax: Default::default(),
543 // HACK: Status colors don't have a real default.
544 // We should look into removing the status colors from the editor
545 // style and retrieve them directly from the theme.
546 status: StatusColors::dark(),
547 inlay_hints_style: HighlightStyle::default(),
548 edit_prediction_styles: EditPredictionStyles {
549 insertion: HighlightStyle::default(),
550 whitespace: HighlightStyle::default(),
551 },
552 unnecessary_code_fade: Default::default(),
553 show_underlines: true,
554 }
555 }
556}
557
558pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
559 let show_background = language_settings::language_settings(None, None, cx)
560 .inlay_hints
561 .show_background;
562
563 let mut style = cx.theme().syntax().get("hint");
564
565 if style.color.is_none() {
566 style.color = Some(cx.theme().status().hint);
567 }
568
569 if !show_background {
570 style.background_color = None;
571 return style;
572 }
573
574 if style.background_color.is_none() {
575 style.background_color = Some(cx.theme().status().hint_background);
576 }
577
578 style
579}
580
581pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
582 EditPredictionStyles {
583 insertion: HighlightStyle {
584 color: Some(cx.theme().status().predictive),
585 ..HighlightStyle::default()
586 },
587 whitespace: HighlightStyle {
588 background_color: Some(cx.theme().status().created_background),
589 ..HighlightStyle::default()
590 },
591 }
592}
593
594type CompletionId = usize;
595
596pub(crate) enum EditDisplayMode {
597 TabAccept,
598 DiffPopover,
599 Inline,
600}
601
602enum EditPrediction {
603 Edit {
604 edits: Vec<(Range<Anchor>, String)>,
605 edit_preview: Option<EditPreview>,
606 display_mode: EditDisplayMode,
607 snapshot: BufferSnapshot,
608 },
609 /// Move to a specific location in the active editor
610 MoveWithin {
611 target: Anchor,
612 snapshot: BufferSnapshot,
613 },
614 /// Move to a specific location in a different editor (not the active one)
615 MoveOutside {
616 target: language::Anchor,
617 snapshot: BufferSnapshot,
618 },
619}
620
621struct EditPredictionState {
622 inlay_ids: Vec<InlayId>,
623 completion: EditPrediction,
624 completion_id: Option<SharedString>,
625 invalidation_range: Option<Range<Anchor>>,
626}
627
628enum EditPredictionSettings {
629 Disabled,
630 Enabled {
631 show_in_menu: bool,
632 preview_requires_modifier: bool,
633 },
634}
635
636enum EditPredictionHighlight {}
637
638#[derive(Debug, Clone)]
639struct InlineDiagnostic {
640 message: SharedString,
641 group_id: usize,
642 is_primary: bool,
643 start: Point,
644 severity: lsp::DiagnosticSeverity,
645}
646
647pub enum MenuEditPredictionsPolicy {
648 Never,
649 ByProvider,
650}
651
652pub enum EditPredictionPreview {
653 /// Modifier is not pressed
654 Inactive { released_too_fast: bool },
655 /// Modifier pressed
656 Active {
657 since: Instant,
658 previous_scroll_position: Option<ScrollAnchor>,
659 },
660}
661
662impl EditPredictionPreview {
663 pub fn released_too_fast(&self) -> bool {
664 match self {
665 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
666 EditPredictionPreview::Active { .. } => false,
667 }
668 }
669
670 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
671 if let EditPredictionPreview::Active {
672 previous_scroll_position,
673 ..
674 } = self
675 {
676 *previous_scroll_position = scroll_position;
677 }
678 }
679}
680
681pub struct ContextMenuOptions {
682 pub min_entries_visible: usize,
683 pub max_entries_visible: usize,
684 pub placement: Option<ContextMenuPlacement>,
685}
686
687#[derive(Debug, Clone, PartialEq, Eq)]
688pub enum ContextMenuPlacement {
689 Above,
690 Below,
691}
692
693#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
694struct EditorActionId(usize);
695
696impl EditorActionId {
697 pub fn post_inc(&mut self) -> Self {
698 let answer = self.0;
699
700 *self = Self(answer + 1);
701
702 Self(answer)
703 }
704}
705
706// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
707// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
708
709type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
710type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
711
712#[derive(Default)]
713struct ScrollbarMarkerState {
714 scrollbar_size: Size<Pixels>,
715 dirty: bool,
716 markers: Arc<[PaintQuad]>,
717 pending_refresh: Option<Task<Result<()>>>,
718}
719
720impl ScrollbarMarkerState {
721 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
722 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
723 }
724}
725
726#[derive(Clone, Copy, PartialEq, Eq)]
727pub enum MinimapVisibility {
728 Disabled,
729 Enabled {
730 /// The configuration currently present in the users settings.
731 setting_configuration: bool,
732 /// Whether to override the currently set visibility from the users setting.
733 toggle_override: bool,
734 },
735}
736
737impl MinimapVisibility {
738 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
739 if mode.is_full() {
740 Self::Enabled {
741 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
742 toggle_override: false,
743 }
744 } else {
745 Self::Disabled
746 }
747 }
748
749 fn hidden(&self) -> Self {
750 match *self {
751 Self::Enabled {
752 setting_configuration,
753 ..
754 } => Self::Enabled {
755 setting_configuration,
756 toggle_override: setting_configuration,
757 },
758 Self::Disabled => Self::Disabled,
759 }
760 }
761
762 fn disabled(&self) -> bool {
763 matches!(*self, Self::Disabled)
764 }
765
766 fn settings_visibility(&self) -> bool {
767 match *self {
768 Self::Enabled {
769 setting_configuration,
770 ..
771 } => setting_configuration,
772 _ => false,
773 }
774 }
775
776 fn visible(&self) -> bool {
777 match *self {
778 Self::Enabled {
779 setting_configuration,
780 toggle_override,
781 } => setting_configuration ^ toggle_override,
782 _ => false,
783 }
784 }
785
786 fn toggle_visibility(&self) -> Self {
787 match *self {
788 Self::Enabled {
789 toggle_override,
790 setting_configuration,
791 } => Self::Enabled {
792 setting_configuration,
793 toggle_override: !toggle_override,
794 },
795 Self::Disabled => Self::Disabled,
796 }
797 }
798}
799
800#[derive(Clone, Debug)]
801struct RunnableTasks {
802 templates: Vec<(TaskSourceKind, TaskTemplate)>,
803 offset: multi_buffer::Anchor,
804 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
805 column: u32,
806 // Values of all named captures, including those starting with '_'
807 extra_variables: HashMap<String, String>,
808 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
809 context_range: Range<BufferOffset>,
810}
811
812impl RunnableTasks {
813 fn resolve<'a>(
814 &'a self,
815 cx: &'a task::TaskContext,
816 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
817 self.templates.iter().filter_map(|(kind, template)| {
818 template
819 .resolve_task(&kind.to_id_base(), cx)
820 .map(|task| (kind.clone(), task))
821 })
822 }
823}
824
825#[derive(Clone)]
826pub struct ResolvedTasks {
827 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
828 position: Anchor,
829}
830
831#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
832struct BufferOffset(usize);
833
834/// Addons allow storing per-editor state in other crates (e.g. Vim)
835pub trait Addon: 'static {
836 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
837
838 fn render_buffer_header_controls(
839 &self,
840 _: &ExcerptInfo,
841 _: &Window,
842 _: &App,
843 ) -> Option<AnyElement> {
844 None
845 }
846
847 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
848 None
849 }
850
851 fn to_any(&self) -> &dyn std::any::Any;
852
853 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
854 None
855 }
856}
857
858struct ChangeLocation {
859 current: Option<Vec<Anchor>>,
860 original: Vec<Anchor>,
861}
862impl ChangeLocation {
863 fn locations(&self) -> &[Anchor] {
864 self.current.as_ref().unwrap_or(&self.original)
865 }
866}
867
868/// A set of caret positions, registered when the editor was edited.
869pub struct ChangeList {
870 changes: Vec<ChangeLocation>,
871 /// Currently "selected" change.
872 position: Option<usize>,
873}
874
875impl ChangeList {
876 pub fn new() -> Self {
877 Self {
878 changes: Vec::new(),
879 position: None,
880 }
881 }
882
883 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
884 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
885 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
886 if self.changes.is_empty() {
887 return None;
888 }
889
890 let prev = self.position.unwrap_or(self.changes.len());
891 let next = if direction == Direction::Prev {
892 prev.saturating_sub(count)
893 } else {
894 (prev + count).min(self.changes.len() - 1)
895 };
896 self.position = Some(next);
897 self.changes.get(next).map(|change| change.locations())
898 }
899
900 /// Adds a new change to the list, resetting the change list position.
901 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
902 self.position.take();
903 if let Some(last) = self.changes.last_mut()
904 && group
905 {
906 last.current = Some(new_positions)
907 } else {
908 self.changes.push(ChangeLocation {
909 original: new_positions,
910 current: None,
911 });
912 }
913 }
914
915 pub fn last(&self) -> Option<&[Anchor]> {
916 self.changes.last().map(|change| change.locations())
917 }
918
919 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
920 self.changes.last().map(|change| change.original.as_slice())
921 }
922
923 pub fn invert_last_group(&mut self) {
924 if let Some(last) = self.changes.last_mut()
925 && let Some(current) = last.current.as_mut()
926 {
927 mem::swap(&mut last.original, current);
928 }
929 }
930}
931
932#[derive(Clone)]
933struct InlineBlamePopoverState {
934 scroll_handle: ScrollHandle,
935 commit_message: Option<ParsedCommitMessage>,
936 markdown: Entity<Markdown>,
937}
938
939struct InlineBlamePopover {
940 position: gpui::Point<Pixels>,
941 hide_task: Option<Task<()>>,
942 popover_bounds: Option<Bounds<Pixels>>,
943 popover_state: InlineBlamePopoverState,
944 keyboard_grace: bool,
945}
946
947enum SelectionDragState {
948 /// State when no drag related activity is detected.
949 None,
950 /// State when the mouse is down on a selection that is about to be dragged.
951 ReadyToDrag {
952 selection: Selection<Anchor>,
953 click_position: gpui::Point<Pixels>,
954 mouse_down_time: Instant,
955 },
956 /// State when the mouse is dragging the selection in the editor.
957 Dragging {
958 selection: Selection<Anchor>,
959 drop_cursor: Selection<Anchor>,
960 hide_drop_cursor: bool,
961 },
962}
963
964enum ColumnarSelectionState {
965 FromMouse {
966 selection_tail: Anchor,
967 display_point: Option<DisplayPoint>,
968 },
969 FromSelection {
970 selection_tail: Anchor,
971 },
972}
973
974/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
975/// a breakpoint on them.
976#[derive(Clone, Copy, Debug, PartialEq, Eq)]
977struct PhantomBreakpointIndicator {
978 display_row: DisplayRow,
979 /// There's a small debounce between hovering over the line and showing the indicator.
980 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
981 is_active: bool,
982 collides_with_existing_breakpoint: bool,
983}
984
985/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
986///
987/// See the [module level documentation](self) for more information.
988pub struct Editor {
989 focus_handle: FocusHandle,
990 last_focused_descendant: Option<WeakFocusHandle>,
991 /// The text buffer being edited
992 buffer: Entity<MultiBuffer>,
993 /// Map of how text in the buffer should be displayed.
994 /// Handles soft wraps, folds, fake inlay text insertions, etc.
995 pub display_map: Entity<DisplayMap>,
996 placeholder_display_map: Option<Entity<DisplayMap>>,
997 pub selections: SelectionsCollection,
998 pub scroll_manager: ScrollManager,
999 /// When inline assist editors are linked, they all render cursors because
1000 /// typing enters text into each of them, even the ones that aren't focused.
1001 pub(crate) show_cursor_when_unfocused: bool,
1002 columnar_selection_state: Option<ColumnarSelectionState>,
1003 add_selections_state: Option<AddSelectionsState>,
1004 select_next_state: Option<SelectNextState>,
1005 select_prev_state: Option<SelectNextState>,
1006 selection_history: SelectionHistory,
1007 defer_selection_effects: bool,
1008 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1009 autoclose_regions: Vec<AutocloseRegion>,
1010 snippet_stack: InvalidationStack<SnippetState>,
1011 select_syntax_node_history: SelectSyntaxNodeHistory,
1012 ime_transaction: Option<TransactionId>,
1013 pub diagnostics_max_severity: DiagnosticSeverity,
1014 active_diagnostics: ActiveDiagnostic,
1015 show_inline_diagnostics: bool,
1016 inline_diagnostics_update: Task<()>,
1017 inline_diagnostics_enabled: bool,
1018 diagnostics_enabled: bool,
1019 word_completions_enabled: bool,
1020 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1021 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1022 hard_wrap: Option<usize>,
1023 project: Option<Entity<Project>>,
1024 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1025 completion_provider: Option<Rc<dyn CompletionProvider>>,
1026 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1027 blink_manager: Entity<BlinkManager>,
1028 show_cursor_names: bool,
1029 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1030 pub show_local_selections: bool,
1031 mode: EditorMode,
1032 show_breadcrumbs: bool,
1033 show_gutter: bool,
1034 show_scrollbars: ScrollbarAxes,
1035 minimap_visibility: MinimapVisibility,
1036 offset_content: bool,
1037 disable_expand_excerpt_buttons: bool,
1038 show_line_numbers: Option<bool>,
1039 use_relative_line_numbers: Option<bool>,
1040 show_git_diff_gutter: Option<bool>,
1041 show_code_actions: Option<bool>,
1042 show_runnables: Option<bool>,
1043 show_breakpoints: Option<bool>,
1044 show_wrap_guides: Option<bool>,
1045 show_indent_guides: Option<bool>,
1046 highlight_order: usize,
1047 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1048 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1049 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1050 scrollbar_marker_state: ScrollbarMarkerState,
1051 active_indent_guides_state: ActiveIndentGuidesState,
1052 nav_history: Option<ItemNavHistory>,
1053 context_menu: RefCell<Option<CodeContextMenu>>,
1054 context_menu_options: Option<ContextMenuOptions>,
1055 mouse_context_menu: Option<MouseContextMenu>,
1056 completion_tasks: Vec<(CompletionId, Task<()>)>,
1057 inline_blame_popover: Option<InlineBlamePopover>,
1058 inline_blame_popover_show_task: Option<Task<()>>,
1059 signature_help_state: SignatureHelpState,
1060 auto_signature_help: Option<bool>,
1061 find_all_references_task_sources: Vec<Anchor>,
1062 next_completion_id: CompletionId,
1063 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1064 code_actions_task: Option<Task<Result<()>>>,
1065 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1066 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1067 document_highlights_task: Option<Task<()>>,
1068 linked_editing_range_task: Option<Task<Option<()>>>,
1069 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1070 pending_rename: Option<RenameState>,
1071 searchable: bool,
1072 cursor_shape: CursorShape,
1073 current_line_highlight: Option<CurrentLineHighlight>,
1074 collapse_matches: bool,
1075 autoindent_mode: Option<AutoindentMode>,
1076 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1077 input_enabled: bool,
1078 use_modal_editing: bool,
1079 read_only: bool,
1080 leader_id: Option<CollaboratorId>,
1081 remote_id: Option<ViewId>,
1082 pub hover_state: HoverState,
1083 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1084 gutter_hovered: bool,
1085 hovered_link_state: Option<HoveredLinkState>,
1086 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1087 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1088 active_edit_prediction: Option<EditPredictionState>,
1089 /// Used to prevent flickering as the user types while the menu is open
1090 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1091 edit_prediction_settings: EditPredictionSettings,
1092 edit_predictions_hidden_for_vim_mode: bool,
1093 show_edit_predictions_override: Option<bool>,
1094 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1095 edit_prediction_preview: EditPredictionPreview,
1096 edit_prediction_indent_conflict: bool,
1097 edit_prediction_requires_modifier_in_indent_conflict: bool,
1098 next_inlay_id: usize,
1099 next_color_inlay_id: usize,
1100 _subscriptions: Vec<Subscription>,
1101 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1102 gutter_dimensions: GutterDimensions,
1103 style: Option<EditorStyle>,
1104 text_style_refinement: Option<TextStyleRefinement>,
1105 next_editor_action_id: EditorActionId,
1106 editor_actions: Rc<
1107 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1108 >,
1109 use_autoclose: bool,
1110 use_auto_surround: bool,
1111 auto_replace_emoji_shortcode: bool,
1112 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1113 show_git_blame_gutter: bool,
1114 show_git_blame_inline: bool,
1115 show_git_blame_inline_delay_task: Option<Task<()>>,
1116 git_blame_inline_enabled: bool,
1117 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1118 serialize_dirty_buffers: bool,
1119 show_selection_menu: Option<bool>,
1120 blame: Option<Entity<GitBlame>>,
1121 blame_subscription: Option<Subscription>,
1122 custom_context_menu: Option<
1123 Box<
1124 dyn 'static
1125 + Fn(
1126 &mut Self,
1127 DisplayPoint,
1128 &mut Window,
1129 &mut Context<Self>,
1130 ) -> Option<Entity<ui::ContextMenu>>,
1131 >,
1132 >,
1133 last_bounds: Option<Bounds<Pixels>>,
1134 last_position_map: Option<Rc<PositionMap>>,
1135 expect_bounds_change: Option<Bounds<Pixels>>,
1136 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1137 tasks_update_task: Option<Task<()>>,
1138 breakpoint_store: Option<Entity<BreakpointStore>>,
1139 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1140 hovered_diff_hunk_row: Option<DisplayRow>,
1141 pull_diagnostics_task: Task<()>,
1142 in_project_search: bool,
1143 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1144 breadcrumb_header: Option<String>,
1145 focused_block: Option<FocusedBlock>,
1146 next_scroll_position: NextScrollCursorCenterTopBottom,
1147 addons: HashMap<TypeId, Box<dyn Addon>>,
1148 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1149 load_diff_task: Option<Shared<Task<()>>>,
1150 /// Whether we are temporarily displaying a diff other than git's
1151 temporary_diff_override: bool,
1152 selection_mark_mode: bool,
1153 toggle_fold_multiple_buffers: Task<()>,
1154 _scroll_cursor_center_top_bottom_task: Task<()>,
1155 serialize_selections: Task<()>,
1156 serialize_folds: Task<()>,
1157 mouse_cursor_hidden: bool,
1158 minimap: Option<Entity<Self>>,
1159 hide_mouse_mode: HideMouseMode,
1160 pub change_list: ChangeList,
1161 inline_value_cache: InlineValueCache,
1162 selection_drag_state: SelectionDragState,
1163 colors: Option<LspColorData>,
1164 post_scroll_update: Task<()>,
1165 refresh_colors_task: Task<()>,
1166 inlay_hints: Option<LspInlayHintData>,
1167 folding_newlines: Task<()>,
1168 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1169}
1170
1171fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1172 if debounce_ms > 0 {
1173 Some(Duration::from_millis(debounce_ms))
1174 } else {
1175 None
1176 }
1177}
1178
1179#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1180enum NextScrollCursorCenterTopBottom {
1181 #[default]
1182 Center,
1183 Top,
1184 Bottom,
1185}
1186
1187impl NextScrollCursorCenterTopBottom {
1188 fn next(&self) -> Self {
1189 match self {
1190 Self::Center => Self::Top,
1191 Self::Top => Self::Bottom,
1192 Self::Bottom => Self::Center,
1193 }
1194 }
1195}
1196
1197#[derive(Clone)]
1198pub struct EditorSnapshot {
1199 pub mode: EditorMode,
1200 show_gutter: bool,
1201 show_line_numbers: Option<bool>,
1202 show_git_diff_gutter: Option<bool>,
1203 show_code_actions: Option<bool>,
1204 show_runnables: Option<bool>,
1205 show_breakpoints: Option<bool>,
1206 git_blame_gutter_max_author_length: Option<usize>,
1207 pub display_snapshot: DisplaySnapshot,
1208 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1209 is_focused: bool,
1210 scroll_anchor: ScrollAnchor,
1211 ongoing_scroll: OngoingScroll,
1212 current_line_highlight: CurrentLineHighlight,
1213 gutter_hovered: bool,
1214}
1215
1216#[derive(Default, Debug, Clone, Copy)]
1217pub struct GutterDimensions {
1218 pub left_padding: Pixels,
1219 pub right_padding: Pixels,
1220 pub width: Pixels,
1221 pub margin: Pixels,
1222 pub git_blame_entries_width: Option<Pixels>,
1223}
1224
1225impl GutterDimensions {
1226 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1227 Self {
1228 margin: Self::default_gutter_margin(font_id, font_size, cx),
1229 ..Default::default()
1230 }
1231 }
1232
1233 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1234 -cx.text_system().descent(font_id, font_size)
1235 }
1236 /// The full width of the space taken up by the gutter.
1237 pub fn full_width(&self) -> Pixels {
1238 self.margin + self.width
1239 }
1240
1241 /// The width of the space reserved for the fold indicators,
1242 /// use alongside 'justify_end' and `gutter_width` to
1243 /// right align content with the line numbers
1244 pub fn fold_area_width(&self) -> Pixels {
1245 self.margin + self.right_padding
1246 }
1247}
1248
1249struct CharacterDimensions {
1250 em_width: Pixels,
1251 em_advance: Pixels,
1252 line_height: Pixels,
1253}
1254
1255#[derive(Debug)]
1256pub struct RemoteSelection {
1257 pub replica_id: ReplicaId,
1258 pub selection: Selection<Anchor>,
1259 pub cursor_shape: CursorShape,
1260 pub collaborator_id: CollaboratorId,
1261 pub line_mode: bool,
1262 pub user_name: Option<SharedString>,
1263 pub color: PlayerColor,
1264}
1265
1266#[derive(Clone, Debug)]
1267struct SelectionHistoryEntry {
1268 selections: Arc<[Selection<Anchor>]>,
1269 select_next_state: Option<SelectNextState>,
1270 select_prev_state: Option<SelectNextState>,
1271 add_selections_state: Option<AddSelectionsState>,
1272}
1273
1274#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1275enum SelectionHistoryMode {
1276 Normal,
1277 Undoing,
1278 Redoing,
1279 Skipping,
1280}
1281
1282#[derive(Clone, PartialEq, Eq, Hash)]
1283struct HoveredCursor {
1284 replica_id: ReplicaId,
1285 selection_id: usize,
1286}
1287
1288impl Default for SelectionHistoryMode {
1289 fn default() -> Self {
1290 Self::Normal
1291 }
1292}
1293
1294#[derive(Debug)]
1295/// SelectionEffects controls the side-effects of updating the selection.
1296///
1297/// The default behaviour does "what you mostly want":
1298/// - it pushes to the nav history if the cursor moved by >10 lines
1299/// - it re-triggers completion requests
1300/// - it scrolls to fit
1301///
1302/// You might want to modify these behaviours. For example when doing a "jump"
1303/// like go to definition, we always want to add to nav history; but when scrolling
1304/// in vim mode we never do.
1305///
1306/// Similarly, you might want to disable scrolling if you don't want the viewport to
1307/// move.
1308#[derive(Clone)]
1309pub struct SelectionEffects {
1310 nav_history: Option<bool>,
1311 completions: bool,
1312 scroll: Option<Autoscroll>,
1313}
1314
1315impl Default for SelectionEffects {
1316 fn default() -> Self {
1317 Self {
1318 nav_history: None,
1319 completions: true,
1320 scroll: Some(Autoscroll::fit()),
1321 }
1322 }
1323}
1324impl SelectionEffects {
1325 pub fn scroll(scroll: Autoscroll) -> Self {
1326 Self {
1327 scroll: Some(scroll),
1328 ..Default::default()
1329 }
1330 }
1331
1332 pub fn no_scroll() -> Self {
1333 Self {
1334 scroll: None,
1335 ..Default::default()
1336 }
1337 }
1338
1339 pub fn completions(self, completions: bool) -> Self {
1340 Self {
1341 completions,
1342 ..self
1343 }
1344 }
1345
1346 pub fn nav_history(self, nav_history: bool) -> Self {
1347 Self {
1348 nav_history: Some(nav_history),
1349 ..self
1350 }
1351 }
1352}
1353
1354struct DeferredSelectionEffectsState {
1355 changed: bool,
1356 effects: SelectionEffects,
1357 old_cursor_position: Anchor,
1358 history_entry: SelectionHistoryEntry,
1359}
1360
1361#[derive(Default)]
1362struct SelectionHistory {
1363 #[allow(clippy::type_complexity)]
1364 selections_by_transaction:
1365 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1366 mode: SelectionHistoryMode,
1367 undo_stack: VecDeque<SelectionHistoryEntry>,
1368 redo_stack: VecDeque<SelectionHistoryEntry>,
1369}
1370
1371impl SelectionHistory {
1372 #[track_caller]
1373 fn insert_transaction(
1374 &mut self,
1375 transaction_id: TransactionId,
1376 selections: Arc<[Selection<Anchor>]>,
1377 ) {
1378 if selections.is_empty() {
1379 log::error!(
1380 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1381 std::panic::Location::caller()
1382 );
1383 return;
1384 }
1385 self.selections_by_transaction
1386 .insert(transaction_id, (selections, None));
1387 }
1388
1389 #[allow(clippy::type_complexity)]
1390 fn transaction(
1391 &self,
1392 transaction_id: TransactionId,
1393 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1394 self.selections_by_transaction.get(&transaction_id)
1395 }
1396
1397 #[allow(clippy::type_complexity)]
1398 fn transaction_mut(
1399 &mut self,
1400 transaction_id: TransactionId,
1401 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1402 self.selections_by_transaction.get_mut(&transaction_id)
1403 }
1404
1405 fn push(&mut self, entry: SelectionHistoryEntry) {
1406 if !entry.selections.is_empty() {
1407 match self.mode {
1408 SelectionHistoryMode::Normal => {
1409 self.push_undo(entry);
1410 self.redo_stack.clear();
1411 }
1412 SelectionHistoryMode::Undoing => self.push_redo(entry),
1413 SelectionHistoryMode::Redoing => self.push_undo(entry),
1414 SelectionHistoryMode::Skipping => {}
1415 }
1416 }
1417 }
1418
1419 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1420 if self
1421 .undo_stack
1422 .back()
1423 .is_none_or(|e| e.selections != entry.selections)
1424 {
1425 self.undo_stack.push_back(entry);
1426 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1427 self.undo_stack.pop_front();
1428 }
1429 }
1430 }
1431
1432 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1433 if self
1434 .redo_stack
1435 .back()
1436 .is_none_or(|e| e.selections != entry.selections)
1437 {
1438 self.redo_stack.push_back(entry);
1439 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1440 self.redo_stack.pop_front();
1441 }
1442 }
1443 }
1444}
1445
1446#[derive(Clone, Copy)]
1447pub struct RowHighlightOptions {
1448 pub autoscroll: bool,
1449 pub include_gutter: bool,
1450}
1451
1452impl Default for RowHighlightOptions {
1453 fn default() -> Self {
1454 Self {
1455 autoscroll: Default::default(),
1456 include_gutter: true,
1457 }
1458 }
1459}
1460
1461struct RowHighlight {
1462 index: usize,
1463 range: Range<Anchor>,
1464 color: Hsla,
1465 options: RowHighlightOptions,
1466 type_id: TypeId,
1467}
1468
1469#[derive(Clone, Debug)]
1470struct AddSelectionsState {
1471 groups: Vec<AddSelectionsGroup>,
1472}
1473
1474#[derive(Clone, Debug)]
1475struct AddSelectionsGroup {
1476 above: bool,
1477 stack: Vec<usize>,
1478}
1479
1480#[derive(Clone)]
1481struct SelectNextState {
1482 query: AhoCorasick,
1483 wordwise: bool,
1484 done: bool,
1485}
1486
1487impl std::fmt::Debug for SelectNextState {
1488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1489 f.debug_struct(std::any::type_name::<Self>())
1490 .field("wordwise", &self.wordwise)
1491 .field("done", &self.done)
1492 .finish()
1493 }
1494}
1495
1496#[derive(Debug)]
1497struct AutocloseRegion {
1498 selection_id: usize,
1499 range: Range<Anchor>,
1500 pair: BracketPair,
1501}
1502
1503#[derive(Debug)]
1504struct SnippetState {
1505 ranges: Vec<Vec<Range<Anchor>>>,
1506 active_index: usize,
1507 choices: Vec<Option<Vec<String>>>,
1508}
1509
1510#[doc(hidden)]
1511pub struct RenameState {
1512 pub range: Range<Anchor>,
1513 pub old_name: Arc<str>,
1514 pub editor: Entity<Editor>,
1515 block_id: CustomBlockId,
1516}
1517
1518struct InvalidationStack<T>(Vec<T>);
1519
1520struct RegisteredEditPredictionProvider {
1521 provider: Arc<dyn EditPredictionProviderHandle>,
1522 _subscription: Subscription,
1523}
1524
1525#[derive(Debug, PartialEq, Eq)]
1526pub struct ActiveDiagnosticGroup {
1527 pub active_range: Range<Anchor>,
1528 pub active_message: String,
1529 pub group_id: usize,
1530 pub blocks: HashSet<CustomBlockId>,
1531}
1532
1533#[derive(Debug, PartialEq, Eq)]
1534
1535pub(crate) enum ActiveDiagnostic {
1536 None,
1537 All,
1538 Group(ActiveDiagnosticGroup),
1539}
1540
1541#[derive(Serialize, Deserialize, Clone, Debug)]
1542pub struct ClipboardSelection {
1543 /// The number of bytes in this selection.
1544 pub len: usize,
1545 /// Whether this was a full-line selection.
1546 pub is_entire_line: bool,
1547 /// The indentation of the first line when this content was originally copied.
1548 pub first_line_indent: u32,
1549}
1550
1551// selections, scroll behavior, was newest selection reversed
1552type SelectSyntaxNodeHistoryState = (
1553 Box<[Selection<usize>]>,
1554 SelectSyntaxNodeScrollBehavior,
1555 bool,
1556);
1557
1558#[derive(Default)]
1559struct SelectSyntaxNodeHistory {
1560 stack: Vec<SelectSyntaxNodeHistoryState>,
1561 // disable temporarily to allow changing selections without losing the stack
1562 pub disable_clearing: bool,
1563}
1564
1565impl SelectSyntaxNodeHistory {
1566 pub fn try_clear(&mut self) {
1567 if !self.disable_clearing {
1568 self.stack.clear();
1569 }
1570 }
1571
1572 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1573 self.stack.push(selection);
1574 }
1575
1576 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1577 self.stack.pop()
1578 }
1579}
1580
1581enum SelectSyntaxNodeScrollBehavior {
1582 CursorTop,
1583 FitSelection,
1584 CursorBottom,
1585}
1586
1587#[derive(Debug)]
1588pub(crate) struct NavigationData {
1589 cursor_anchor: Anchor,
1590 cursor_position: Point,
1591 scroll_anchor: ScrollAnchor,
1592 scroll_top_row: u32,
1593}
1594
1595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1596pub enum GotoDefinitionKind {
1597 Symbol,
1598 Declaration,
1599 Type,
1600 Implementation,
1601}
1602
1603pub enum FormatTarget {
1604 Buffers(HashSet<Entity<Buffer>>),
1605 Ranges(Vec<Range<MultiBufferPoint>>),
1606}
1607
1608pub(crate) struct FocusedBlock {
1609 id: BlockId,
1610 focus_handle: WeakFocusHandle,
1611}
1612
1613#[derive(Clone)]
1614enum JumpData {
1615 MultiBufferRow {
1616 row: MultiBufferRow,
1617 line_offset_from_top: u32,
1618 },
1619 MultiBufferPoint {
1620 excerpt_id: ExcerptId,
1621 position: Point,
1622 anchor: text::Anchor,
1623 line_offset_from_top: u32,
1624 },
1625}
1626
1627pub enum MultibufferSelectionMode {
1628 First,
1629 All,
1630}
1631
1632#[derive(Clone, Copy, Debug, Default)]
1633pub struct RewrapOptions {
1634 pub override_language_settings: bool,
1635 pub preserve_existing_whitespace: bool,
1636}
1637
1638impl Editor {
1639 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1640 let buffer = cx.new(|cx| Buffer::local("", cx));
1641 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1642 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1643 }
1644
1645 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1646 let buffer = cx.new(|cx| Buffer::local("", cx));
1647 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1648 Self::new(EditorMode::full(), buffer, None, window, cx)
1649 }
1650
1651 pub fn auto_height(
1652 min_lines: usize,
1653 max_lines: usize,
1654 window: &mut Window,
1655 cx: &mut Context<Self>,
1656 ) -> Self {
1657 let buffer = cx.new(|cx| Buffer::local("", cx));
1658 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1659 Self::new(
1660 EditorMode::AutoHeight {
1661 min_lines,
1662 max_lines: Some(max_lines),
1663 },
1664 buffer,
1665 None,
1666 window,
1667 cx,
1668 )
1669 }
1670
1671 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1672 /// The editor grows as tall as needed to fit its content.
1673 pub fn auto_height_unbounded(
1674 min_lines: usize,
1675 window: &mut Window,
1676 cx: &mut Context<Self>,
1677 ) -> Self {
1678 let buffer = cx.new(|cx| Buffer::local("", cx));
1679 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1680 Self::new(
1681 EditorMode::AutoHeight {
1682 min_lines,
1683 max_lines: None,
1684 },
1685 buffer,
1686 None,
1687 window,
1688 cx,
1689 )
1690 }
1691
1692 pub fn for_buffer(
1693 buffer: Entity<Buffer>,
1694 project: Option<Entity<Project>>,
1695 window: &mut Window,
1696 cx: &mut Context<Self>,
1697 ) -> Self {
1698 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1699 Self::new(EditorMode::full(), buffer, project, window, cx)
1700 }
1701
1702 pub fn for_multibuffer(
1703 buffer: Entity<MultiBuffer>,
1704 project: Option<Entity<Project>>,
1705 window: &mut Window,
1706 cx: &mut Context<Self>,
1707 ) -> Self {
1708 Self::new(EditorMode::full(), buffer, project, window, cx)
1709 }
1710
1711 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1712 let mut clone = Self::new(
1713 self.mode.clone(),
1714 self.buffer.clone(),
1715 self.project.clone(),
1716 window,
1717 cx,
1718 );
1719 self.display_map.update(cx, |display_map, cx| {
1720 let snapshot = display_map.snapshot(cx);
1721 clone.display_map.update(cx, |display_map, cx| {
1722 display_map.set_state(&snapshot, cx);
1723 });
1724 });
1725 clone.folds_did_change(cx);
1726 clone.selections.clone_state(&self.selections);
1727 clone.scroll_manager.clone_state(&self.scroll_manager);
1728 clone.searchable = self.searchable;
1729 clone.read_only = self.read_only;
1730 clone
1731 }
1732
1733 pub fn new(
1734 mode: EditorMode,
1735 buffer: Entity<MultiBuffer>,
1736 project: Option<Entity<Project>>,
1737 window: &mut Window,
1738 cx: &mut Context<Self>,
1739 ) -> Self {
1740 Editor::new_internal(mode, buffer, project, None, window, cx)
1741 }
1742
1743 fn new_internal(
1744 mode: EditorMode,
1745 multi_buffer: Entity<MultiBuffer>,
1746 project: Option<Entity<Project>>,
1747 display_map: Option<Entity<DisplayMap>>,
1748 window: &mut Window,
1749 cx: &mut Context<Self>,
1750 ) -> Self {
1751 debug_assert!(
1752 display_map.is_none() || mode.is_minimap(),
1753 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1754 );
1755
1756 let full_mode = mode.is_full();
1757 let is_minimap = mode.is_minimap();
1758 let diagnostics_max_severity = if full_mode {
1759 EditorSettings::get_global(cx)
1760 .diagnostics_max_severity
1761 .unwrap_or(DiagnosticSeverity::Hint)
1762 } else {
1763 DiagnosticSeverity::Off
1764 };
1765 let style = window.text_style();
1766 let font_size = style.font_size.to_pixels(window.rem_size());
1767 let editor = cx.entity().downgrade();
1768 let fold_placeholder = FoldPlaceholder {
1769 constrain_width: false,
1770 render: Arc::new(move |fold_id, fold_range, cx| {
1771 let editor = editor.clone();
1772 div()
1773 .id(fold_id)
1774 .bg(cx.theme().colors().ghost_element_background)
1775 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1776 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1777 .rounded_xs()
1778 .size_full()
1779 .cursor_pointer()
1780 .child("⋯")
1781 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1782 .on_click(move |_, _window, cx| {
1783 editor
1784 .update(cx, |editor, cx| {
1785 editor.unfold_ranges(
1786 &[fold_range.start..fold_range.end],
1787 true,
1788 false,
1789 cx,
1790 );
1791 cx.stop_propagation();
1792 })
1793 .ok();
1794 })
1795 .into_any()
1796 }),
1797 merge_adjacent: true,
1798 ..FoldPlaceholder::default()
1799 };
1800 let display_map = display_map.unwrap_or_else(|| {
1801 cx.new(|cx| {
1802 DisplayMap::new(
1803 multi_buffer.clone(),
1804 style.font(),
1805 font_size,
1806 None,
1807 FILE_HEADER_HEIGHT,
1808 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1809 fold_placeholder,
1810 diagnostics_max_severity,
1811 cx,
1812 )
1813 })
1814 });
1815
1816 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1817
1818 let blink_manager = cx.new(|cx| {
1819 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1820 if is_minimap {
1821 blink_manager.disable(cx);
1822 }
1823 blink_manager
1824 });
1825
1826 let soft_wrap_mode_override =
1827 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1828
1829 let mut project_subscriptions = Vec::new();
1830 if full_mode && let Some(project) = project.as_ref() {
1831 project_subscriptions.push(cx.subscribe_in(
1832 project,
1833 window,
1834 |editor, _, event, window, cx| match event {
1835 project::Event::RefreshCodeLens => {
1836 // we always query lens with actions, without storing them, always refreshing them
1837 }
1838 project::Event::RefreshInlayHints(server_id) => {
1839 editor.refresh_inlay_hints(
1840 InlayHintRefreshReason::RefreshRequested(*server_id),
1841 cx,
1842 );
1843 }
1844 project::Event::LanguageServerRemoved(..) => {
1845 if editor.tasks_update_task.is_none() {
1846 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1847 }
1848 editor.registered_buffers.clear();
1849 editor.register_visible_buffers(cx);
1850 }
1851 project::Event::LanguageServerAdded(..) => {
1852 if editor.tasks_update_task.is_none() {
1853 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1854 }
1855 }
1856 project::Event::SnippetEdit(id, snippet_edits) => {
1857 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1858 let focus_handle = editor.focus_handle(cx);
1859 if focus_handle.is_focused(window) {
1860 let snapshot = buffer.read(cx).snapshot();
1861 for (range, snippet) in snippet_edits {
1862 let editor_range =
1863 language::range_from_lsp(*range).to_offset(&snapshot);
1864 editor
1865 .insert_snippet(
1866 &[editor_range],
1867 snippet.clone(),
1868 window,
1869 cx,
1870 )
1871 .ok();
1872 }
1873 }
1874 }
1875 }
1876 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1877 let buffer_id = *buffer_id;
1878 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1879 editor.register_buffer(buffer_id, cx);
1880 editor.update_lsp_data(Some(buffer_id), window, cx);
1881 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1882 refresh_linked_ranges(editor, window, cx);
1883 editor.refresh_code_actions(window, cx);
1884 editor.refresh_document_highlights(cx);
1885 }
1886 }
1887
1888 project::Event::EntryRenamed(transaction) => {
1889 let Some(workspace) = editor.workspace() else {
1890 return;
1891 };
1892 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1893 else {
1894 return;
1895 };
1896 if active_editor.entity_id() == cx.entity_id() {
1897 let edited_buffers_already_open = {
1898 let other_editors: Vec<Entity<Editor>> = workspace
1899 .read(cx)
1900 .panes()
1901 .iter()
1902 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1903 .filter(|editor| editor.entity_id() != cx.entity_id())
1904 .collect();
1905
1906 transaction.0.keys().all(|buffer| {
1907 other_editors.iter().any(|editor| {
1908 let multi_buffer = editor.read(cx).buffer();
1909 multi_buffer.read(cx).is_singleton()
1910 && multi_buffer.read(cx).as_singleton().map_or(
1911 false,
1912 |singleton| {
1913 singleton.entity_id() == buffer.entity_id()
1914 },
1915 )
1916 })
1917 })
1918 };
1919
1920 if !edited_buffers_already_open {
1921 let workspace = workspace.downgrade();
1922 let transaction = transaction.clone();
1923 cx.defer_in(window, move |_, window, cx| {
1924 cx.spawn_in(window, async move |editor, cx| {
1925 Self::open_project_transaction(
1926 &editor,
1927 workspace,
1928 transaction,
1929 "Rename".to_string(),
1930 cx,
1931 )
1932 .await
1933 .ok()
1934 })
1935 .detach();
1936 });
1937 }
1938 }
1939 }
1940
1941 _ => {}
1942 },
1943 ));
1944 if let Some(task_inventory) = project
1945 .read(cx)
1946 .task_store()
1947 .read(cx)
1948 .task_inventory()
1949 .cloned()
1950 {
1951 project_subscriptions.push(cx.observe_in(
1952 &task_inventory,
1953 window,
1954 |editor, _, window, cx| {
1955 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1956 },
1957 ));
1958 };
1959
1960 project_subscriptions.push(cx.subscribe_in(
1961 &project.read(cx).breakpoint_store(),
1962 window,
1963 |editor, _, event, window, cx| match event {
1964 BreakpointStoreEvent::ClearDebugLines => {
1965 editor.clear_row_highlights::<ActiveDebugLine>();
1966 editor.refresh_inline_values(cx);
1967 }
1968 BreakpointStoreEvent::SetDebugLine => {
1969 if editor.go_to_active_debug_line(window, cx) {
1970 cx.stop_propagation();
1971 }
1972
1973 editor.refresh_inline_values(cx);
1974 }
1975 _ => {}
1976 },
1977 ));
1978 let git_store = project.read(cx).git_store().clone();
1979 let project = project.clone();
1980 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1981 if let GitStoreEvent::RepositoryAdded = event {
1982 this.load_diff_task = Some(
1983 update_uncommitted_diff_for_buffer(
1984 cx.entity(),
1985 &project,
1986 this.buffer.read(cx).all_buffers(),
1987 this.buffer.clone(),
1988 cx,
1989 )
1990 .shared(),
1991 );
1992 }
1993 }));
1994 }
1995
1996 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
1997
1998 let inlay_hint_settings =
1999 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2000 let focus_handle = cx.focus_handle();
2001 if !is_minimap {
2002 cx.on_focus(&focus_handle, window, Self::handle_focus)
2003 .detach();
2004 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2005 .detach();
2006 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2007 .detach();
2008 cx.on_blur(&focus_handle, window, Self::handle_blur)
2009 .detach();
2010 cx.observe_pending_input(window, Self::observe_pending_input)
2011 .detach();
2012 }
2013
2014 let show_indent_guides =
2015 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2016 Some(false)
2017 } else {
2018 None
2019 };
2020
2021 let breakpoint_store = match (&mode, project.as_ref()) {
2022 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2023 _ => None,
2024 };
2025
2026 let mut code_action_providers = Vec::new();
2027 let mut load_uncommitted_diff = None;
2028 if let Some(project) = project.clone() {
2029 load_uncommitted_diff = Some(
2030 update_uncommitted_diff_for_buffer(
2031 cx.entity(),
2032 &project,
2033 multi_buffer.read(cx).all_buffers(),
2034 multi_buffer.clone(),
2035 cx,
2036 )
2037 .shared(),
2038 );
2039 code_action_providers.push(Rc::new(project) as Rc<_>);
2040 }
2041
2042 let mut editor = Self {
2043 focus_handle,
2044 show_cursor_when_unfocused: false,
2045 last_focused_descendant: None,
2046 buffer: multi_buffer.clone(),
2047 display_map: display_map.clone(),
2048 placeholder_display_map: None,
2049 selections,
2050 scroll_manager: ScrollManager::new(cx),
2051 columnar_selection_state: None,
2052 add_selections_state: None,
2053 select_next_state: None,
2054 select_prev_state: None,
2055 selection_history: SelectionHistory::default(),
2056 defer_selection_effects: false,
2057 deferred_selection_effects_state: None,
2058 autoclose_regions: Vec::new(),
2059 snippet_stack: InvalidationStack::default(),
2060 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2061 ime_transaction: None,
2062 active_diagnostics: ActiveDiagnostic::None,
2063 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2064 inline_diagnostics_update: Task::ready(()),
2065 inline_diagnostics: Vec::new(),
2066 soft_wrap_mode_override,
2067 diagnostics_max_severity,
2068 hard_wrap: None,
2069 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2070 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2071 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2072 project,
2073 blink_manager: blink_manager.clone(),
2074 show_local_selections: true,
2075 show_scrollbars: ScrollbarAxes {
2076 horizontal: full_mode,
2077 vertical: full_mode,
2078 },
2079 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2080 offset_content: !matches!(mode, EditorMode::SingleLine),
2081 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2082 show_gutter: full_mode,
2083 show_line_numbers: (!full_mode).then_some(false),
2084 use_relative_line_numbers: None,
2085 disable_expand_excerpt_buttons: !full_mode,
2086 show_git_diff_gutter: None,
2087 show_code_actions: None,
2088 show_runnables: None,
2089 show_breakpoints: None,
2090 show_wrap_guides: None,
2091 show_indent_guides,
2092 highlight_order: 0,
2093 highlighted_rows: HashMap::default(),
2094 background_highlights: HashMap::default(),
2095 gutter_highlights: HashMap::default(),
2096 scrollbar_marker_state: ScrollbarMarkerState::default(),
2097 active_indent_guides_state: ActiveIndentGuidesState::default(),
2098 nav_history: None,
2099 context_menu: RefCell::new(None),
2100 context_menu_options: None,
2101 mouse_context_menu: None,
2102 completion_tasks: Vec::new(),
2103 inline_blame_popover: None,
2104 inline_blame_popover_show_task: None,
2105 signature_help_state: SignatureHelpState::default(),
2106 auto_signature_help: None,
2107 find_all_references_task_sources: Vec::new(),
2108 next_completion_id: 0,
2109 next_inlay_id: 0,
2110 code_action_providers,
2111 available_code_actions: None,
2112 code_actions_task: None,
2113 quick_selection_highlight_task: None,
2114 debounced_selection_highlight_task: None,
2115 document_highlights_task: None,
2116 linked_editing_range_task: None,
2117 pending_rename: None,
2118 searchable: !is_minimap,
2119 cursor_shape: EditorSettings::get_global(cx)
2120 .cursor_shape
2121 .unwrap_or_default(),
2122 current_line_highlight: None,
2123 autoindent_mode: Some(AutoindentMode::EachLine),
2124 collapse_matches: false,
2125 workspace: None,
2126 input_enabled: !is_minimap,
2127 use_modal_editing: full_mode,
2128 read_only: is_minimap,
2129 use_autoclose: true,
2130 use_auto_surround: true,
2131 auto_replace_emoji_shortcode: false,
2132 jsx_tag_auto_close_enabled_in_any_buffer: false,
2133 leader_id: None,
2134 remote_id: None,
2135 hover_state: HoverState::default(),
2136 pending_mouse_down: None,
2137 hovered_link_state: None,
2138 edit_prediction_provider: None,
2139 active_edit_prediction: None,
2140 stale_edit_prediction_in_menu: None,
2141 edit_prediction_preview: EditPredictionPreview::Inactive {
2142 released_too_fast: false,
2143 },
2144 inline_diagnostics_enabled: full_mode,
2145 diagnostics_enabled: full_mode,
2146 word_completions_enabled: full_mode,
2147 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2148 gutter_hovered: false,
2149 pixel_position_of_newest_cursor: None,
2150 last_bounds: None,
2151 last_position_map: None,
2152 expect_bounds_change: None,
2153 gutter_dimensions: GutterDimensions::default(),
2154 style: None,
2155 show_cursor_names: false,
2156 hovered_cursors: HashMap::default(),
2157 next_editor_action_id: EditorActionId::default(),
2158 editor_actions: Rc::default(),
2159 edit_predictions_hidden_for_vim_mode: false,
2160 show_edit_predictions_override: None,
2161 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2162 edit_prediction_settings: EditPredictionSettings::Disabled,
2163 edit_prediction_indent_conflict: false,
2164 edit_prediction_requires_modifier_in_indent_conflict: true,
2165 custom_context_menu: None,
2166 show_git_blame_gutter: false,
2167 show_git_blame_inline: false,
2168 show_selection_menu: None,
2169 show_git_blame_inline_delay_task: None,
2170 git_blame_inline_enabled: full_mode
2171 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2172 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2173 serialize_dirty_buffers: !is_minimap
2174 && ProjectSettings::get_global(cx)
2175 .session
2176 .restore_unsaved_buffers,
2177 blame: None,
2178 blame_subscription: None,
2179 tasks: BTreeMap::default(),
2180
2181 breakpoint_store,
2182 gutter_breakpoint_indicator: (None, None),
2183 hovered_diff_hunk_row: None,
2184 _subscriptions: (!is_minimap)
2185 .then(|| {
2186 vec![
2187 cx.observe(&multi_buffer, Self::on_buffer_changed),
2188 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2189 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2190 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2191 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2192 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2193 cx.observe_window_activation(window, |editor, window, cx| {
2194 let active = window.is_window_active();
2195 editor.blink_manager.update(cx, |blink_manager, cx| {
2196 if active {
2197 blink_manager.enable(cx);
2198 } else {
2199 blink_manager.disable(cx);
2200 }
2201 });
2202 if active {
2203 editor.show_mouse_cursor(cx);
2204 }
2205 }),
2206 ]
2207 })
2208 .unwrap_or_default(),
2209 tasks_update_task: None,
2210 pull_diagnostics_task: Task::ready(()),
2211 colors: None,
2212 refresh_colors_task: Task::ready(()),
2213 inlay_hints: None,
2214 next_color_inlay_id: 0,
2215 post_scroll_update: Task::ready(()),
2216 linked_edit_ranges: Default::default(),
2217 in_project_search: false,
2218 previous_search_ranges: None,
2219 breadcrumb_header: None,
2220 focused_block: None,
2221 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2222 addons: HashMap::default(),
2223 registered_buffers: HashMap::default(),
2224 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2225 selection_mark_mode: false,
2226 toggle_fold_multiple_buffers: Task::ready(()),
2227 serialize_selections: Task::ready(()),
2228 serialize_folds: Task::ready(()),
2229 text_style_refinement: None,
2230 load_diff_task: load_uncommitted_diff,
2231 temporary_diff_override: false,
2232 mouse_cursor_hidden: false,
2233 minimap: None,
2234 hide_mouse_mode: EditorSettings::get_global(cx)
2235 .hide_mouse
2236 .unwrap_or_default(),
2237 change_list: ChangeList::new(),
2238 mode,
2239 selection_drag_state: SelectionDragState::None,
2240 folding_newlines: Task::ready(()),
2241 lookup_key: None,
2242 };
2243
2244 if is_minimap {
2245 return editor;
2246 }
2247
2248 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2249 editor
2250 ._subscriptions
2251 .push(cx.observe(breakpoints, |_, _, cx| {
2252 cx.notify();
2253 }));
2254 }
2255 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2256 editor._subscriptions.extend(project_subscriptions);
2257
2258 editor._subscriptions.push(cx.subscribe_in(
2259 &cx.entity(),
2260 window,
2261 |editor, _, e: &EditorEvent, window, cx| match e {
2262 EditorEvent::ScrollPositionChanged { local, .. } => {
2263 if *local {
2264 let new_anchor = editor.scroll_manager.anchor();
2265 let snapshot = editor.snapshot(window, cx);
2266 editor.update_restoration_data(cx, move |data| {
2267 data.scroll_position = (
2268 new_anchor.top_row(snapshot.buffer_snapshot()),
2269 new_anchor.offset,
2270 );
2271 });
2272 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2273 editor.inline_blame_popover.take();
2274 }
2275 }
2276 EditorEvent::Edited { .. } => {
2277 if !vim_enabled(cx) {
2278 let display_map = editor.display_snapshot(cx);
2279 let selections = editor.selections.all_adjusted_display(&display_map);
2280 let pop_state = editor
2281 .change_list
2282 .last()
2283 .map(|previous| {
2284 previous.len() == selections.len()
2285 && previous.iter().enumerate().all(|(ix, p)| {
2286 p.to_display_point(&display_map).row()
2287 == selections[ix].head().row()
2288 })
2289 })
2290 .unwrap_or(false);
2291 let new_positions = selections
2292 .into_iter()
2293 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2294 .collect();
2295 editor
2296 .change_list
2297 .push_to_change_list(pop_state, new_positions);
2298 }
2299 }
2300 _ => (),
2301 },
2302 ));
2303
2304 if let Some(dap_store) = editor
2305 .project
2306 .as_ref()
2307 .map(|project| project.read(cx).dap_store())
2308 {
2309 let weak_editor = cx.weak_entity();
2310
2311 editor
2312 ._subscriptions
2313 .push(
2314 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2315 let session_entity = cx.entity();
2316 weak_editor
2317 .update(cx, |editor, cx| {
2318 editor._subscriptions.push(
2319 cx.subscribe(&session_entity, Self::on_debug_session_event),
2320 );
2321 })
2322 .ok();
2323 }),
2324 );
2325
2326 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2327 editor
2328 ._subscriptions
2329 .push(cx.subscribe(&session, Self::on_debug_session_event));
2330 }
2331 }
2332
2333 // skip adding the initial selection to selection history
2334 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2335 editor.end_selection(window, cx);
2336 editor.selection_history.mode = SelectionHistoryMode::Normal;
2337
2338 editor.scroll_manager.show_scrollbars(window, cx);
2339 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2340
2341 if full_mode {
2342 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2343 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2344
2345 if editor.git_blame_inline_enabled {
2346 editor.start_git_blame_inline(false, window, cx);
2347 }
2348
2349 editor.go_to_active_debug_line(window, cx);
2350
2351 editor.minimap =
2352 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2353 editor.colors = Some(LspColorData::new(cx));
2354 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2355
2356 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2357 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2358 }
2359 editor.update_lsp_data(None, window, cx);
2360 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2361 }
2362
2363 editor
2364 }
2365
2366 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2367 self.selections.display_map(cx)
2368 }
2369
2370 pub fn deploy_mouse_context_menu(
2371 &mut self,
2372 position: gpui::Point<Pixels>,
2373 context_menu: Entity<ContextMenu>,
2374 window: &mut Window,
2375 cx: &mut Context<Self>,
2376 ) {
2377 self.mouse_context_menu = Some(MouseContextMenu::new(
2378 self,
2379 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2380 context_menu,
2381 window,
2382 cx,
2383 ));
2384 }
2385
2386 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2387 self.mouse_context_menu
2388 .as_ref()
2389 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2390 }
2391
2392 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2393 if self
2394 .selections
2395 .pending_anchor()
2396 .is_some_and(|pending_selection| {
2397 let snapshot = self.buffer().read(cx).snapshot(cx);
2398 pending_selection.range().includes(range, &snapshot)
2399 })
2400 {
2401 return true;
2402 }
2403
2404 self.selections
2405 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2406 .into_iter()
2407 .any(|selection| {
2408 // This is needed to cover a corner case, if we just check for an existing
2409 // selection in the fold range, having a cursor at the start of the fold
2410 // marks it as selected. Non-empty selections don't cause this.
2411 let length = selection.end - selection.start;
2412 length > 0
2413 })
2414 }
2415
2416 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2417 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2418 }
2419
2420 fn key_context_internal(
2421 &self,
2422 has_active_edit_prediction: bool,
2423 window: &mut Window,
2424 cx: &mut App,
2425 ) -> KeyContext {
2426 let mut key_context = KeyContext::new_with_defaults();
2427 key_context.add("Editor");
2428 let mode = match self.mode {
2429 EditorMode::SingleLine => "single_line",
2430 EditorMode::AutoHeight { .. } => "auto_height",
2431 EditorMode::Minimap { .. } => "minimap",
2432 EditorMode::Full { .. } => "full",
2433 };
2434
2435 if EditorSettings::jupyter_enabled(cx) {
2436 key_context.add("jupyter");
2437 }
2438
2439 key_context.set("mode", mode);
2440 if self.pending_rename.is_some() {
2441 key_context.add("renaming");
2442 }
2443
2444 match self.context_menu.borrow().as_ref() {
2445 Some(CodeContextMenu::Completions(menu)) => {
2446 if menu.visible() {
2447 key_context.add("menu");
2448 key_context.add("showing_completions");
2449 }
2450 }
2451 Some(CodeContextMenu::CodeActions(menu)) => {
2452 if menu.visible() {
2453 key_context.add("menu");
2454 key_context.add("showing_code_actions")
2455 }
2456 }
2457 None => {}
2458 }
2459
2460 if self.signature_help_state.has_multiple_signatures() {
2461 key_context.add("showing_signature_help");
2462 }
2463
2464 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2465 if !self.focus_handle(cx).contains_focused(window, cx)
2466 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2467 {
2468 for addon in self.addons.values() {
2469 addon.extend_key_context(&mut key_context, cx)
2470 }
2471 }
2472
2473 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2474 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2475 Some(
2476 file.full_path(cx)
2477 .extension()?
2478 .to_string_lossy()
2479 .into_owned(),
2480 )
2481 }) {
2482 key_context.set("extension", extension);
2483 }
2484 } else {
2485 key_context.add("multibuffer");
2486 }
2487
2488 if has_active_edit_prediction {
2489 if self.edit_prediction_in_conflict() {
2490 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2491 } else {
2492 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2493 key_context.add("copilot_suggestion");
2494 }
2495 }
2496
2497 if self.selection_mark_mode {
2498 key_context.add("selection_mode");
2499 }
2500
2501 let disjoint = self.selections.disjoint_anchors();
2502 let snapshot = self.snapshot(window, cx);
2503 let snapshot = snapshot.buffer_snapshot();
2504 if self.mode == EditorMode::SingleLine
2505 && let [selection] = disjoint
2506 && selection.start == selection.end
2507 && selection.end.to_offset(snapshot) == snapshot.len()
2508 {
2509 key_context.add("end_of_input");
2510 }
2511
2512 key_context
2513 }
2514
2515 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2516 self.last_bounds.as_ref()
2517 }
2518
2519 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2520 if self.mouse_cursor_hidden {
2521 self.mouse_cursor_hidden = false;
2522 cx.notify();
2523 }
2524 }
2525
2526 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2527 let hide_mouse_cursor = match origin {
2528 HideMouseCursorOrigin::TypingAction => {
2529 matches!(
2530 self.hide_mouse_mode,
2531 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2532 )
2533 }
2534 HideMouseCursorOrigin::MovementAction => {
2535 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2536 }
2537 };
2538 if self.mouse_cursor_hidden != hide_mouse_cursor {
2539 self.mouse_cursor_hidden = hide_mouse_cursor;
2540 cx.notify();
2541 }
2542 }
2543
2544 pub fn edit_prediction_in_conflict(&self) -> bool {
2545 if !self.show_edit_predictions_in_menu() {
2546 return false;
2547 }
2548
2549 let showing_completions = self
2550 .context_menu
2551 .borrow()
2552 .as_ref()
2553 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2554
2555 showing_completions
2556 || self.edit_prediction_requires_modifier()
2557 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2558 // bindings to insert tab characters.
2559 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2560 }
2561
2562 pub fn accept_edit_prediction_keybind(
2563 &self,
2564 accept_partial: bool,
2565 window: &mut Window,
2566 cx: &mut App,
2567 ) -> AcceptEditPredictionBinding {
2568 let key_context = self.key_context_internal(true, window, cx);
2569 let in_conflict = self.edit_prediction_in_conflict();
2570
2571 let bindings = if accept_partial {
2572 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2573 } else {
2574 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2575 };
2576
2577 // TODO: if the binding contains multiple keystrokes, display all of them, not
2578 // just the first one.
2579 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2580 !in_conflict
2581 || binding
2582 .keystrokes()
2583 .first()
2584 .is_some_and(|keystroke| keystroke.modifiers().modified())
2585 }))
2586 }
2587
2588 pub fn new_file(
2589 workspace: &mut Workspace,
2590 _: &workspace::NewFile,
2591 window: &mut Window,
2592 cx: &mut Context<Workspace>,
2593 ) {
2594 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2595 "Failed to create buffer",
2596 window,
2597 cx,
2598 |e, _, _| match e.error_code() {
2599 ErrorCode::RemoteUpgradeRequired => Some(format!(
2600 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2601 e.error_tag("required").unwrap_or("the latest version")
2602 )),
2603 _ => None,
2604 },
2605 );
2606 }
2607
2608 pub fn new_in_workspace(
2609 workspace: &mut Workspace,
2610 window: &mut Window,
2611 cx: &mut Context<Workspace>,
2612 ) -> Task<Result<Entity<Editor>>> {
2613 let project = workspace.project().clone();
2614 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2615
2616 cx.spawn_in(window, async move |workspace, cx| {
2617 let buffer = create.await?;
2618 workspace.update_in(cx, |workspace, window, cx| {
2619 let editor =
2620 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2621 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2622 editor
2623 })
2624 })
2625 }
2626
2627 fn new_file_vertical(
2628 workspace: &mut Workspace,
2629 _: &workspace::NewFileSplitVertical,
2630 window: &mut Window,
2631 cx: &mut Context<Workspace>,
2632 ) {
2633 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2634 }
2635
2636 fn new_file_horizontal(
2637 workspace: &mut Workspace,
2638 _: &workspace::NewFileSplitHorizontal,
2639 window: &mut Window,
2640 cx: &mut Context<Workspace>,
2641 ) {
2642 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2643 }
2644
2645 fn new_file_split(
2646 workspace: &mut Workspace,
2647 action: &workspace::NewFileSplit,
2648 window: &mut Window,
2649 cx: &mut Context<Workspace>,
2650 ) {
2651 Self::new_file_in_direction(workspace, action.0, window, cx)
2652 }
2653
2654 fn new_file_in_direction(
2655 workspace: &mut Workspace,
2656 direction: SplitDirection,
2657 window: &mut Window,
2658 cx: &mut Context<Workspace>,
2659 ) {
2660 let project = workspace.project().clone();
2661 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2662
2663 cx.spawn_in(window, async move |workspace, cx| {
2664 let buffer = create.await?;
2665 workspace.update_in(cx, move |workspace, window, cx| {
2666 workspace.split_item(
2667 direction,
2668 Box::new(
2669 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2670 ),
2671 window,
2672 cx,
2673 )
2674 })?;
2675 anyhow::Ok(())
2676 })
2677 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2678 match e.error_code() {
2679 ErrorCode::RemoteUpgradeRequired => Some(format!(
2680 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2681 e.error_tag("required").unwrap_or("the latest version")
2682 )),
2683 _ => None,
2684 }
2685 });
2686 }
2687
2688 pub fn leader_id(&self) -> Option<CollaboratorId> {
2689 self.leader_id
2690 }
2691
2692 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2693 &self.buffer
2694 }
2695
2696 pub fn project(&self) -> Option<&Entity<Project>> {
2697 self.project.as_ref()
2698 }
2699
2700 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2701 self.workspace.as_ref()?.0.upgrade()
2702 }
2703
2704 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2705 self.buffer().read(cx).title(cx)
2706 }
2707
2708 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2709 let git_blame_gutter_max_author_length = self
2710 .render_git_blame_gutter(cx)
2711 .then(|| {
2712 if let Some(blame) = self.blame.as_ref() {
2713 let max_author_length =
2714 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2715 Some(max_author_length)
2716 } else {
2717 None
2718 }
2719 })
2720 .flatten();
2721
2722 EditorSnapshot {
2723 mode: self.mode.clone(),
2724 show_gutter: self.show_gutter,
2725 show_line_numbers: self.show_line_numbers,
2726 show_git_diff_gutter: self.show_git_diff_gutter,
2727 show_code_actions: self.show_code_actions,
2728 show_runnables: self.show_runnables,
2729 show_breakpoints: self.show_breakpoints,
2730 git_blame_gutter_max_author_length,
2731 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2732 placeholder_display_snapshot: self
2733 .placeholder_display_map
2734 .as_ref()
2735 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2736 scroll_anchor: self.scroll_manager.anchor(),
2737 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2738 is_focused: self.focus_handle.is_focused(window),
2739 current_line_highlight: self
2740 .current_line_highlight
2741 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2742 gutter_hovered: self.gutter_hovered,
2743 }
2744 }
2745
2746 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2747 self.buffer.read(cx).language_at(point, cx)
2748 }
2749
2750 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2751 self.buffer.read(cx).read(cx).file_at(point).cloned()
2752 }
2753
2754 pub fn active_excerpt(
2755 &self,
2756 cx: &App,
2757 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2758 self.buffer
2759 .read(cx)
2760 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2761 }
2762
2763 pub fn mode(&self) -> &EditorMode {
2764 &self.mode
2765 }
2766
2767 pub fn set_mode(&mut self, mode: EditorMode) {
2768 self.mode = mode;
2769 }
2770
2771 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2772 self.collaboration_hub.as_deref()
2773 }
2774
2775 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2776 self.collaboration_hub = Some(hub);
2777 }
2778
2779 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2780 self.in_project_search = in_project_search;
2781 }
2782
2783 pub fn set_custom_context_menu(
2784 &mut self,
2785 f: impl 'static
2786 + Fn(
2787 &mut Self,
2788 DisplayPoint,
2789 &mut Window,
2790 &mut Context<Self>,
2791 ) -> Option<Entity<ui::ContextMenu>>,
2792 ) {
2793 self.custom_context_menu = Some(Box::new(f))
2794 }
2795
2796 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2797 self.completion_provider = provider;
2798 }
2799
2800 #[cfg(any(test, feature = "test-support"))]
2801 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2802 self.completion_provider.clone()
2803 }
2804
2805 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2806 self.semantics_provider.clone()
2807 }
2808
2809 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2810 self.semantics_provider = provider;
2811 }
2812
2813 pub fn set_edit_prediction_provider<T>(
2814 &mut self,
2815 provider: Option<Entity<T>>,
2816 window: &mut Window,
2817 cx: &mut Context<Self>,
2818 ) where
2819 T: EditPredictionProvider,
2820 {
2821 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2822 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2823 if this.focus_handle.is_focused(window) {
2824 this.update_visible_edit_prediction(window, cx);
2825 }
2826 }),
2827 provider: Arc::new(provider),
2828 });
2829 self.update_edit_prediction_settings(cx);
2830 self.refresh_edit_prediction(false, false, window, cx);
2831 }
2832
2833 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2834 self.placeholder_display_map
2835 .as_ref()
2836 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2837 }
2838
2839 pub fn set_placeholder_text(
2840 &mut self,
2841 placeholder_text: &str,
2842 window: &mut Window,
2843 cx: &mut Context<Self>,
2844 ) {
2845 let multibuffer = cx
2846 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2847
2848 let style = window.text_style();
2849
2850 self.placeholder_display_map = Some(cx.new(|cx| {
2851 DisplayMap::new(
2852 multibuffer,
2853 style.font(),
2854 style.font_size.to_pixels(window.rem_size()),
2855 None,
2856 FILE_HEADER_HEIGHT,
2857 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2858 Default::default(),
2859 DiagnosticSeverity::Off,
2860 cx,
2861 )
2862 }));
2863 cx.notify();
2864 }
2865
2866 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2867 self.cursor_shape = cursor_shape;
2868
2869 // Disrupt blink for immediate user feedback that the cursor shape has changed
2870 self.blink_manager.update(cx, BlinkManager::show_cursor);
2871
2872 cx.notify();
2873 }
2874
2875 pub fn set_current_line_highlight(
2876 &mut self,
2877 current_line_highlight: Option<CurrentLineHighlight>,
2878 ) {
2879 self.current_line_highlight = current_line_highlight;
2880 }
2881
2882 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2883 self.collapse_matches = collapse_matches;
2884 }
2885
2886 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2887 if self.collapse_matches {
2888 return range.start..range.start;
2889 }
2890 range.clone()
2891 }
2892
2893 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2894 if self.display_map.read(cx).clip_at_line_ends != clip {
2895 self.display_map
2896 .update(cx, |map, _| map.clip_at_line_ends = clip);
2897 }
2898 }
2899
2900 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2901 self.input_enabled = input_enabled;
2902 }
2903
2904 pub fn set_edit_predictions_hidden_for_vim_mode(
2905 &mut self,
2906 hidden: bool,
2907 window: &mut Window,
2908 cx: &mut Context<Self>,
2909 ) {
2910 if hidden != self.edit_predictions_hidden_for_vim_mode {
2911 self.edit_predictions_hidden_for_vim_mode = hidden;
2912 if hidden {
2913 self.update_visible_edit_prediction(window, cx);
2914 } else {
2915 self.refresh_edit_prediction(true, false, window, cx);
2916 }
2917 }
2918 }
2919
2920 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2921 self.menu_edit_predictions_policy = value;
2922 }
2923
2924 pub fn set_autoindent(&mut self, autoindent: bool) {
2925 if autoindent {
2926 self.autoindent_mode = Some(AutoindentMode::EachLine);
2927 } else {
2928 self.autoindent_mode = None;
2929 }
2930 }
2931
2932 pub fn read_only(&self, cx: &App) -> bool {
2933 self.read_only || self.buffer.read(cx).read_only()
2934 }
2935
2936 pub fn set_read_only(&mut self, read_only: bool) {
2937 self.read_only = read_only;
2938 }
2939
2940 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2941 self.use_autoclose = autoclose;
2942 }
2943
2944 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2945 self.use_auto_surround = auto_surround;
2946 }
2947
2948 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2949 self.auto_replace_emoji_shortcode = auto_replace;
2950 }
2951
2952 pub fn toggle_edit_predictions(
2953 &mut self,
2954 _: &ToggleEditPrediction,
2955 window: &mut Window,
2956 cx: &mut Context<Self>,
2957 ) {
2958 if self.show_edit_predictions_override.is_some() {
2959 self.set_show_edit_predictions(None, window, cx);
2960 } else {
2961 let show_edit_predictions = !self.edit_predictions_enabled();
2962 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2963 }
2964 }
2965
2966 pub fn set_show_edit_predictions(
2967 &mut self,
2968 show_edit_predictions: Option<bool>,
2969 window: &mut Window,
2970 cx: &mut Context<Self>,
2971 ) {
2972 self.show_edit_predictions_override = show_edit_predictions;
2973 self.update_edit_prediction_settings(cx);
2974
2975 if let Some(false) = show_edit_predictions {
2976 self.discard_edit_prediction(false, cx);
2977 } else {
2978 self.refresh_edit_prediction(false, true, window, cx);
2979 }
2980 }
2981
2982 fn edit_predictions_disabled_in_scope(
2983 &self,
2984 buffer: &Entity<Buffer>,
2985 buffer_position: language::Anchor,
2986 cx: &App,
2987 ) -> bool {
2988 let snapshot = buffer.read(cx).snapshot();
2989 let settings = snapshot.settings_at(buffer_position, cx);
2990
2991 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2992 return false;
2993 };
2994
2995 scope.override_name().is_some_and(|scope_name| {
2996 settings
2997 .edit_predictions_disabled_in
2998 .iter()
2999 .any(|s| s == scope_name)
3000 })
3001 }
3002
3003 pub fn set_use_modal_editing(&mut self, to: bool) {
3004 self.use_modal_editing = to;
3005 }
3006
3007 pub fn use_modal_editing(&self) -> bool {
3008 self.use_modal_editing
3009 }
3010
3011 fn selections_did_change(
3012 &mut self,
3013 local: bool,
3014 old_cursor_position: &Anchor,
3015 effects: SelectionEffects,
3016 window: &mut Window,
3017 cx: &mut Context<Self>,
3018 ) {
3019 window.invalidate_character_coordinates();
3020
3021 // Copy selections to primary selection buffer
3022 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3023 if local {
3024 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3025 let buffer_handle = self.buffer.read(cx).read(cx);
3026
3027 let mut text = String::new();
3028 for (index, selection) in selections.iter().enumerate() {
3029 let text_for_selection = buffer_handle
3030 .text_for_range(selection.start..selection.end)
3031 .collect::<String>();
3032
3033 text.push_str(&text_for_selection);
3034 if index != selections.len() - 1 {
3035 text.push('\n');
3036 }
3037 }
3038
3039 if !text.is_empty() {
3040 cx.write_to_primary(ClipboardItem::new_string(text));
3041 }
3042 }
3043
3044 let selection_anchors = self.selections.disjoint_anchors_arc();
3045
3046 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3047 self.buffer.update(cx, |buffer, cx| {
3048 buffer.set_active_selections(
3049 &selection_anchors,
3050 self.selections.line_mode(),
3051 self.cursor_shape,
3052 cx,
3053 )
3054 });
3055 }
3056 let display_map = self
3057 .display_map
3058 .update(cx, |display_map, cx| display_map.snapshot(cx));
3059 let buffer = display_map.buffer_snapshot();
3060 if self.selections.count() == 1 {
3061 self.add_selections_state = None;
3062 }
3063 self.select_next_state = None;
3064 self.select_prev_state = None;
3065 self.select_syntax_node_history.try_clear();
3066 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3067 self.snippet_stack.invalidate(&selection_anchors, buffer);
3068 self.take_rename(false, window, cx);
3069
3070 let newest_selection = self.selections.newest_anchor();
3071 let new_cursor_position = newest_selection.head();
3072 let selection_start = newest_selection.start;
3073
3074 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3075 self.push_to_nav_history(
3076 *old_cursor_position,
3077 Some(new_cursor_position.to_point(buffer)),
3078 false,
3079 effects.nav_history == Some(true),
3080 cx,
3081 );
3082 }
3083
3084 if local {
3085 if let Some(buffer_id) = new_cursor_position.buffer_id {
3086 self.register_buffer(buffer_id, cx);
3087 }
3088
3089 let mut context_menu = self.context_menu.borrow_mut();
3090 let completion_menu = match context_menu.as_ref() {
3091 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3092 Some(CodeContextMenu::CodeActions(_)) => {
3093 *context_menu = None;
3094 None
3095 }
3096 None => None,
3097 };
3098 let completion_position = completion_menu.map(|menu| menu.initial_position);
3099 drop(context_menu);
3100
3101 if effects.completions
3102 && let Some(completion_position) = completion_position
3103 {
3104 let start_offset = selection_start.to_offset(buffer);
3105 let position_matches = start_offset == completion_position.to_offset(buffer);
3106 let continue_showing = if position_matches {
3107 if self.snippet_stack.is_empty() {
3108 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3109 == Some(CharKind::Word)
3110 } else {
3111 // Snippet choices can be shown even when the cursor is in whitespace.
3112 // Dismissing the menu with actions like backspace is handled by
3113 // invalidation regions.
3114 true
3115 }
3116 } else {
3117 false
3118 };
3119
3120 if continue_showing {
3121 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3122 } else {
3123 self.hide_context_menu(window, cx);
3124 }
3125 }
3126
3127 hide_hover(self, cx);
3128
3129 if old_cursor_position.to_display_point(&display_map).row()
3130 != new_cursor_position.to_display_point(&display_map).row()
3131 {
3132 self.available_code_actions.take();
3133 }
3134 self.refresh_code_actions(window, cx);
3135 self.refresh_document_highlights(cx);
3136 refresh_linked_ranges(self, window, cx);
3137
3138 self.refresh_selected_text_highlights(false, window, cx);
3139 refresh_matching_bracket_highlights(self, cx);
3140 self.update_visible_edit_prediction(window, cx);
3141 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3142 self.inline_blame_popover.take();
3143 if self.git_blame_inline_enabled {
3144 self.start_inline_blame_timer(window, cx);
3145 }
3146 }
3147
3148 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3149 cx.emit(EditorEvent::SelectionsChanged { local });
3150
3151 let selections = &self.selections.disjoint_anchors_arc();
3152 if selections.len() == 1 {
3153 cx.emit(SearchEvent::ActiveMatchChanged)
3154 }
3155 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3156 let inmemory_selections = selections
3157 .iter()
3158 .map(|s| {
3159 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3160 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3161 })
3162 .collect();
3163 self.update_restoration_data(cx, |data| {
3164 data.selections = inmemory_selections;
3165 });
3166
3167 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3168 && let Some(workspace_id) =
3169 self.workspace.as_ref().and_then(|workspace| workspace.1)
3170 {
3171 let snapshot = self.buffer().read(cx).snapshot(cx);
3172 let selections = selections.clone();
3173 let background_executor = cx.background_executor().clone();
3174 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3175 self.serialize_selections = cx.background_spawn(async move {
3176 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3177 let db_selections = selections
3178 .iter()
3179 .map(|selection| {
3180 (
3181 selection.start.to_offset(&snapshot),
3182 selection.end.to_offset(&snapshot),
3183 )
3184 })
3185 .collect();
3186
3187 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3188 .await
3189 .with_context(|| {
3190 format!(
3191 "persisting editor selections for editor {editor_id}, \
3192 workspace {workspace_id:?}"
3193 )
3194 })
3195 .log_err();
3196 });
3197 }
3198 }
3199
3200 cx.notify();
3201 }
3202
3203 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3204 use text::ToOffset as _;
3205 use text::ToPoint as _;
3206
3207 if self.mode.is_minimap()
3208 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3209 {
3210 return;
3211 }
3212
3213 if !self.buffer().read(cx).is_singleton() {
3214 return;
3215 }
3216
3217 let display_snapshot = self
3218 .display_map
3219 .update(cx, |display_map, cx| display_map.snapshot(cx));
3220 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3221 return;
3222 };
3223 let inmemory_folds = display_snapshot
3224 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3225 .map(|fold| {
3226 fold.range.start.text_anchor.to_point(&snapshot)
3227 ..fold.range.end.text_anchor.to_point(&snapshot)
3228 })
3229 .collect();
3230 self.update_restoration_data(cx, |data| {
3231 data.folds = inmemory_folds;
3232 });
3233
3234 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3235 return;
3236 };
3237 let background_executor = cx.background_executor().clone();
3238 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3239 let db_folds = display_snapshot
3240 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3241 .map(|fold| {
3242 (
3243 fold.range.start.text_anchor.to_offset(&snapshot),
3244 fold.range.end.text_anchor.to_offset(&snapshot),
3245 )
3246 })
3247 .collect();
3248 self.serialize_folds = cx.background_spawn(async move {
3249 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3250 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3251 .await
3252 .with_context(|| {
3253 format!(
3254 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3255 )
3256 })
3257 .log_err();
3258 });
3259 }
3260
3261 pub fn sync_selections(
3262 &mut self,
3263 other: Entity<Editor>,
3264 cx: &mut Context<Self>,
3265 ) -> gpui::Subscription {
3266 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3267 if !other_selections.is_empty() {
3268 self.selections.change_with(cx, |selections| {
3269 selections.select_anchors(other_selections);
3270 });
3271 }
3272
3273 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3274 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3275 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3276 if other_selections.is_empty() {
3277 return;
3278 }
3279 this.selections.change_with(cx, |selections| {
3280 selections.select_anchors(other_selections);
3281 });
3282 }
3283 });
3284
3285 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3286 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3287 let these_selections = this.selections.disjoint_anchors().to_vec();
3288 if these_selections.is_empty() {
3289 return;
3290 }
3291 other.update(cx, |other_editor, cx| {
3292 other_editor.selections.change_with(cx, |selections| {
3293 selections.select_anchors(these_selections);
3294 })
3295 });
3296 }
3297 });
3298
3299 Subscription::join(other_subscription, this_subscription)
3300 }
3301
3302 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3303 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3304 /// effects of selection change occur at the end of the transaction.
3305 pub fn change_selections<R>(
3306 &mut self,
3307 effects: SelectionEffects,
3308 window: &mut Window,
3309 cx: &mut Context<Self>,
3310 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3311 ) -> R {
3312 if let Some(state) = &mut self.deferred_selection_effects_state {
3313 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3314 state.effects.completions = effects.completions;
3315 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3316 let (changed, result) = self.selections.change_with(cx, change);
3317 state.changed |= changed;
3318 return result;
3319 }
3320 let mut state = DeferredSelectionEffectsState {
3321 changed: false,
3322 effects,
3323 old_cursor_position: self.selections.newest_anchor().head(),
3324 history_entry: SelectionHistoryEntry {
3325 selections: self.selections.disjoint_anchors_arc(),
3326 select_next_state: self.select_next_state.clone(),
3327 select_prev_state: self.select_prev_state.clone(),
3328 add_selections_state: self.add_selections_state.clone(),
3329 },
3330 };
3331 let (changed, result) = self.selections.change_with(cx, change);
3332 state.changed = state.changed || changed;
3333 if self.defer_selection_effects {
3334 self.deferred_selection_effects_state = Some(state);
3335 } else {
3336 self.apply_selection_effects(state, window, cx);
3337 }
3338 result
3339 }
3340
3341 /// Defers the effects of selection change, so that the effects of multiple calls to
3342 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3343 /// to selection history and the state of popovers based on selection position aren't
3344 /// erroneously updated.
3345 pub fn with_selection_effects_deferred<R>(
3346 &mut self,
3347 window: &mut Window,
3348 cx: &mut Context<Self>,
3349 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3350 ) -> R {
3351 let already_deferred = self.defer_selection_effects;
3352 self.defer_selection_effects = true;
3353 let result = update(self, window, cx);
3354 if !already_deferred {
3355 self.defer_selection_effects = false;
3356 if let Some(state) = self.deferred_selection_effects_state.take() {
3357 self.apply_selection_effects(state, window, cx);
3358 }
3359 }
3360 result
3361 }
3362
3363 fn apply_selection_effects(
3364 &mut self,
3365 state: DeferredSelectionEffectsState,
3366 window: &mut Window,
3367 cx: &mut Context<Self>,
3368 ) {
3369 if state.changed {
3370 self.selection_history.push(state.history_entry);
3371
3372 if let Some(autoscroll) = state.effects.scroll {
3373 self.request_autoscroll(autoscroll, cx);
3374 }
3375
3376 let old_cursor_position = &state.old_cursor_position;
3377
3378 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3379
3380 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3381 self.show_signature_help(&ShowSignatureHelp, window, cx);
3382 }
3383 }
3384 }
3385
3386 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3387 where
3388 I: IntoIterator<Item = (Range<S>, T)>,
3389 S: ToOffset,
3390 T: Into<Arc<str>>,
3391 {
3392 if self.read_only(cx) {
3393 return;
3394 }
3395
3396 self.buffer
3397 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3398 }
3399
3400 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3401 where
3402 I: IntoIterator<Item = (Range<S>, T)>,
3403 S: ToOffset,
3404 T: Into<Arc<str>>,
3405 {
3406 if self.read_only(cx) {
3407 return;
3408 }
3409
3410 self.buffer.update(cx, |buffer, cx| {
3411 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3412 });
3413 }
3414
3415 pub fn edit_with_block_indent<I, S, T>(
3416 &mut self,
3417 edits: I,
3418 original_indent_columns: Vec<Option<u32>>,
3419 cx: &mut Context<Self>,
3420 ) where
3421 I: IntoIterator<Item = (Range<S>, T)>,
3422 S: ToOffset,
3423 T: Into<Arc<str>>,
3424 {
3425 if self.read_only(cx) {
3426 return;
3427 }
3428
3429 self.buffer.update(cx, |buffer, cx| {
3430 buffer.edit(
3431 edits,
3432 Some(AutoindentMode::Block {
3433 original_indent_columns,
3434 }),
3435 cx,
3436 )
3437 });
3438 }
3439
3440 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3441 self.hide_context_menu(window, cx);
3442
3443 match phase {
3444 SelectPhase::Begin {
3445 position,
3446 add,
3447 click_count,
3448 } => self.begin_selection(position, add, click_count, window, cx),
3449 SelectPhase::BeginColumnar {
3450 position,
3451 goal_column,
3452 reset,
3453 mode,
3454 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3455 SelectPhase::Extend {
3456 position,
3457 click_count,
3458 } => self.extend_selection(position, click_count, window, cx),
3459 SelectPhase::Update {
3460 position,
3461 goal_column,
3462 scroll_delta,
3463 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3464 SelectPhase::End => self.end_selection(window, cx),
3465 }
3466 }
3467
3468 fn extend_selection(
3469 &mut self,
3470 position: DisplayPoint,
3471 click_count: usize,
3472 window: &mut Window,
3473 cx: &mut Context<Self>,
3474 ) {
3475 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3476 let tail = self.selections.newest::<usize>(&display_map).tail();
3477 let click_count = click_count.max(match self.selections.select_mode() {
3478 SelectMode::Character => 1,
3479 SelectMode::Word(_) => 2,
3480 SelectMode::Line(_) => 3,
3481 SelectMode::All => 4,
3482 });
3483 self.begin_selection(position, false, click_count, window, cx);
3484
3485 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3486
3487 let current_selection = match self.selections.select_mode() {
3488 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3489 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3490 };
3491
3492 let mut pending_selection = self
3493 .selections
3494 .pending_anchor()
3495 .cloned()
3496 .expect("extend_selection not called with pending selection");
3497
3498 if pending_selection
3499 .start
3500 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3501 == Ordering::Greater
3502 {
3503 pending_selection.start = current_selection.start;
3504 }
3505 if pending_selection
3506 .end
3507 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3508 == Ordering::Less
3509 {
3510 pending_selection.end = current_selection.end;
3511 pending_selection.reversed = true;
3512 }
3513
3514 let mut pending_mode = self.selections.pending_mode().unwrap();
3515 match &mut pending_mode {
3516 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3517 _ => {}
3518 }
3519
3520 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3521 SelectionEffects::scroll(Autoscroll::fit())
3522 } else {
3523 SelectionEffects::no_scroll()
3524 };
3525
3526 self.change_selections(effects, window, cx, |s| {
3527 s.set_pending(pending_selection.clone(), pending_mode);
3528 s.set_is_extending(true);
3529 });
3530 }
3531
3532 fn begin_selection(
3533 &mut self,
3534 position: DisplayPoint,
3535 add: bool,
3536 click_count: usize,
3537 window: &mut Window,
3538 cx: &mut Context<Self>,
3539 ) {
3540 if !self.focus_handle.is_focused(window) {
3541 self.last_focused_descendant = None;
3542 window.focus(&self.focus_handle);
3543 }
3544
3545 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3546 let buffer = display_map.buffer_snapshot();
3547 let position = display_map.clip_point(position, Bias::Left);
3548
3549 let start;
3550 let end;
3551 let mode;
3552 let mut auto_scroll;
3553 match click_count {
3554 1 => {
3555 start = buffer.anchor_before(position.to_point(&display_map));
3556 end = start;
3557 mode = SelectMode::Character;
3558 auto_scroll = true;
3559 }
3560 2 => {
3561 let position = display_map
3562 .clip_point(position, Bias::Left)
3563 .to_offset(&display_map, Bias::Left);
3564 let (range, _) = buffer.surrounding_word(position, None);
3565 start = buffer.anchor_before(range.start);
3566 end = buffer.anchor_before(range.end);
3567 mode = SelectMode::Word(start..end);
3568 auto_scroll = true;
3569 }
3570 3 => {
3571 let position = display_map
3572 .clip_point(position, Bias::Left)
3573 .to_point(&display_map);
3574 let line_start = display_map.prev_line_boundary(position).0;
3575 let next_line_start = buffer.clip_point(
3576 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3577 Bias::Left,
3578 );
3579 start = buffer.anchor_before(line_start);
3580 end = buffer.anchor_before(next_line_start);
3581 mode = SelectMode::Line(start..end);
3582 auto_scroll = true;
3583 }
3584 _ => {
3585 start = buffer.anchor_before(0);
3586 end = buffer.anchor_before(buffer.len());
3587 mode = SelectMode::All;
3588 auto_scroll = false;
3589 }
3590 }
3591 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3592
3593 let point_to_delete: Option<usize> = {
3594 let selected_points: Vec<Selection<Point>> =
3595 self.selections.disjoint_in_range(start..end, &display_map);
3596
3597 if !add || click_count > 1 {
3598 None
3599 } else if !selected_points.is_empty() {
3600 Some(selected_points[0].id)
3601 } else {
3602 let clicked_point_already_selected =
3603 self.selections.disjoint_anchors().iter().find(|selection| {
3604 selection.start.to_point(buffer) == start.to_point(buffer)
3605 || selection.end.to_point(buffer) == end.to_point(buffer)
3606 });
3607
3608 clicked_point_already_selected.map(|selection| selection.id)
3609 }
3610 };
3611
3612 let selections_count = self.selections.count();
3613 let effects = if auto_scroll {
3614 SelectionEffects::default()
3615 } else {
3616 SelectionEffects::no_scroll()
3617 };
3618
3619 self.change_selections(effects, window, cx, |s| {
3620 if let Some(point_to_delete) = point_to_delete {
3621 s.delete(point_to_delete);
3622
3623 if selections_count == 1 {
3624 s.set_pending_anchor_range(start..end, mode);
3625 }
3626 } else {
3627 if !add {
3628 s.clear_disjoint();
3629 }
3630
3631 s.set_pending_anchor_range(start..end, mode);
3632 }
3633 });
3634 }
3635
3636 fn begin_columnar_selection(
3637 &mut self,
3638 position: DisplayPoint,
3639 goal_column: u32,
3640 reset: bool,
3641 mode: ColumnarMode,
3642 window: &mut Window,
3643 cx: &mut Context<Self>,
3644 ) {
3645 if !self.focus_handle.is_focused(window) {
3646 self.last_focused_descendant = None;
3647 window.focus(&self.focus_handle);
3648 }
3649
3650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3651
3652 if reset {
3653 let pointer_position = display_map
3654 .buffer_snapshot()
3655 .anchor_before(position.to_point(&display_map));
3656
3657 self.change_selections(
3658 SelectionEffects::scroll(Autoscroll::newest()),
3659 window,
3660 cx,
3661 |s| {
3662 s.clear_disjoint();
3663 s.set_pending_anchor_range(
3664 pointer_position..pointer_position,
3665 SelectMode::Character,
3666 );
3667 },
3668 );
3669 };
3670
3671 let tail = self.selections.newest::<Point>(&display_map).tail();
3672 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3673 self.columnar_selection_state = match mode {
3674 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3675 selection_tail: selection_anchor,
3676 display_point: if reset {
3677 if position.column() != goal_column {
3678 Some(DisplayPoint::new(position.row(), goal_column))
3679 } else {
3680 None
3681 }
3682 } else {
3683 None
3684 },
3685 }),
3686 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3687 selection_tail: selection_anchor,
3688 }),
3689 };
3690
3691 if !reset {
3692 self.select_columns(position, goal_column, &display_map, window, cx);
3693 }
3694 }
3695
3696 fn update_selection(
3697 &mut self,
3698 position: DisplayPoint,
3699 goal_column: u32,
3700 scroll_delta: gpui::Point<f32>,
3701 window: &mut Window,
3702 cx: &mut Context<Self>,
3703 ) {
3704 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3705
3706 if self.columnar_selection_state.is_some() {
3707 self.select_columns(position, goal_column, &display_map, window, cx);
3708 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3709 let buffer = display_map.buffer_snapshot();
3710 let head;
3711 let tail;
3712 let mode = self.selections.pending_mode().unwrap();
3713 match &mode {
3714 SelectMode::Character => {
3715 head = position.to_point(&display_map);
3716 tail = pending.tail().to_point(buffer);
3717 }
3718 SelectMode::Word(original_range) => {
3719 let offset = display_map
3720 .clip_point(position, Bias::Left)
3721 .to_offset(&display_map, Bias::Left);
3722 let original_range = original_range.to_offset(buffer);
3723
3724 let head_offset = if buffer.is_inside_word(offset, None)
3725 || original_range.contains(&offset)
3726 {
3727 let (word_range, _) = buffer.surrounding_word(offset, None);
3728 if word_range.start < original_range.start {
3729 word_range.start
3730 } else {
3731 word_range.end
3732 }
3733 } else {
3734 offset
3735 };
3736
3737 head = head_offset.to_point(buffer);
3738 if head_offset <= original_range.start {
3739 tail = original_range.end.to_point(buffer);
3740 } else {
3741 tail = original_range.start.to_point(buffer);
3742 }
3743 }
3744 SelectMode::Line(original_range) => {
3745 let original_range = original_range.to_point(display_map.buffer_snapshot());
3746
3747 let position = display_map
3748 .clip_point(position, Bias::Left)
3749 .to_point(&display_map);
3750 let line_start = display_map.prev_line_boundary(position).0;
3751 let next_line_start = buffer.clip_point(
3752 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3753 Bias::Left,
3754 );
3755
3756 if line_start < original_range.start {
3757 head = line_start
3758 } else {
3759 head = next_line_start
3760 }
3761
3762 if head <= original_range.start {
3763 tail = original_range.end;
3764 } else {
3765 tail = original_range.start;
3766 }
3767 }
3768 SelectMode::All => {
3769 return;
3770 }
3771 };
3772
3773 if head < tail {
3774 pending.start = buffer.anchor_before(head);
3775 pending.end = buffer.anchor_before(tail);
3776 pending.reversed = true;
3777 } else {
3778 pending.start = buffer.anchor_before(tail);
3779 pending.end = buffer.anchor_before(head);
3780 pending.reversed = false;
3781 }
3782
3783 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3784 s.set_pending(pending.clone(), mode);
3785 });
3786 } else {
3787 log::error!("update_selection dispatched with no pending selection");
3788 return;
3789 }
3790
3791 self.apply_scroll_delta(scroll_delta, window, cx);
3792 cx.notify();
3793 }
3794
3795 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3796 self.columnar_selection_state.take();
3797 if let Some(pending_mode) = self.selections.pending_mode() {
3798 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3799 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3800 s.select(selections);
3801 s.clear_pending();
3802 if s.is_extending() {
3803 s.set_is_extending(false);
3804 } else {
3805 s.set_select_mode(pending_mode);
3806 }
3807 });
3808 }
3809 }
3810
3811 fn select_columns(
3812 &mut self,
3813 head: DisplayPoint,
3814 goal_column: u32,
3815 display_map: &DisplaySnapshot,
3816 window: &mut Window,
3817 cx: &mut Context<Self>,
3818 ) {
3819 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3820 return;
3821 };
3822
3823 let tail = match columnar_state {
3824 ColumnarSelectionState::FromMouse {
3825 selection_tail,
3826 display_point,
3827 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3828 ColumnarSelectionState::FromSelection { selection_tail } => {
3829 selection_tail.to_display_point(display_map)
3830 }
3831 };
3832
3833 let start_row = cmp::min(tail.row(), head.row());
3834 let end_row = cmp::max(tail.row(), head.row());
3835 let start_column = cmp::min(tail.column(), goal_column);
3836 let end_column = cmp::max(tail.column(), goal_column);
3837 let reversed = start_column < tail.column();
3838
3839 let selection_ranges = (start_row.0..=end_row.0)
3840 .map(DisplayRow)
3841 .filter_map(|row| {
3842 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3843 || start_column <= display_map.line_len(row))
3844 && !display_map.is_block_line(row)
3845 {
3846 let start = display_map
3847 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3848 .to_point(display_map);
3849 let end = display_map
3850 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3851 .to_point(display_map);
3852 if reversed {
3853 Some(end..start)
3854 } else {
3855 Some(start..end)
3856 }
3857 } else {
3858 None
3859 }
3860 })
3861 .collect::<Vec<_>>();
3862 if selection_ranges.is_empty() {
3863 return;
3864 }
3865
3866 let ranges = match columnar_state {
3867 ColumnarSelectionState::FromMouse { .. } => {
3868 let mut non_empty_ranges = selection_ranges
3869 .iter()
3870 .filter(|selection_range| selection_range.start != selection_range.end)
3871 .peekable();
3872 if non_empty_ranges.peek().is_some() {
3873 non_empty_ranges.cloned().collect()
3874 } else {
3875 selection_ranges
3876 }
3877 }
3878 _ => selection_ranges,
3879 };
3880
3881 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3882 s.select_ranges(ranges);
3883 });
3884 cx.notify();
3885 }
3886
3887 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3888 self.selections
3889 .all_adjusted(snapshot)
3890 .iter()
3891 .any(|selection| !selection.is_empty())
3892 }
3893
3894 pub fn has_pending_nonempty_selection(&self) -> bool {
3895 let pending_nonempty_selection = match self.selections.pending_anchor() {
3896 Some(Selection { start, end, .. }) => start != end,
3897 None => false,
3898 };
3899
3900 pending_nonempty_selection
3901 || (self.columnar_selection_state.is_some()
3902 && self.selections.disjoint_anchors().len() > 1)
3903 }
3904
3905 pub fn has_pending_selection(&self) -> bool {
3906 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3907 }
3908
3909 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3910 self.selection_mark_mode = false;
3911 self.selection_drag_state = SelectionDragState::None;
3912
3913 if self.clear_expanded_diff_hunks(cx) {
3914 cx.notify();
3915 return;
3916 }
3917 if self.dismiss_menus_and_popups(true, window, cx) {
3918 return;
3919 }
3920
3921 if self.mode.is_full()
3922 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3923 {
3924 return;
3925 }
3926
3927 cx.propagate();
3928 }
3929
3930 pub fn dismiss_menus_and_popups(
3931 &mut self,
3932 is_user_requested: bool,
3933 window: &mut Window,
3934 cx: &mut Context<Self>,
3935 ) -> bool {
3936 if self.take_rename(false, window, cx).is_some() {
3937 return true;
3938 }
3939
3940 if self.hide_blame_popover(true, cx) {
3941 return true;
3942 }
3943
3944 if hide_hover(self, cx) {
3945 return true;
3946 }
3947
3948 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3949 return true;
3950 }
3951
3952 if self.hide_context_menu(window, cx).is_some() {
3953 return true;
3954 }
3955
3956 if self.mouse_context_menu.take().is_some() {
3957 return true;
3958 }
3959
3960 if is_user_requested && self.discard_edit_prediction(true, cx) {
3961 return true;
3962 }
3963
3964 if self.snippet_stack.pop().is_some() {
3965 return true;
3966 }
3967
3968 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3969 self.dismiss_diagnostics(cx);
3970 return true;
3971 }
3972
3973 false
3974 }
3975
3976 fn linked_editing_ranges_for(
3977 &self,
3978 selection: Range<text::Anchor>,
3979 cx: &App,
3980 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3981 if self.linked_edit_ranges.is_empty() {
3982 return None;
3983 }
3984 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3985 selection.end.buffer_id.and_then(|end_buffer_id| {
3986 if selection.start.buffer_id != Some(end_buffer_id) {
3987 return None;
3988 }
3989 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3990 let snapshot = buffer.read(cx).snapshot();
3991 self.linked_edit_ranges
3992 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3993 .map(|ranges| (ranges, snapshot, buffer))
3994 })?;
3995 use text::ToOffset as TO;
3996 // find offset from the start of current range to current cursor position
3997 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3998
3999 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4000 let start_difference = start_offset - start_byte_offset;
4001 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4002 let end_difference = end_offset - start_byte_offset;
4003 // Current range has associated linked ranges.
4004 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4005 for range in linked_ranges.iter() {
4006 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4007 let end_offset = start_offset + end_difference;
4008 let start_offset = start_offset + start_difference;
4009 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4010 continue;
4011 }
4012 if self.selections.disjoint_anchor_ranges().any(|s| {
4013 if s.start.buffer_id != selection.start.buffer_id
4014 || s.end.buffer_id != selection.end.buffer_id
4015 {
4016 return false;
4017 }
4018 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4019 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4020 }) {
4021 continue;
4022 }
4023 let start = buffer_snapshot.anchor_after(start_offset);
4024 let end = buffer_snapshot.anchor_after(end_offset);
4025 linked_edits
4026 .entry(buffer.clone())
4027 .or_default()
4028 .push(start..end);
4029 }
4030 Some(linked_edits)
4031 }
4032
4033 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4034 let text: Arc<str> = text.into();
4035
4036 if self.read_only(cx) {
4037 return;
4038 }
4039
4040 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4041
4042 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4043 let mut bracket_inserted = false;
4044 let mut edits = Vec::new();
4045 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4046 let mut new_selections = Vec::with_capacity(selections.len());
4047 let mut new_autoclose_regions = Vec::new();
4048 let snapshot = self.buffer.read(cx).read(cx);
4049 let mut clear_linked_edit_ranges = false;
4050
4051 for (selection, autoclose_region) in
4052 self.selections_with_autoclose_regions(selections, &snapshot)
4053 {
4054 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4055 // Determine if the inserted text matches the opening or closing
4056 // bracket of any of this language's bracket pairs.
4057 let mut bracket_pair = None;
4058 let mut is_bracket_pair_start = false;
4059 let mut is_bracket_pair_end = false;
4060 if !text.is_empty() {
4061 let mut bracket_pair_matching_end = None;
4062 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4063 // and they are removing the character that triggered IME popup.
4064 for (pair, enabled) in scope.brackets() {
4065 if !pair.close && !pair.surround {
4066 continue;
4067 }
4068
4069 if enabled && pair.start.ends_with(text.as_ref()) {
4070 let prefix_len = pair.start.len() - text.len();
4071 let preceding_text_matches_prefix = prefix_len == 0
4072 || (selection.start.column >= (prefix_len as u32)
4073 && snapshot.contains_str_at(
4074 Point::new(
4075 selection.start.row,
4076 selection.start.column - (prefix_len as u32),
4077 ),
4078 &pair.start[..prefix_len],
4079 ));
4080 if preceding_text_matches_prefix {
4081 bracket_pair = Some(pair.clone());
4082 is_bracket_pair_start = true;
4083 break;
4084 }
4085 }
4086 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4087 {
4088 // take first bracket pair matching end, but don't break in case a later bracket
4089 // pair matches start
4090 bracket_pair_matching_end = Some(pair.clone());
4091 }
4092 }
4093 if let Some(end) = bracket_pair_matching_end
4094 && bracket_pair.is_none()
4095 {
4096 bracket_pair = Some(end);
4097 is_bracket_pair_end = true;
4098 }
4099 }
4100
4101 if let Some(bracket_pair) = bracket_pair {
4102 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4103 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4104 let auto_surround =
4105 self.use_auto_surround && snapshot_settings.use_auto_surround;
4106 if selection.is_empty() {
4107 if is_bracket_pair_start {
4108 // If the inserted text is a suffix of an opening bracket and the
4109 // selection is preceded by the rest of the opening bracket, then
4110 // insert the closing bracket.
4111 let following_text_allows_autoclose = snapshot
4112 .chars_at(selection.start)
4113 .next()
4114 .is_none_or(|c| scope.should_autoclose_before(c));
4115
4116 let preceding_text_allows_autoclose = selection.start.column == 0
4117 || snapshot
4118 .reversed_chars_at(selection.start)
4119 .next()
4120 .is_none_or(|c| {
4121 bracket_pair.start != bracket_pair.end
4122 || !snapshot
4123 .char_classifier_at(selection.start)
4124 .is_word(c)
4125 });
4126
4127 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4128 && bracket_pair.start.len() == 1
4129 {
4130 let target = bracket_pair.start.chars().next().unwrap();
4131 let current_line_count = snapshot
4132 .reversed_chars_at(selection.start)
4133 .take_while(|&c| c != '\n')
4134 .filter(|&c| c == target)
4135 .count();
4136 current_line_count % 2 == 1
4137 } else {
4138 false
4139 };
4140
4141 if autoclose
4142 && bracket_pair.close
4143 && following_text_allows_autoclose
4144 && preceding_text_allows_autoclose
4145 && !is_closing_quote
4146 {
4147 let anchor = snapshot.anchor_before(selection.end);
4148 new_selections.push((selection.map(|_| anchor), text.len()));
4149 new_autoclose_regions.push((
4150 anchor,
4151 text.len(),
4152 selection.id,
4153 bracket_pair.clone(),
4154 ));
4155 edits.push((
4156 selection.range(),
4157 format!("{}{}", text, bracket_pair.end).into(),
4158 ));
4159 bracket_inserted = true;
4160 continue;
4161 }
4162 }
4163
4164 if let Some(region) = autoclose_region {
4165 // If the selection is followed by an auto-inserted closing bracket,
4166 // then don't insert that closing bracket again; just move the selection
4167 // past the closing bracket.
4168 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4169 && text.as_ref() == region.pair.end.as_str()
4170 && snapshot.contains_str_at(region.range.end, text.as_ref());
4171 if should_skip {
4172 let anchor = snapshot.anchor_after(selection.end);
4173 new_selections
4174 .push((selection.map(|_| anchor), region.pair.end.len()));
4175 continue;
4176 }
4177 }
4178
4179 let always_treat_brackets_as_autoclosed = snapshot
4180 .language_settings_at(selection.start, cx)
4181 .always_treat_brackets_as_autoclosed;
4182 if always_treat_brackets_as_autoclosed
4183 && is_bracket_pair_end
4184 && snapshot.contains_str_at(selection.end, text.as_ref())
4185 {
4186 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4187 // and the inserted text is a closing bracket and the selection is followed
4188 // by the closing bracket then move the selection past the closing bracket.
4189 let anchor = snapshot.anchor_after(selection.end);
4190 new_selections.push((selection.map(|_| anchor), text.len()));
4191 continue;
4192 }
4193 }
4194 // If an opening bracket is 1 character long and is typed while
4195 // text is selected, then surround that text with the bracket pair.
4196 else if auto_surround
4197 && bracket_pair.surround
4198 && is_bracket_pair_start
4199 && bracket_pair.start.chars().count() == 1
4200 {
4201 edits.push((selection.start..selection.start, text.clone()));
4202 edits.push((
4203 selection.end..selection.end,
4204 bracket_pair.end.as_str().into(),
4205 ));
4206 bracket_inserted = true;
4207 new_selections.push((
4208 Selection {
4209 id: selection.id,
4210 start: snapshot.anchor_after(selection.start),
4211 end: snapshot.anchor_before(selection.end),
4212 reversed: selection.reversed,
4213 goal: selection.goal,
4214 },
4215 0,
4216 ));
4217 continue;
4218 }
4219 }
4220 }
4221
4222 if self.auto_replace_emoji_shortcode
4223 && selection.is_empty()
4224 && text.as_ref().ends_with(':')
4225 && let Some(possible_emoji_short_code) =
4226 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4227 && !possible_emoji_short_code.is_empty()
4228 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4229 {
4230 let emoji_shortcode_start = Point::new(
4231 selection.start.row,
4232 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4233 );
4234
4235 // Remove shortcode from buffer
4236 edits.push((
4237 emoji_shortcode_start..selection.start,
4238 "".to_string().into(),
4239 ));
4240 new_selections.push((
4241 Selection {
4242 id: selection.id,
4243 start: snapshot.anchor_after(emoji_shortcode_start),
4244 end: snapshot.anchor_before(selection.start),
4245 reversed: selection.reversed,
4246 goal: selection.goal,
4247 },
4248 0,
4249 ));
4250
4251 // Insert emoji
4252 let selection_start_anchor = snapshot.anchor_after(selection.start);
4253 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4254 edits.push((selection.start..selection.end, emoji.to_string().into()));
4255
4256 continue;
4257 }
4258
4259 // If not handling any auto-close operation, then just replace the selected
4260 // text with the given input and move the selection to the end of the
4261 // newly inserted text.
4262 let anchor = snapshot.anchor_after(selection.end);
4263 if !self.linked_edit_ranges.is_empty() {
4264 let start_anchor = snapshot.anchor_before(selection.start);
4265
4266 let is_word_char = text.chars().next().is_none_or(|char| {
4267 let classifier = snapshot
4268 .char_classifier_at(start_anchor.to_offset(&snapshot))
4269 .scope_context(Some(CharScopeContext::LinkedEdit));
4270 classifier.is_word(char)
4271 });
4272
4273 if is_word_char {
4274 if let Some(ranges) = self
4275 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4276 {
4277 for (buffer, edits) in ranges {
4278 linked_edits
4279 .entry(buffer.clone())
4280 .or_default()
4281 .extend(edits.into_iter().map(|range| (range, text.clone())));
4282 }
4283 }
4284 } else {
4285 clear_linked_edit_ranges = true;
4286 }
4287 }
4288
4289 new_selections.push((selection.map(|_| anchor), 0));
4290 edits.push((selection.start..selection.end, text.clone()));
4291 }
4292
4293 drop(snapshot);
4294
4295 self.transact(window, cx, |this, window, cx| {
4296 if clear_linked_edit_ranges {
4297 this.linked_edit_ranges.clear();
4298 }
4299 let initial_buffer_versions =
4300 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4301
4302 this.buffer.update(cx, |buffer, cx| {
4303 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4304 });
4305 for (buffer, edits) in linked_edits {
4306 buffer.update(cx, |buffer, cx| {
4307 let snapshot = buffer.snapshot();
4308 let edits = edits
4309 .into_iter()
4310 .map(|(range, text)| {
4311 use text::ToPoint as TP;
4312 let end_point = TP::to_point(&range.end, &snapshot);
4313 let start_point = TP::to_point(&range.start, &snapshot);
4314 (start_point..end_point, text)
4315 })
4316 .sorted_by_key(|(range, _)| range.start);
4317 buffer.edit(edits, None, cx);
4318 })
4319 }
4320 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4321 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4322 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4323 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4324 .zip(new_selection_deltas)
4325 .map(|(selection, delta)| Selection {
4326 id: selection.id,
4327 start: selection.start + delta,
4328 end: selection.end + delta,
4329 reversed: selection.reversed,
4330 goal: SelectionGoal::None,
4331 })
4332 .collect::<Vec<_>>();
4333
4334 let mut i = 0;
4335 for (position, delta, selection_id, pair) in new_autoclose_regions {
4336 let position = position.to_offset(map.buffer_snapshot()) + delta;
4337 let start = map.buffer_snapshot().anchor_before(position);
4338 let end = map.buffer_snapshot().anchor_after(position);
4339 while let Some(existing_state) = this.autoclose_regions.get(i) {
4340 match existing_state
4341 .range
4342 .start
4343 .cmp(&start, map.buffer_snapshot())
4344 {
4345 Ordering::Less => i += 1,
4346 Ordering::Greater => break,
4347 Ordering::Equal => {
4348 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4349 Ordering::Less => i += 1,
4350 Ordering::Equal => break,
4351 Ordering::Greater => break,
4352 }
4353 }
4354 }
4355 }
4356 this.autoclose_regions.insert(
4357 i,
4358 AutocloseRegion {
4359 selection_id,
4360 range: start..end,
4361 pair,
4362 },
4363 );
4364 }
4365
4366 let had_active_edit_prediction = this.has_active_edit_prediction();
4367 this.change_selections(
4368 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4369 window,
4370 cx,
4371 |s| s.select(new_selections),
4372 );
4373
4374 if !bracket_inserted
4375 && let Some(on_type_format_task) =
4376 this.trigger_on_type_formatting(text.to_string(), window, cx)
4377 {
4378 on_type_format_task.detach_and_log_err(cx);
4379 }
4380
4381 let editor_settings = EditorSettings::get_global(cx);
4382 if bracket_inserted
4383 && (editor_settings.auto_signature_help
4384 || editor_settings.show_signature_help_after_edits)
4385 {
4386 this.show_signature_help(&ShowSignatureHelp, window, cx);
4387 }
4388
4389 let trigger_in_words =
4390 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4391 if this.hard_wrap.is_some() {
4392 let latest: Range<Point> = this.selections.newest(&map).range();
4393 if latest.is_empty()
4394 && this
4395 .buffer()
4396 .read(cx)
4397 .snapshot(cx)
4398 .line_len(MultiBufferRow(latest.start.row))
4399 == latest.start.column
4400 {
4401 this.rewrap_impl(
4402 RewrapOptions {
4403 override_language_settings: true,
4404 preserve_existing_whitespace: true,
4405 },
4406 cx,
4407 )
4408 }
4409 }
4410 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4411 refresh_linked_ranges(this, window, cx);
4412 this.refresh_edit_prediction(true, false, window, cx);
4413 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4414 });
4415 }
4416
4417 fn find_possible_emoji_shortcode_at_position(
4418 snapshot: &MultiBufferSnapshot,
4419 position: Point,
4420 ) -> Option<String> {
4421 let mut chars = Vec::new();
4422 let mut found_colon = false;
4423 for char in snapshot.reversed_chars_at(position).take(100) {
4424 // Found a possible emoji shortcode in the middle of the buffer
4425 if found_colon {
4426 if char.is_whitespace() {
4427 chars.reverse();
4428 return Some(chars.iter().collect());
4429 }
4430 // If the previous character is not a whitespace, we are in the middle of a word
4431 // and we only want to complete the shortcode if the word is made up of other emojis
4432 let mut containing_word = String::new();
4433 for ch in snapshot
4434 .reversed_chars_at(position)
4435 .skip(chars.len() + 1)
4436 .take(100)
4437 {
4438 if ch.is_whitespace() {
4439 break;
4440 }
4441 containing_word.push(ch);
4442 }
4443 let containing_word = containing_word.chars().rev().collect::<String>();
4444 if util::word_consists_of_emojis(containing_word.as_str()) {
4445 chars.reverse();
4446 return Some(chars.iter().collect());
4447 }
4448 }
4449
4450 if char.is_whitespace() || !char.is_ascii() {
4451 return None;
4452 }
4453 if char == ':' {
4454 found_colon = true;
4455 } else {
4456 chars.push(char);
4457 }
4458 }
4459 // Found a possible emoji shortcode at the beginning of the buffer
4460 chars.reverse();
4461 Some(chars.iter().collect())
4462 }
4463
4464 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4465 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4466 self.transact(window, cx, |this, window, cx| {
4467 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4468 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4469 let multi_buffer = this.buffer.read(cx);
4470 let buffer = multi_buffer.snapshot(cx);
4471 selections
4472 .iter()
4473 .map(|selection| {
4474 let start_point = selection.start.to_point(&buffer);
4475 let mut existing_indent =
4476 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4477 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4478 let start = selection.start;
4479 let end = selection.end;
4480 let selection_is_empty = start == end;
4481 let language_scope = buffer.language_scope_at(start);
4482 let (
4483 comment_delimiter,
4484 doc_delimiter,
4485 insert_extra_newline,
4486 indent_on_newline,
4487 indent_on_extra_newline,
4488 ) = if let Some(language) = &language_scope {
4489 let mut insert_extra_newline =
4490 insert_extra_newline_brackets(&buffer, start..end, language)
4491 || insert_extra_newline_tree_sitter(&buffer, start..end);
4492
4493 // Comment extension on newline is allowed only for cursor selections
4494 let comment_delimiter = maybe!({
4495 if !selection_is_empty {
4496 return None;
4497 }
4498
4499 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4500 return None;
4501 }
4502
4503 let delimiters = language.line_comment_prefixes();
4504 let max_len_of_delimiter =
4505 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4506 let (snapshot, range) =
4507 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4508
4509 let num_of_whitespaces = snapshot
4510 .chars_for_range(range.clone())
4511 .take_while(|c| c.is_whitespace())
4512 .count();
4513 let comment_candidate = snapshot
4514 .chars_for_range(range.clone())
4515 .skip(num_of_whitespaces)
4516 .take(max_len_of_delimiter)
4517 .collect::<String>();
4518 let (delimiter, trimmed_len) = delimiters
4519 .iter()
4520 .filter_map(|delimiter| {
4521 let prefix = delimiter.trim_end();
4522 if comment_candidate.starts_with(prefix) {
4523 Some((delimiter, prefix.len()))
4524 } else {
4525 None
4526 }
4527 })
4528 .max_by_key(|(_, len)| *len)?;
4529
4530 if let Some(BlockCommentConfig {
4531 start: block_start, ..
4532 }) = language.block_comment()
4533 {
4534 let block_start_trimmed = block_start.trim_end();
4535 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4536 let line_content = snapshot
4537 .chars_for_range(range)
4538 .skip(num_of_whitespaces)
4539 .take(block_start_trimmed.len())
4540 .collect::<String>();
4541
4542 if line_content.starts_with(block_start_trimmed) {
4543 return None;
4544 }
4545 }
4546 }
4547
4548 let cursor_is_placed_after_comment_marker =
4549 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4550 if cursor_is_placed_after_comment_marker {
4551 Some(delimiter.clone())
4552 } else {
4553 None
4554 }
4555 });
4556
4557 let mut indent_on_newline = IndentSize::spaces(0);
4558 let mut indent_on_extra_newline = IndentSize::spaces(0);
4559
4560 let doc_delimiter = maybe!({
4561 if !selection_is_empty {
4562 return None;
4563 }
4564
4565 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4566 return None;
4567 }
4568
4569 let BlockCommentConfig {
4570 start: start_tag,
4571 end: end_tag,
4572 prefix: delimiter,
4573 tab_size: len,
4574 } = language.documentation_comment()?;
4575 let is_within_block_comment = buffer
4576 .language_scope_at(start_point)
4577 .is_some_and(|scope| scope.override_name() == Some("comment"));
4578 if !is_within_block_comment {
4579 return None;
4580 }
4581
4582 let (snapshot, range) =
4583 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4584
4585 let num_of_whitespaces = snapshot
4586 .chars_for_range(range.clone())
4587 .take_while(|c| c.is_whitespace())
4588 .count();
4589
4590 // 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.
4591 let column = start_point.column;
4592 let cursor_is_after_start_tag = {
4593 let start_tag_len = start_tag.len();
4594 let start_tag_line = snapshot
4595 .chars_for_range(range.clone())
4596 .skip(num_of_whitespaces)
4597 .take(start_tag_len)
4598 .collect::<String>();
4599 if start_tag_line.starts_with(start_tag.as_ref()) {
4600 num_of_whitespaces + start_tag_len <= column as usize
4601 } else {
4602 false
4603 }
4604 };
4605
4606 let cursor_is_after_delimiter = {
4607 let delimiter_trim = delimiter.trim_end();
4608 let delimiter_line = snapshot
4609 .chars_for_range(range.clone())
4610 .skip(num_of_whitespaces)
4611 .take(delimiter_trim.len())
4612 .collect::<String>();
4613 if delimiter_line.starts_with(delimiter_trim) {
4614 num_of_whitespaces + delimiter_trim.len() <= column as usize
4615 } else {
4616 false
4617 }
4618 };
4619
4620 let cursor_is_before_end_tag_if_exists = {
4621 let mut char_position = 0u32;
4622 let mut end_tag_offset = None;
4623
4624 'outer: for chunk in snapshot.text_for_range(range) {
4625 if let Some(byte_pos) = chunk.find(&**end_tag) {
4626 let chars_before_match =
4627 chunk[..byte_pos].chars().count() as u32;
4628 end_tag_offset =
4629 Some(char_position + chars_before_match);
4630 break 'outer;
4631 }
4632 char_position += chunk.chars().count() as u32;
4633 }
4634
4635 if let Some(end_tag_offset) = end_tag_offset {
4636 let cursor_is_before_end_tag = column <= end_tag_offset;
4637 if cursor_is_after_start_tag {
4638 if cursor_is_before_end_tag {
4639 insert_extra_newline = true;
4640 }
4641 let cursor_is_at_start_of_end_tag =
4642 column == end_tag_offset;
4643 if cursor_is_at_start_of_end_tag {
4644 indent_on_extra_newline.len = *len;
4645 }
4646 }
4647 cursor_is_before_end_tag
4648 } else {
4649 true
4650 }
4651 };
4652
4653 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4654 && cursor_is_before_end_tag_if_exists
4655 {
4656 if cursor_is_after_start_tag {
4657 indent_on_newline.len = *len;
4658 }
4659 Some(delimiter.clone())
4660 } else {
4661 None
4662 }
4663 });
4664
4665 (
4666 comment_delimiter,
4667 doc_delimiter,
4668 insert_extra_newline,
4669 indent_on_newline,
4670 indent_on_extra_newline,
4671 )
4672 } else {
4673 (
4674 None,
4675 None,
4676 false,
4677 IndentSize::default(),
4678 IndentSize::default(),
4679 )
4680 };
4681
4682 let prevent_auto_indent = doc_delimiter.is_some();
4683 let delimiter = comment_delimiter.or(doc_delimiter);
4684
4685 let capacity_for_delimiter =
4686 delimiter.as_deref().map(str::len).unwrap_or_default();
4687 let mut new_text = String::with_capacity(
4688 1 + capacity_for_delimiter
4689 + existing_indent.len as usize
4690 + indent_on_newline.len as usize
4691 + indent_on_extra_newline.len as usize,
4692 );
4693 new_text.push('\n');
4694 new_text.extend(existing_indent.chars());
4695 new_text.extend(indent_on_newline.chars());
4696
4697 if let Some(delimiter) = &delimiter {
4698 new_text.push_str(delimiter);
4699 }
4700
4701 if insert_extra_newline {
4702 new_text.push('\n');
4703 new_text.extend(existing_indent.chars());
4704 new_text.extend(indent_on_extra_newline.chars());
4705 }
4706
4707 let anchor = buffer.anchor_after(end);
4708 let new_selection = selection.map(|_| anchor);
4709 (
4710 ((start..end, new_text), prevent_auto_indent),
4711 (insert_extra_newline, new_selection),
4712 )
4713 })
4714 .unzip()
4715 };
4716
4717 let mut auto_indent_edits = Vec::new();
4718 let mut edits = Vec::new();
4719 for (edit, prevent_auto_indent) in edits_with_flags {
4720 if prevent_auto_indent {
4721 edits.push(edit);
4722 } else {
4723 auto_indent_edits.push(edit);
4724 }
4725 }
4726 if !edits.is_empty() {
4727 this.edit(edits, cx);
4728 }
4729 if !auto_indent_edits.is_empty() {
4730 this.edit_with_autoindent(auto_indent_edits, cx);
4731 }
4732
4733 let buffer = this.buffer.read(cx).snapshot(cx);
4734 let new_selections = selection_info
4735 .into_iter()
4736 .map(|(extra_newline_inserted, new_selection)| {
4737 let mut cursor = new_selection.end.to_point(&buffer);
4738 if extra_newline_inserted {
4739 cursor.row -= 1;
4740 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4741 }
4742 new_selection.map(|_| cursor)
4743 })
4744 .collect();
4745
4746 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4747 this.refresh_edit_prediction(true, false, window, cx);
4748 });
4749 }
4750
4751 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4752 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4753
4754 let buffer = self.buffer.read(cx);
4755 let snapshot = buffer.snapshot(cx);
4756
4757 let mut edits = Vec::new();
4758 let mut rows = Vec::new();
4759
4760 for (rows_inserted, selection) in self
4761 .selections
4762 .all_adjusted(&self.display_snapshot(cx))
4763 .into_iter()
4764 .enumerate()
4765 {
4766 let cursor = selection.head();
4767 let row = cursor.row;
4768
4769 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4770
4771 let newline = "\n".to_string();
4772 edits.push((start_of_line..start_of_line, newline));
4773
4774 rows.push(row + rows_inserted as u32);
4775 }
4776
4777 self.transact(window, cx, |editor, window, cx| {
4778 editor.edit(edits, cx);
4779
4780 editor.change_selections(Default::default(), window, cx, |s| {
4781 let mut index = 0;
4782 s.move_cursors_with(|map, _, _| {
4783 let row = rows[index];
4784 index += 1;
4785
4786 let point = Point::new(row, 0);
4787 let boundary = map.next_line_boundary(point).1;
4788 let clipped = map.clip_point(boundary, Bias::Left);
4789
4790 (clipped, SelectionGoal::None)
4791 });
4792 });
4793
4794 let mut indent_edits = Vec::new();
4795 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4796 for row in rows {
4797 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4798 for (row, indent) in indents {
4799 if indent.len == 0 {
4800 continue;
4801 }
4802
4803 let text = match indent.kind {
4804 IndentKind::Space => " ".repeat(indent.len as usize),
4805 IndentKind::Tab => "\t".repeat(indent.len as usize),
4806 };
4807 let point = Point::new(row.0, 0);
4808 indent_edits.push((point..point, text));
4809 }
4810 }
4811 editor.edit(indent_edits, cx);
4812 });
4813 }
4814
4815 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4816 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4817
4818 let buffer = self.buffer.read(cx);
4819 let snapshot = buffer.snapshot(cx);
4820
4821 let mut edits = Vec::new();
4822 let mut rows = Vec::new();
4823 let mut rows_inserted = 0;
4824
4825 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4826 let cursor = selection.head();
4827 let row = cursor.row;
4828
4829 let point = Point::new(row + 1, 0);
4830 let start_of_line = snapshot.clip_point(point, Bias::Left);
4831
4832 let newline = "\n".to_string();
4833 edits.push((start_of_line..start_of_line, newline));
4834
4835 rows_inserted += 1;
4836 rows.push(row + rows_inserted);
4837 }
4838
4839 self.transact(window, cx, |editor, window, cx| {
4840 editor.edit(edits, cx);
4841
4842 editor.change_selections(Default::default(), window, cx, |s| {
4843 let mut index = 0;
4844 s.move_cursors_with(|map, _, _| {
4845 let row = rows[index];
4846 index += 1;
4847
4848 let point = Point::new(row, 0);
4849 let boundary = map.next_line_boundary(point).1;
4850 let clipped = map.clip_point(boundary, Bias::Left);
4851
4852 (clipped, SelectionGoal::None)
4853 });
4854 });
4855
4856 let mut indent_edits = Vec::new();
4857 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4858 for row in rows {
4859 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4860 for (row, indent) in indents {
4861 if indent.len == 0 {
4862 continue;
4863 }
4864
4865 let text = match indent.kind {
4866 IndentKind::Space => " ".repeat(indent.len as usize),
4867 IndentKind::Tab => "\t".repeat(indent.len as usize),
4868 };
4869 let point = Point::new(row.0, 0);
4870 indent_edits.push((point..point, text));
4871 }
4872 }
4873 editor.edit(indent_edits, cx);
4874 });
4875 }
4876
4877 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4878 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4879 original_indent_columns: Vec::new(),
4880 });
4881 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4882 }
4883
4884 fn insert_with_autoindent_mode(
4885 &mut self,
4886 text: &str,
4887 autoindent_mode: Option<AutoindentMode>,
4888 window: &mut Window,
4889 cx: &mut Context<Self>,
4890 ) {
4891 if self.read_only(cx) {
4892 return;
4893 }
4894
4895 let text: Arc<str> = text.into();
4896 self.transact(window, cx, |this, window, cx| {
4897 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4898 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4899 let anchors = {
4900 let snapshot = buffer.read(cx);
4901 old_selections
4902 .iter()
4903 .map(|s| {
4904 let anchor = snapshot.anchor_after(s.head());
4905 s.map(|_| anchor)
4906 })
4907 .collect::<Vec<_>>()
4908 };
4909 buffer.edit(
4910 old_selections
4911 .iter()
4912 .map(|s| (s.start..s.end, text.clone())),
4913 autoindent_mode,
4914 cx,
4915 );
4916 anchors
4917 });
4918
4919 this.change_selections(Default::default(), window, cx, |s| {
4920 s.select_anchors(selection_anchors);
4921 });
4922
4923 cx.notify();
4924 });
4925 }
4926
4927 fn trigger_completion_on_input(
4928 &mut self,
4929 text: &str,
4930 trigger_in_words: bool,
4931 window: &mut Window,
4932 cx: &mut Context<Self>,
4933 ) {
4934 let completions_source = self
4935 .context_menu
4936 .borrow()
4937 .as_ref()
4938 .and_then(|menu| match menu {
4939 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4940 CodeContextMenu::CodeActions(_) => None,
4941 });
4942
4943 match completions_source {
4944 Some(CompletionsMenuSource::Words { .. }) => {
4945 self.open_or_update_completions_menu(
4946 Some(CompletionsMenuSource::Words {
4947 ignore_threshold: false,
4948 }),
4949 None,
4950 window,
4951 cx,
4952 );
4953 }
4954 Some(CompletionsMenuSource::Normal)
4955 | Some(CompletionsMenuSource::SnippetChoices)
4956 | None
4957 if self.is_completion_trigger(
4958 text,
4959 trigger_in_words,
4960 completions_source.is_some(),
4961 cx,
4962 ) =>
4963 {
4964 self.show_completions(
4965 &ShowCompletions {
4966 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4967 },
4968 window,
4969 cx,
4970 )
4971 }
4972 _ => {
4973 self.hide_context_menu(window, cx);
4974 }
4975 }
4976 }
4977
4978 fn is_completion_trigger(
4979 &self,
4980 text: &str,
4981 trigger_in_words: bool,
4982 menu_is_open: bool,
4983 cx: &mut Context<Self>,
4984 ) -> bool {
4985 let position = self.selections.newest_anchor().head();
4986 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4987 return false;
4988 };
4989
4990 if let Some(completion_provider) = &self.completion_provider {
4991 completion_provider.is_completion_trigger(
4992 &buffer,
4993 position.text_anchor,
4994 text,
4995 trigger_in_words,
4996 menu_is_open,
4997 cx,
4998 )
4999 } else {
5000 false
5001 }
5002 }
5003
5004 /// If any empty selections is touching the start of its innermost containing autoclose
5005 /// region, expand it to select the brackets.
5006 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5007 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5008 let buffer = self.buffer.read(cx).read(cx);
5009 let new_selections = self
5010 .selections_with_autoclose_regions(selections, &buffer)
5011 .map(|(mut selection, region)| {
5012 if !selection.is_empty() {
5013 return selection;
5014 }
5015
5016 if let Some(region) = region {
5017 let mut range = region.range.to_offset(&buffer);
5018 if selection.start == range.start && range.start >= region.pair.start.len() {
5019 range.start -= region.pair.start.len();
5020 if buffer.contains_str_at(range.start, ®ion.pair.start)
5021 && buffer.contains_str_at(range.end, ®ion.pair.end)
5022 {
5023 range.end += region.pair.end.len();
5024 selection.start = range.start;
5025 selection.end = range.end;
5026
5027 return selection;
5028 }
5029 }
5030 }
5031
5032 let always_treat_brackets_as_autoclosed = buffer
5033 .language_settings_at(selection.start, cx)
5034 .always_treat_brackets_as_autoclosed;
5035
5036 if !always_treat_brackets_as_autoclosed {
5037 return selection;
5038 }
5039
5040 if let Some(scope) = buffer.language_scope_at(selection.start) {
5041 for (pair, enabled) in scope.brackets() {
5042 if !enabled || !pair.close {
5043 continue;
5044 }
5045
5046 if buffer.contains_str_at(selection.start, &pair.end) {
5047 let pair_start_len = pair.start.len();
5048 if buffer.contains_str_at(
5049 selection.start.saturating_sub(pair_start_len),
5050 &pair.start,
5051 ) {
5052 selection.start -= pair_start_len;
5053 selection.end += pair.end.len();
5054
5055 return selection;
5056 }
5057 }
5058 }
5059 }
5060
5061 selection
5062 })
5063 .collect();
5064
5065 drop(buffer);
5066 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5067 selections.select(new_selections)
5068 });
5069 }
5070
5071 /// Iterate the given selections, and for each one, find the smallest surrounding
5072 /// autoclose region. This uses the ordering of the selections and the autoclose
5073 /// regions to avoid repeated comparisons.
5074 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5075 &'a self,
5076 selections: impl IntoIterator<Item = Selection<D>>,
5077 buffer: &'a MultiBufferSnapshot,
5078 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5079 let mut i = 0;
5080 let mut regions = self.autoclose_regions.as_slice();
5081 selections.into_iter().map(move |selection| {
5082 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5083
5084 let mut enclosing = None;
5085 while let Some(pair_state) = regions.get(i) {
5086 if pair_state.range.end.to_offset(buffer) < range.start {
5087 regions = ®ions[i + 1..];
5088 i = 0;
5089 } else if pair_state.range.start.to_offset(buffer) > range.end {
5090 break;
5091 } else {
5092 if pair_state.selection_id == selection.id {
5093 enclosing = Some(pair_state);
5094 }
5095 i += 1;
5096 }
5097 }
5098
5099 (selection, enclosing)
5100 })
5101 }
5102
5103 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5104 fn invalidate_autoclose_regions(
5105 &mut self,
5106 mut selections: &[Selection<Anchor>],
5107 buffer: &MultiBufferSnapshot,
5108 ) {
5109 self.autoclose_regions.retain(|state| {
5110 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5111 return false;
5112 }
5113
5114 let mut i = 0;
5115 while let Some(selection) = selections.get(i) {
5116 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5117 selections = &selections[1..];
5118 continue;
5119 }
5120 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5121 break;
5122 }
5123 if selection.id == state.selection_id {
5124 return true;
5125 } else {
5126 i += 1;
5127 }
5128 }
5129 false
5130 });
5131 }
5132
5133 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5134 let offset = position.to_offset(buffer);
5135 let (word_range, kind) =
5136 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5137 if offset > word_range.start && kind == Some(CharKind::Word) {
5138 Some(
5139 buffer
5140 .text_for_range(word_range.start..offset)
5141 .collect::<String>(),
5142 )
5143 } else {
5144 None
5145 }
5146 }
5147
5148 pub fn visible_excerpts(
5149 &self,
5150 cx: &mut Context<Editor>,
5151 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5152 let Some(project) = self.project() else {
5153 return HashMap::default();
5154 };
5155 let project = project.read(cx);
5156 let multi_buffer = self.buffer().read(cx);
5157 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5158 let multi_buffer_visible_start = self
5159 .scroll_manager
5160 .anchor()
5161 .anchor
5162 .to_point(&multi_buffer_snapshot);
5163 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5164 multi_buffer_visible_start
5165 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5166 Bias::Left,
5167 );
5168 multi_buffer_snapshot
5169 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5170 .into_iter()
5171 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5172 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5173 let buffer_file = project::File::from_dyn(buffer.file())?;
5174 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5175 let worktree_entry = buffer_worktree
5176 .read(cx)
5177 .entry_for_id(buffer_file.project_entry_id()?)?;
5178 if worktree_entry.is_ignored {
5179 None
5180 } else {
5181 Some((
5182 excerpt_id,
5183 (
5184 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5185 buffer.version().clone(),
5186 excerpt_visible_range,
5187 ),
5188 ))
5189 }
5190 })
5191 .collect()
5192 }
5193
5194 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5195 TextLayoutDetails {
5196 text_system: window.text_system().clone(),
5197 editor_style: self.style.clone().unwrap(),
5198 rem_size: window.rem_size(),
5199 scroll_anchor: self.scroll_manager.anchor(),
5200 visible_rows: self.visible_line_count(),
5201 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5202 }
5203 }
5204
5205 fn trigger_on_type_formatting(
5206 &self,
5207 input: String,
5208 window: &mut Window,
5209 cx: &mut Context<Self>,
5210 ) -> Option<Task<Result<()>>> {
5211 if input.len() != 1 {
5212 return None;
5213 }
5214
5215 let project = self.project()?;
5216 let position = self.selections.newest_anchor().head();
5217 let (buffer, buffer_position) = self
5218 .buffer
5219 .read(cx)
5220 .text_anchor_for_position(position, cx)?;
5221
5222 let settings = language_settings::language_settings(
5223 buffer
5224 .read(cx)
5225 .language_at(buffer_position)
5226 .map(|l| l.name()),
5227 buffer.read(cx).file(),
5228 cx,
5229 );
5230 if !settings.use_on_type_format {
5231 return None;
5232 }
5233
5234 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5235 // hence we do LSP request & edit on host side only — add formats to host's history.
5236 let push_to_lsp_host_history = true;
5237 // If this is not the host, append its history with new edits.
5238 let push_to_client_history = project.read(cx).is_via_collab();
5239
5240 let on_type_formatting = project.update(cx, |project, cx| {
5241 project.on_type_format(
5242 buffer.clone(),
5243 buffer_position,
5244 input,
5245 push_to_lsp_host_history,
5246 cx,
5247 )
5248 });
5249 Some(cx.spawn_in(window, async move |editor, cx| {
5250 if let Some(transaction) = on_type_formatting.await? {
5251 if push_to_client_history {
5252 buffer
5253 .update(cx, |buffer, _| {
5254 buffer.push_transaction(transaction, Instant::now());
5255 buffer.finalize_last_transaction();
5256 })
5257 .ok();
5258 }
5259 editor.update(cx, |editor, cx| {
5260 editor.refresh_document_highlights(cx);
5261 })?;
5262 }
5263 Ok(())
5264 }))
5265 }
5266
5267 pub fn show_word_completions(
5268 &mut self,
5269 _: &ShowWordCompletions,
5270 window: &mut Window,
5271 cx: &mut Context<Self>,
5272 ) {
5273 self.open_or_update_completions_menu(
5274 Some(CompletionsMenuSource::Words {
5275 ignore_threshold: true,
5276 }),
5277 None,
5278 window,
5279 cx,
5280 );
5281 }
5282
5283 pub fn show_completions(
5284 &mut self,
5285 options: &ShowCompletions,
5286 window: &mut Window,
5287 cx: &mut Context<Self>,
5288 ) {
5289 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5290 }
5291
5292 fn open_or_update_completions_menu(
5293 &mut self,
5294 requested_source: Option<CompletionsMenuSource>,
5295 trigger: Option<&str>,
5296 window: &mut Window,
5297 cx: &mut Context<Self>,
5298 ) {
5299 if self.pending_rename.is_some() {
5300 return;
5301 }
5302
5303 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5304
5305 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5306 // inserted and selected. To handle that case, the start of the selection is used so that
5307 // the menu starts with all choices.
5308 let position = self
5309 .selections
5310 .newest_anchor()
5311 .start
5312 .bias_right(&multibuffer_snapshot);
5313 if position.diff_base_anchor.is_some() {
5314 return;
5315 }
5316 let buffer_position = multibuffer_snapshot.anchor_before(position);
5317 let Some(buffer) = buffer_position
5318 .buffer_id
5319 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5320 else {
5321 return;
5322 };
5323 let buffer_snapshot = buffer.read(cx).snapshot();
5324
5325 let query: Option<Arc<String>> =
5326 Self::completion_query(&multibuffer_snapshot, buffer_position)
5327 .map(|query| query.into());
5328
5329 drop(multibuffer_snapshot);
5330
5331 // Hide the current completions menu when query is empty. Without this, cached
5332 // completions from before the trigger char may be reused (#32774).
5333 if query.is_none() {
5334 let menu_is_open = matches!(
5335 self.context_menu.borrow().as_ref(),
5336 Some(CodeContextMenu::Completions(_))
5337 );
5338 if menu_is_open {
5339 self.hide_context_menu(window, cx);
5340 }
5341 }
5342
5343 let mut ignore_word_threshold = false;
5344 let provider = match requested_source {
5345 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5346 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5347 ignore_word_threshold = ignore_threshold;
5348 None
5349 }
5350 Some(CompletionsMenuSource::SnippetChoices) => {
5351 log::error!("bug: SnippetChoices requested_source is not handled");
5352 None
5353 }
5354 };
5355
5356 let sort_completions = provider
5357 .as_ref()
5358 .is_some_and(|provider| provider.sort_completions());
5359
5360 let filter_completions = provider
5361 .as_ref()
5362 .is_none_or(|provider| provider.filter_completions());
5363
5364 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5365 if filter_completions {
5366 menu.filter(query.clone(), provider.clone(), window, cx);
5367 }
5368 // When `is_incomplete` is false, no need to re-query completions when the current query
5369 // is a suffix of the initial query.
5370 if !menu.is_incomplete {
5371 // If the new query is a suffix of the old query (typing more characters) and
5372 // the previous result was complete, the existing completions can be filtered.
5373 //
5374 // Note that this is always true for snippet completions.
5375 let query_matches = match (&menu.initial_query, &query) {
5376 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5377 (None, _) => true,
5378 _ => false,
5379 };
5380 if query_matches {
5381 let position_matches = if menu.initial_position == position {
5382 true
5383 } else {
5384 let snapshot = self.buffer.read(cx).read(cx);
5385 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5386 };
5387 if position_matches {
5388 return;
5389 }
5390 }
5391 }
5392 };
5393
5394 let trigger_kind = match trigger {
5395 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5396 CompletionTriggerKind::TRIGGER_CHARACTER
5397 }
5398 _ => CompletionTriggerKind::INVOKED,
5399 };
5400 let completion_context = CompletionContext {
5401 trigger_character: trigger.and_then(|trigger| {
5402 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5403 Some(String::from(trigger))
5404 } else {
5405 None
5406 }
5407 }),
5408 trigger_kind,
5409 };
5410
5411 let Anchor {
5412 excerpt_id: buffer_excerpt_id,
5413 text_anchor: buffer_position,
5414 ..
5415 } = buffer_position;
5416
5417 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5418 buffer_snapshot.surrounding_word(buffer_position, None)
5419 {
5420 let word_to_exclude = buffer_snapshot
5421 .text_for_range(word_range.clone())
5422 .collect::<String>();
5423 (
5424 buffer_snapshot.anchor_before(word_range.start)
5425 ..buffer_snapshot.anchor_after(buffer_position),
5426 Some(word_to_exclude),
5427 )
5428 } else {
5429 (buffer_position..buffer_position, None)
5430 };
5431
5432 let language = buffer_snapshot
5433 .language_at(buffer_position)
5434 .map(|language| language.name());
5435
5436 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5437 .completions
5438 .clone();
5439
5440 let show_completion_documentation = buffer_snapshot
5441 .settings_at(buffer_position, cx)
5442 .show_completion_documentation;
5443
5444 // The document can be large, so stay in reasonable bounds when searching for words,
5445 // otherwise completion pop-up might be slow to appear.
5446 const WORD_LOOKUP_ROWS: u32 = 5_000;
5447 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5448 let min_word_search = buffer_snapshot.clip_point(
5449 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5450 Bias::Left,
5451 );
5452 let max_word_search = buffer_snapshot.clip_point(
5453 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5454 Bias::Right,
5455 );
5456 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5457 ..buffer_snapshot.point_to_offset(max_word_search);
5458
5459 let skip_digits = query
5460 .as_ref()
5461 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5462
5463 let omit_word_completions = !self.word_completions_enabled
5464 || (!ignore_word_threshold
5465 && match &query {
5466 Some(query) => query.chars().count() < completion_settings.words_min_length,
5467 None => completion_settings.words_min_length != 0,
5468 });
5469
5470 let (mut words, provider_responses) = match &provider {
5471 Some(provider) => {
5472 let provider_responses = provider.completions(
5473 buffer_excerpt_id,
5474 &buffer,
5475 buffer_position,
5476 completion_context,
5477 window,
5478 cx,
5479 );
5480
5481 let words = match (omit_word_completions, completion_settings.words) {
5482 (true, _) | (_, WordsCompletionMode::Disabled) => {
5483 Task::ready(BTreeMap::default())
5484 }
5485 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5486 .background_spawn(async move {
5487 buffer_snapshot.words_in_range(WordsQuery {
5488 fuzzy_contents: None,
5489 range: word_search_range,
5490 skip_digits,
5491 })
5492 }),
5493 };
5494
5495 (words, provider_responses)
5496 }
5497 None => {
5498 let words = if omit_word_completions {
5499 Task::ready(BTreeMap::default())
5500 } else {
5501 cx.background_spawn(async move {
5502 buffer_snapshot.words_in_range(WordsQuery {
5503 fuzzy_contents: None,
5504 range: word_search_range,
5505 skip_digits,
5506 })
5507 })
5508 };
5509 (words, Task::ready(Ok(Vec::new())))
5510 }
5511 };
5512
5513 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5514
5515 let id = post_inc(&mut self.next_completion_id);
5516 let task = cx.spawn_in(window, async move |editor, cx| {
5517 let Ok(()) = editor.update(cx, |this, _| {
5518 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5519 }) else {
5520 return;
5521 };
5522
5523 // TODO: Ideally completions from different sources would be selectively re-queried, so
5524 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5525 let mut completions = Vec::new();
5526 let mut is_incomplete = false;
5527 let mut display_options: Option<CompletionDisplayOptions> = None;
5528 if let Some(provider_responses) = provider_responses.await.log_err()
5529 && !provider_responses.is_empty()
5530 {
5531 for response in provider_responses {
5532 completions.extend(response.completions);
5533 is_incomplete = is_incomplete || response.is_incomplete;
5534 match display_options.as_mut() {
5535 None => {
5536 display_options = Some(response.display_options);
5537 }
5538 Some(options) => options.merge(&response.display_options),
5539 }
5540 }
5541 if completion_settings.words == WordsCompletionMode::Fallback {
5542 words = Task::ready(BTreeMap::default());
5543 }
5544 }
5545 let display_options = display_options.unwrap_or_default();
5546
5547 let mut words = words.await;
5548 if let Some(word_to_exclude) = &word_to_exclude {
5549 words.remove(word_to_exclude);
5550 }
5551 for lsp_completion in &completions {
5552 words.remove(&lsp_completion.new_text);
5553 }
5554 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5555 replace_range: word_replace_range.clone(),
5556 new_text: word.clone(),
5557 label: CodeLabel::plain(word, None),
5558 icon_path: None,
5559 documentation: None,
5560 source: CompletionSource::BufferWord {
5561 word_range,
5562 resolved: false,
5563 },
5564 insert_text_mode: Some(InsertTextMode::AS_IS),
5565 confirm: None,
5566 }));
5567
5568 let menu = if completions.is_empty() {
5569 None
5570 } else {
5571 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5572 let languages = editor
5573 .workspace
5574 .as_ref()
5575 .and_then(|(workspace, _)| workspace.upgrade())
5576 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5577 let menu = CompletionsMenu::new(
5578 id,
5579 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5580 sort_completions,
5581 show_completion_documentation,
5582 position,
5583 query.clone(),
5584 is_incomplete,
5585 buffer.clone(),
5586 completions.into(),
5587 display_options,
5588 snippet_sort_order,
5589 languages,
5590 language,
5591 cx,
5592 );
5593
5594 let query = if filter_completions { query } else { None };
5595 let matches_task = if let Some(query) = query {
5596 menu.do_async_filtering(query, cx)
5597 } else {
5598 Task::ready(menu.unfiltered_matches())
5599 };
5600 (menu, matches_task)
5601 }) else {
5602 return;
5603 };
5604
5605 let matches = matches_task.await;
5606
5607 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5608 // Newer menu already set, so exit.
5609 if let Some(CodeContextMenu::Completions(prev_menu)) =
5610 editor.context_menu.borrow().as_ref()
5611 && prev_menu.id > id
5612 {
5613 return;
5614 };
5615
5616 // Only valid to take prev_menu because it the new menu is immediately set
5617 // below, or the menu is hidden.
5618 if let Some(CodeContextMenu::Completions(prev_menu)) =
5619 editor.context_menu.borrow_mut().take()
5620 {
5621 let position_matches =
5622 if prev_menu.initial_position == menu.initial_position {
5623 true
5624 } else {
5625 let snapshot = editor.buffer.read(cx).read(cx);
5626 prev_menu.initial_position.to_offset(&snapshot)
5627 == menu.initial_position.to_offset(&snapshot)
5628 };
5629 if position_matches {
5630 // Preserve markdown cache before `set_filter_results` because it will
5631 // try to populate the documentation cache.
5632 menu.preserve_markdown_cache(prev_menu);
5633 }
5634 };
5635
5636 menu.set_filter_results(matches, provider, window, cx);
5637 }) else {
5638 return;
5639 };
5640
5641 menu.visible().then_some(menu)
5642 };
5643
5644 editor
5645 .update_in(cx, |editor, window, cx| {
5646 if editor.focus_handle.is_focused(window)
5647 && let Some(menu) = menu
5648 {
5649 *editor.context_menu.borrow_mut() =
5650 Some(CodeContextMenu::Completions(menu));
5651
5652 crate::hover_popover::hide_hover(editor, cx);
5653 if editor.show_edit_predictions_in_menu() {
5654 editor.update_visible_edit_prediction(window, cx);
5655 } else {
5656 editor.discard_edit_prediction(false, cx);
5657 }
5658
5659 cx.notify();
5660 return;
5661 }
5662
5663 if editor.completion_tasks.len() <= 1 {
5664 // If there are no more completion tasks and the last menu was empty, we should hide it.
5665 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5666 // If it was already hidden and we don't show edit predictions in the menu,
5667 // we should also show the edit prediction when available.
5668 if was_hidden && editor.show_edit_predictions_in_menu() {
5669 editor.update_visible_edit_prediction(window, cx);
5670 }
5671 }
5672 })
5673 .ok();
5674 });
5675
5676 self.completion_tasks.push((id, task));
5677 }
5678
5679 #[cfg(feature = "test-support")]
5680 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5681 let menu = self.context_menu.borrow();
5682 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5683 let completions = menu.completions.borrow();
5684 Some(completions.to_vec())
5685 } else {
5686 None
5687 }
5688 }
5689
5690 pub fn with_completions_menu_matching_id<R>(
5691 &self,
5692 id: CompletionId,
5693 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5694 ) -> R {
5695 let mut context_menu = self.context_menu.borrow_mut();
5696 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5697 return f(None);
5698 };
5699 if completions_menu.id != id {
5700 return f(None);
5701 }
5702 f(Some(completions_menu))
5703 }
5704
5705 pub fn confirm_completion(
5706 &mut self,
5707 action: &ConfirmCompletion,
5708 window: &mut Window,
5709 cx: &mut Context<Self>,
5710 ) -> Option<Task<Result<()>>> {
5711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5712 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5713 }
5714
5715 pub fn confirm_completion_insert(
5716 &mut self,
5717 _: &ConfirmCompletionInsert,
5718 window: &mut Window,
5719 cx: &mut Context<Self>,
5720 ) -> Option<Task<Result<()>>> {
5721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5722 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5723 }
5724
5725 pub fn confirm_completion_replace(
5726 &mut self,
5727 _: &ConfirmCompletionReplace,
5728 window: &mut Window,
5729 cx: &mut Context<Self>,
5730 ) -> Option<Task<Result<()>>> {
5731 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5732 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5733 }
5734
5735 pub fn compose_completion(
5736 &mut self,
5737 action: &ComposeCompletion,
5738 window: &mut Window,
5739 cx: &mut Context<Self>,
5740 ) -> Option<Task<Result<()>>> {
5741 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5742 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5743 }
5744
5745 fn do_completion(
5746 &mut self,
5747 item_ix: Option<usize>,
5748 intent: CompletionIntent,
5749 window: &mut Window,
5750 cx: &mut Context<Editor>,
5751 ) -> Option<Task<Result<()>>> {
5752 use language::ToOffset as _;
5753
5754 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5755 else {
5756 return None;
5757 };
5758
5759 let candidate_id = {
5760 let entries = completions_menu.entries.borrow();
5761 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5762 if self.show_edit_predictions_in_menu() {
5763 self.discard_edit_prediction(true, cx);
5764 }
5765 mat.candidate_id
5766 };
5767
5768 let completion = completions_menu
5769 .completions
5770 .borrow()
5771 .get(candidate_id)?
5772 .clone();
5773 cx.stop_propagation();
5774
5775 let buffer_handle = completions_menu.buffer.clone();
5776
5777 let CompletionEdit {
5778 new_text,
5779 snippet,
5780 replace_range,
5781 } = process_completion_for_edit(
5782 &completion,
5783 intent,
5784 &buffer_handle,
5785 &completions_menu.initial_position.text_anchor,
5786 cx,
5787 );
5788
5789 let buffer = buffer_handle.read(cx);
5790 let snapshot = self.buffer.read(cx).snapshot(cx);
5791 let newest_anchor = self.selections.newest_anchor();
5792 let replace_range_multibuffer = {
5793 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5794 excerpt.map_range_from_buffer(replace_range.clone())
5795 };
5796 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5797 return None;
5798 }
5799
5800 let old_text = buffer
5801 .text_for_range(replace_range.clone())
5802 .collect::<String>();
5803 let lookbehind = newest_anchor
5804 .start
5805 .text_anchor
5806 .to_offset(buffer)
5807 .saturating_sub(replace_range.start);
5808 let lookahead = replace_range
5809 .end
5810 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5811 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5812 let suffix = &old_text[lookbehind.min(old_text.len())..];
5813
5814 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5815 let mut ranges = Vec::new();
5816 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5817
5818 for selection in &selections {
5819 let range = if selection.id == newest_anchor.id {
5820 replace_range_multibuffer.clone()
5821 } else {
5822 let mut range = selection.range();
5823
5824 // if prefix is present, don't duplicate it
5825 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5826 range.start = range.start.saturating_sub(lookbehind);
5827
5828 // if suffix is also present, mimic the newest cursor and replace it
5829 if selection.id != newest_anchor.id
5830 && snapshot.contains_str_at(range.end, suffix)
5831 {
5832 range.end += lookahead;
5833 }
5834 }
5835 range
5836 };
5837
5838 ranges.push(range.clone());
5839
5840 if !self.linked_edit_ranges.is_empty() {
5841 let start_anchor = snapshot.anchor_before(range.start);
5842 let end_anchor = snapshot.anchor_after(range.end);
5843 if let Some(ranges) = self
5844 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5845 {
5846 for (buffer, edits) in ranges {
5847 linked_edits
5848 .entry(buffer.clone())
5849 .or_default()
5850 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5851 }
5852 }
5853 }
5854 }
5855
5856 let common_prefix_len = old_text
5857 .chars()
5858 .zip(new_text.chars())
5859 .take_while(|(a, b)| a == b)
5860 .map(|(a, _)| a.len_utf8())
5861 .sum::<usize>();
5862
5863 cx.emit(EditorEvent::InputHandled {
5864 utf16_range_to_replace: None,
5865 text: new_text[common_prefix_len..].into(),
5866 });
5867
5868 self.transact(window, cx, |editor, window, cx| {
5869 if let Some(mut snippet) = snippet {
5870 snippet.text = new_text.to_string();
5871 editor
5872 .insert_snippet(&ranges, snippet, window, cx)
5873 .log_err();
5874 } else {
5875 editor.buffer.update(cx, |multi_buffer, cx| {
5876 let auto_indent = match completion.insert_text_mode {
5877 Some(InsertTextMode::AS_IS) => None,
5878 _ => editor.autoindent_mode.clone(),
5879 };
5880 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5881 multi_buffer.edit(edits, auto_indent, cx);
5882 });
5883 }
5884 for (buffer, edits) in linked_edits {
5885 buffer.update(cx, |buffer, cx| {
5886 let snapshot = buffer.snapshot();
5887 let edits = edits
5888 .into_iter()
5889 .map(|(range, text)| {
5890 use text::ToPoint as TP;
5891 let end_point = TP::to_point(&range.end, &snapshot);
5892 let start_point = TP::to_point(&range.start, &snapshot);
5893 (start_point..end_point, text)
5894 })
5895 .sorted_by_key(|(range, _)| range.start);
5896 buffer.edit(edits, None, cx);
5897 })
5898 }
5899
5900 editor.refresh_edit_prediction(true, false, window, cx);
5901 });
5902 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5903
5904 let show_new_completions_on_confirm = completion
5905 .confirm
5906 .as_ref()
5907 .is_some_and(|confirm| confirm(intent, window, cx));
5908 if show_new_completions_on_confirm {
5909 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5910 }
5911
5912 let provider = self.completion_provider.as_ref()?;
5913 drop(completion);
5914 let apply_edits = provider.apply_additional_edits_for_completion(
5915 buffer_handle,
5916 completions_menu.completions.clone(),
5917 candidate_id,
5918 true,
5919 cx,
5920 );
5921
5922 let editor_settings = EditorSettings::get_global(cx);
5923 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5924 // After the code completion is finished, users often want to know what signatures are needed.
5925 // so we should automatically call signature_help
5926 self.show_signature_help(&ShowSignatureHelp, window, cx);
5927 }
5928
5929 Some(cx.foreground_executor().spawn(async move {
5930 apply_edits.await?;
5931 Ok(())
5932 }))
5933 }
5934
5935 pub fn toggle_code_actions(
5936 &mut self,
5937 action: &ToggleCodeActions,
5938 window: &mut Window,
5939 cx: &mut Context<Self>,
5940 ) {
5941 let quick_launch = action.quick_launch;
5942 let mut context_menu = self.context_menu.borrow_mut();
5943 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5944 if code_actions.deployed_from == action.deployed_from {
5945 // Toggle if we're selecting the same one
5946 *context_menu = None;
5947 cx.notify();
5948 return;
5949 } else {
5950 // Otherwise, clear it and start a new one
5951 *context_menu = None;
5952 cx.notify();
5953 }
5954 }
5955 drop(context_menu);
5956 let snapshot = self.snapshot(window, cx);
5957 let deployed_from = action.deployed_from.clone();
5958 let action = action.clone();
5959 self.completion_tasks.clear();
5960 self.discard_edit_prediction(false, cx);
5961
5962 let multibuffer_point = match &action.deployed_from {
5963 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5964 DisplayPoint::new(*row, 0).to_point(&snapshot)
5965 }
5966 _ => self
5967 .selections
5968 .newest::<Point>(&snapshot.display_snapshot)
5969 .head(),
5970 };
5971 let Some((buffer, buffer_row)) = snapshot
5972 .buffer_snapshot()
5973 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5974 .and_then(|(buffer_snapshot, range)| {
5975 self.buffer()
5976 .read(cx)
5977 .buffer(buffer_snapshot.remote_id())
5978 .map(|buffer| (buffer, range.start.row))
5979 })
5980 else {
5981 return;
5982 };
5983 let buffer_id = buffer.read(cx).remote_id();
5984 let tasks = self
5985 .tasks
5986 .get(&(buffer_id, buffer_row))
5987 .map(|t| Arc::new(t.to_owned()));
5988
5989 if !self.focus_handle.is_focused(window) {
5990 return;
5991 }
5992 let project = self.project.clone();
5993
5994 let code_actions_task = match deployed_from {
5995 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5996 _ => self.code_actions(buffer_row, window, cx),
5997 };
5998
5999 let runnable_task = match deployed_from {
6000 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6001 _ => {
6002 let mut task_context_task = Task::ready(None);
6003 if let Some(tasks) = &tasks
6004 && let Some(project) = project
6005 {
6006 task_context_task =
6007 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6008 }
6009
6010 cx.spawn_in(window, {
6011 let buffer = buffer.clone();
6012 async move |editor, cx| {
6013 let task_context = task_context_task.await;
6014
6015 let resolved_tasks =
6016 tasks
6017 .zip(task_context.clone())
6018 .map(|(tasks, task_context)| ResolvedTasks {
6019 templates: tasks.resolve(&task_context).collect(),
6020 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6021 multibuffer_point.row,
6022 tasks.column,
6023 )),
6024 });
6025 let debug_scenarios = editor
6026 .update(cx, |editor, cx| {
6027 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6028 })?
6029 .await;
6030 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6031 }
6032 })
6033 }
6034 };
6035
6036 cx.spawn_in(window, async move |editor, cx| {
6037 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6038 let code_actions = code_actions_task.await;
6039 let spawn_straight_away = quick_launch
6040 && resolved_tasks
6041 .as_ref()
6042 .is_some_and(|tasks| tasks.templates.len() == 1)
6043 && code_actions
6044 .as_ref()
6045 .is_none_or(|actions| actions.is_empty())
6046 && debug_scenarios.is_empty();
6047
6048 editor.update_in(cx, |editor, window, cx| {
6049 crate::hover_popover::hide_hover(editor, cx);
6050 let actions = CodeActionContents::new(
6051 resolved_tasks,
6052 code_actions,
6053 debug_scenarios,
6054 task_context.unwrap_or_default(),
6055 );
6056
6057 // Don't show the menu if there are no actions available
6058 if actions.is_empty() {
6059 cx.notify();
6060 return Task::ready(Ok(()));
6061 }
6062
6063 *editor.context_menu.borrow_mut() =
6064 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6065 buffer,
6066 actions,
6067 selected_item: Default::default(),
6068 scroll_handle: UniformListScrollHandle::default(),
6069 deployed_from,
6070 }));
6071 cx.notify();
6072 if spawn_straight_away
6073 && let Some(task) = editor.confirm_code_action(
6074 &ConfirmCodeAction { item_ix: Some(0) },
6075 window,
6076 cx,
6077 )
6078 {
6079 return task;
6080 }
6081
6082 Task::ready(Ok(()))
6083 })
6084 })
6085 .detach_and_log_err(cx);
6086 }
6087
6088 fn debug_scenarios(
6089 &mut self,
6090 resolved_tasks: &Option<ResolvedTasks>,
6091 buffer: &Entity<Buffer>,
6092 cx: &mut App,
6093 ) -> Task<Vec<task::DebugScenario>> {
6094 maybe!({
6095 let project = self.project()?;
6096 let dap_store = project.read(cx).dap_store();
6097 let mut scenarios = vec![];
6098 let resolved_tasks = resolved_tasks.as_ref()?;
6099 let buffer = buffer.read(cx);
6100 let language = buffer.language()?;
6101 let file = buffer.file();
6102 let debug_adapter = language_settings(language.name().into(), file, cx)
6103 .debuggers
6104 .first()
6105 .map(SharedString::from)
6106 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6107
6108 dap_store.update(cx, |dap_store, cx| {
6109 for (_, task) in &resolved_tasks.templates {
6110 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6111 task.original_task().clone(),
6112 debug_adapter.clone().into(),
6113 task.display_label().to_owned().into(),
6114 cx,
6115 );
6116 scenarios.push(maybe_scenario);
6117 }
6118 });
6119 Some(cx.background_spawn(async move {
6120 futures::future::join_all(scenarios)
6121 .await
6122 .into_iter()
6123 .flatten()
6124 .collect::<Vec<_>>()
6125 }))
6126 })
6127 .unwrap_or_else(|| Task::ready(vec![]))
6128 }
6129
6130 fn code_actions(
6131 &mut self,
6132 buffer_row: u32,
6133 window: &mut Window,
6134 cx: &mut Context<Self>,
6135 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6136 let mut task = self.code_actions_task.take();
6137 cx.spawn_in(window, async move |editor, cx| {
6138 while let Some(prev_task) = task {
6139 prev_task.await.log_err();
6140 task = editor
6141 .update(cx, |this, _| this.code_actions_task.take())
6142 .ok()?;
6143 }
6144
6145 editor
6146 .update(cx, |editor, cx| {
6147 editor
6148 .available_code_actions
6149 .clone()
6150 .and_then(|(location, code_actions)| {
6151 let snapshot = location.buffer.read(cx).snapshot();
6152 let point_range = location.range.to_point(&snapshot);
6153 let point_range = point_range.start.row..=point_range.end.row;
6154 if point_range.contains(&buffer_row) {
6155 Some(code_actions)
6156 } else {
6157 None
6158 }
6159 })
6160 })
6161 .ok()
6162 .flatten()
6163 })
6164 }
6165
6166 pub fn confirm_code_action(
6167 &mut self,
6168 action: &ConfirmCodeAction,
6169 window: &mut Window,
6170 cx: &mut Context<Self>,
6171 ) -> Option<Task<Result<()>>> {
6172 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6173
6174 let actions_menu =
6175 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6176 menu
6177 } else {
6178 return None;
6179 };
6180
6181 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6182 let action = actions_menu.actions.get(action_ix)?;
6183 let title = action.label();
6184 let buffer = actions_menu.buffer;
6185 let workspace = self.workspace()?;
6186
6187 match action {
6188 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6189 workspace.update(cx, |workspace, cx| {
6190 workspace.schedule_resolved_task(
6191 task_source_kind,
6192 resolved_task,
6193 false,
6194 window,
6195 cx,
6196 );
6197
6198 Some(Task::ready(Ok(())))
6199 })
6200 }
6201 CodeActionsItem::CodeAction {
6202 excerpt_id,
6203 action,
6204 provider,
6205 } => {
6206 let apply_code_action =
6207 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6208 let workspace = workspace.downgrade();
6209 Some(cx.spawn_in(window, async move |editor, cx| {
6210 let project_transaction = apply_code_action.await?;
6211 Self::open_project_transaction(
6212 &editor,
6213 workspace,
6214 project_transaction,
6215 title,
6216 cx,
6217 )
6218 .await
6219 }))
6220 }
6221 CodeActionsItem::DebugScenario(scenario) => {
6222 let context = actions_menu.actions.context;
6223
6224 workspace.update(cx, |workspace, cx| {
6225 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6226 workspace.start_debug_session(
6227 scenario,
6228 context,
6229 Some(buffer),
6230 None,
6231 window,
6232 cx,
6233 );
6234 });
6235 Some(Task::ready(Ok(())))
6236 }
6237 }
6238 }
6239
6240 pub async fn open_project_transaction(
6241 editor: &WeakEntity<Editor>,
6242 workspace: WeakEntity<Workspace>,
6243 transaction: ProjectTransaction,
6244 title: String,
6245 cx: &mut AsyncWindowContext,
6246 ) -> Result<()> {
6247 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6248 cx.update(|_, cx| {
6249 entries.sort_unstable_by_key(|(buffer, _)| {
6250 buffer.read(cx).file().map(|f| f.path().clone())
6251 });
6252 })?;
6253 if entries.is_empty() {
6254 return Ok(());
6255 }
6256
6257 // If the project transaction's edits are all contained within this editor, then
6258 // avoid opening a new editor to display them.
6259
6260 if let [(buffer, transaction)] = &*entries {
6261 let excerpt = editor.update(cx, |editor, cx| {
6262 editor
6263 .buffer()
6264 .read(cx)
6265 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6266 })?;
6267 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6268 && excerpted_buffer == *buffer
6269 {
6270 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6271 let excerpt_range = excerpt_range.to_offset(buffer);
6272 buffer
6273 .edited_ranges_for_transaction::<usize>(transaction)
6274 .all(|range| {
6275 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6276 })
6277 })?;
6278
6279 if all_edits_within_excerpt {
6280 return Ok(());
6281 }
6282 }
6283 }
6284
6285 let mut ranges_to_highlight = Vec::new();
6286 let excerpt_buffer = cx.new(|cx| {
6287 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6288 for (buffer_handle, transaction) in &entries {
6289 let edited_ranges = buffer_handle
6290 .read(cx)
6291 .edited_ranges_for_transaction::<Point>(transaction)
6292 .collect::<Vec<_>>();
6293 let (ranges, _) = multibuffer.set_excerpts_for_path(
6294 PathKey::for_buffer(buffer_handle, cx),
6295 buffer_handle.clone(),
6296 edited_ranges,
6297 multibuffer_context_lines(cx),
6298 cx,
6299 );
6300
6301 ranges_to_highlight.extend(ranges);
6302 }
6303 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6304 multibuffer
6305 })?;
6306
6307 workspace.update_in(cx, |workspace, window, cx| {
6308 let project = workspace.project().clone();
6309 let editor =
6310 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6311 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6312 editor.update(cx, |editor, cx| {
6313 editor.highlight_background::<Self>(
6314 &ranges_to_highlight,
6315 |theme| theme.colors().editor_highlighted_line_background,
6316 cx,
6317 );
6318 });
6319 })?;
6320
6321 Ok(())
6322 }
6323
6324 pub fn clear_code_action_providers(&mut self) {
6325 self.code_action_providers.clear();
6326 self.available_code_actions.take();
6327 }
6328
6329 pub fn add_code_action_provider(
6330 &mut self,
6331 provider: Rc<dyn CodeActionProvider>,
6332 window: &mut Window,
6333 cx: &mut Context<Self>,
6334 ) {
6335 if self
6336 .code_action_providers
6337 .iter()
6338 .any(|existing_provider| existing_provider.id() == provider.id())
6339 {
6340 return;
6341 }
6342
6343 self.code_action_providers.push(provider);
6344 self.refresh_code_actions(window, cx);
6345 }
6346
6347 pub fn remove_code_action_provider(
6348 &mut self,
6349 id: Arc<str>,
6350 window: &mut Window,
6351 cx: &mut Context<Self>,
6352 ) {
6353 self.code_action_providers
6354 .retain(|provider| provider.id() != id);
6355 self.refresh_code_actions(window, cx);
6356 }
6357
6358 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6359 !self.code_action_providers.is_empty()
6360 && EditorSettings::get_global(cx).toolbar.code_actions
6361 }
6362
6363 pub fn has_available_code_actions(&self) -> bool {
6364 self.available_code_actions
6365 .as_ref()
6366 .is_some_and(|(_, actions)| !actions.is_empty())
6367 }
6368
6369 fn render_inline_code_actions(
6370 &self,
6371 icon_size: ui::IconSize,
6372 display_row: DisplayRow,
6373 is_active: bool,
6374 cx: &mut Context<Self>,
6375 ) -> AnyElement {
6376 let show_tooltip = !self.context_menu_visible();
6377 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6378 .icon_size(icon_size)
6379 .shape(ui::IconButtonShape::Square)
6380 .icon_color(ui::Color::Hidden)
6381 .toggle_state(is_active)
6382 .when(show_tooltip, |this| {
6383 this.tooltip({
6384 let focus_handle = self.focus_handle.clone();
6385 move |_window, cx| {
6386 Tooltip::for_action_in(
6387 "Toggle Code Actions",
6388 &ToggleCodeActions {
6389 deployed_from: None,
6390 quick_launch: false,
6391 },
6392 &focus_handle,
6393 cx,
6394 )
6395 }
6396 })
6397 })
6398 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6399 window.focus(&editor.focus_handle(cx));
6400 editor.toggle_code_actions(
6401 &crate::actions::ToggleCodeActions {
6402 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6403 display_row,
6404 )),
6405 quick_launch: false,
6406 },
6407 window,
6408 cx,
6409 );
6410 }))
6411 .into_any_element()
6412 }
6413
6414 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6415 &self.context_menu
6416 }
6417
6418 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6419 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6420 cx.background_executor()
6421 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6422 .await;
6423
6424 let (start_buffer, start, _, end, newest_selection) = this
6425 .update(cx, |this, cx| {
6426 let newest_selection = this.selections.newest_anchor().clone();
6427 if newest_selection.head().diff_base_anchor.is_some() {
6428 return None;
6429 }
6430 let display_snapshot = this.display_snapshot(cx);
6431 let newest_selection_adjusted =
6432 this.selections.newest_adjusted(&display_snapshot);
6433 let buffer = this.buffer.read(cx);
6434
6435 let (start_buffer, start) =
6436 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6437 let (end_buffer, end) =
6438 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6439
6440 Some((start_buffer, start, end_buffer, end, newest_selection))
6441 })?
6442 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6443 .context(
6444 "Expected selection to lie in a single buffer when refreshing code actions",
6445 )?;
6446 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6447 let providers = this.code_action_providers.clone();
6448 let tasks = this
6449 .code_action_providers
6450 .iter()
6451 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6452 .collect::<Vec<_>>();
6453 (providers, tasks)
6454 })?;
6455
6456 let mut actions = Vec::new();
6457 for (provider, provider_actions) in
6458 providers.into_iter().zip(future::join_all(tasks).await)
6459 {
6460 if let Some(provider_actions) = provider_actions.log_err() {
6461 actions.extend(provider_actions.into_iter().map(|action| {
6462 AvailableCodeAction {
6463 excerpt_id: newest_selection.start.excerpt_id,
6464 action,
6465 provider: provider.clone(),
6466 }
6467 }));
6468 }
6469 }
6470
6471 this.update(cx, |this, cx| {
6472 this.available_code_actions = if actions.is_empty() {
6473 None
6474 } else {
6475 Some((
6476 Location {
6477 buffer: start_buffer,
6478 range: start..end,
6479 },
6480 actions.into(),
6481 ))
6482 };
6483 cx.notify();
6484 })
6485 }));
6486 }
6487
6488 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6489 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6490 self.show_git_blame_inline = false;
6491
6492 self.show_git_blame_inline_delay_task =
6493 Some(cx.spawn_in(window, async move |this, cx| {
6494 cx.background_executor().timer(delay).await;
6495
6496 this.update(cx, |this, cx| {
6497 this.show_git_blame_inline = true;
6498 cx.notify();
6499 })
6500 .log_err();
6501 }));
6502 }
6503 }
6504
6505 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6506 let snapshot = self.snapshot(window, cx);
6507 let cursor = self
6508 .selections
6509 .newest::<Point>(&snapshot.display_snapshot)
6510 .head();
6511 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6512 else {
6513 return;
6514 };
6515
6516 let Some(blame) = self.blame.as_ref() else {
6517 return;
6518 };
6519
6520 let row_info = RowInfo {
6521 buffer_id: Some(buffer.remote_id()),
6522 buffer_row: Some(point.row),
6523 ..Default::default()
6524 };
6525 let Some((buffer, blame_entry)) = blame
6526 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6527 .flatten()
6528 else {
6529 return;
6530 };
6531
6532 let anchor = self.selections.newest_anchor().head();
6533 let position = self.to_pixel_point(anchor, &snapshot, window);
6534 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6535 self.show_blame_popover(
6536 buffer,
6537 &blame_entry,
6538 position + last_bounds.origin,
6539 true,
6540 cx,
6541 );
6542 };
6543 }
6544
6545 fn show_blame_popover(
6546 &mut self,
6547 buffer: BufferId,
6548 blame_entry: &BlameEntry,
6549 position: gpui::Point<Pixels>,
6550 ignore_timeout: bool,
6551 cx: &mut Context<Self>,
6552 ) {
6553 if let Some(state) = &mut self.inline_blame_popover {
6554 state.hide_task.take();
6555 } else {
6556 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6557 let blame_entry = blame_entry.clone();
6558 let show_task = cx.spawn(async move |editor, cx| {
6559 if !ignore_timeout {
6560 cx.background_executor()
6561 .timer(std::time::Duration::from_millis(blame_popover_delay))
6562 .await;
6563 }
6564 editor
6565 .update(cx, |editor, cx| {
6566 editor.inline_blame_popover_show_task.take();
6567 let Some(blame) = editor.blame.as_ref() else {
6568 return;
6569 };
6570 let blame = blame.read(cx);
6571 let details = blame.details_for_entry(buffer, &blame_entry);
6572 let markdown = cx.new(|cx| {
6573 Markdown::new(
6574 details
6575 .as_ref()
6576 .map(|message| message.message.clone())
6577 .unwrap_or_default(),
6578 None,
6579 None,
6580 cx,
6581 )
6582 });
6583 editor.inline_blame_popover = Some(InlineBlamePopover {
6584 position,
6585 hide_task: None,
6586 popover_bounds: None,
6587 popover_state: InlineBlamePopoverState {
6588 scroll_handle: ScrollHandle::new(),
6589 commit_message: details,
6590 markdown,
6591 },
6592 keyboard_grace: ignore_timeout,
6593 });
6594 cx.notify();
6595 })
6596 .ok();
6597 });
6598 self.inline_blame_popover_show_task = Some(show_task);
6599 }
6600 }
6601
6602 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6603 self.inline_blame_popover_show_task.take();
6604 if let Some(state) = &mut self.inline_blame_popover {
6605 let hide_task = cx.spawn(async move |editor, cx| {
6606 if !ignore_timeout {
6607 cx.background_executor()
6608 .timer(std::time::Duration::from_millis(100))
6609 .await;
6610 }
6611 editor
6612 .update(cx, |editor, cx| {
6613 editor.inline_blame_popover.take();
6614 cx.notify();
6615 })
6616 .ok();
6617 });
6618 state.hide_task = Some(hide_task);
6619 true
6620 } else {
6621 false
6622 }
6623 }
6624
6625 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6626 if self.pending_rename.is_some() {
6627 return None;
6628 }
6629
6630 let provider = self.semantics_provider.clone()?;
6631 let buffer = self.buffer.read(cx);
6632 let newest_selection = self.selections.newest_anchor().clone();
6633 let cursor_position = newest_selection.head();
6634 let (cursor_buffer, cursor_buffer_position) =
6635 buffer.text_anchor_for_position(cursor_position, cx)?;
6636 let (tail_buffer, tail_buffer_position) =
6637 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6638 if cursor_buffer != tail_buffer {
6639 return None;
6640 }
6641
6642 let snapshot = cursor_buffer.read(cx).snapshot();
6643 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6644 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6645 if start_word_range != end_word_range {
6646 self.document_highlights_task.take();
6647 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6648 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6649 return None;
6650 }
6651
6652 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6653 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6654 cx.background_executor()
6655 .timer(Duration::from_millis(debounce))
6656 .await;
6657
6658 let highlights = if let Some(highlights) = cx
6659 .update(|cx| {
6660 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6661 })
6662 .ok()
6663 .flatten()
6664 {
6665 highlights.await.log_err()
6666 } else {
6667 None
6668 };
6669
6670 if let Some(highlights) = highlights {
6671 this.update(cx, |this, cx| {
6672 if this.pending_rename.is_some() {
6673 return;
6674 }
6675
6676 let buffer = this.buffer.read(cx);
6677 if buffer
6678 .text_anchor_for_position(cursor_position, cx)
6679 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6680 {
6681 return;
6682 }
6683
6684 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6685 let mut write_ranges = Vec::new();
6686 let mut read_ranges = Vec::new();
6687 for highlight in highlights {
6688 let buffer_id = cursor_buffer.read(cx).remote_id();
6689 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6690 {
6691 let start = highlight
6692 .range
6693 .start
6694 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6695 let end = highlight
6696 .range
6697 .end
6698 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6699 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6700 continue;
6701 }
6702
6703 let range =
6704 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6705 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6706 write_ranges.push(range);
6707 } else {
6708 read_ranges.push(range);
6709 }
6710 }
6711 }
6712
6713 this.highlight_background::<DocumentHighlightRead>(
6714 &read_ranges,
6715 |theme| theme.colors().editor_document_highlight_read_background,
6716 cx,
6717 );
6718 this.highlight_background::<DocumentHighlightWrite>(
6719 &write_ranges,
6720 |theme| theme.colors().editor_document_highlight_write_background,
6721 cx,
6722 );
6723 cx.notify();
6724 })
6725 .log_err();
6726 }
6727 }));
6728 None
6729 }
6730
6731 fn prepare_highlight_query_from_selection(
6732 &mut self,
6733 cx: &mut Context<Editor>,
6734 ) -> Option<(String, Range<Anchor>)> {
6735 if matches!(self.mode, EditorMode::SingleLine) {
6736 return None;
6737 }
6738 if !EditorSettings::get_global(cx).selection_highlight {
6739 return None;
6740 }
6741 if self.selections.count() != 1 || self.selections.line_mode() {
6742 return None;
6743 }
6744 let selection = self.selections.newest_anchor();
6745 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6746 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6747 ..selection.end.to_point(&multi_buffer_snapshot);
6748 // If the selection spans multiple rows OR it is empty
6749 if selection_point_range.start.row != selection_point_range.end.row
6750 || selection_point_range.start.column == selection_point_range.end.column
6751 {
6752 return None;
6753 }
6754
6755 let query = multi_buffer_snapshot
6756 .text_for_range(selection.range())
6757 .collect::<String>();
6758 if query.trim().is_empty() {
6759 return None;
6760 }
6761 Some((query, selection.range()))
6762 }
6763
6764 fn update_selection_occurrence_highlights(
6765 &mut self,
6766 query_text: String,
6767 query_range: Range<Anchor>,
6768 multi_buffer_range_to_query: Range<Point>,
6769 use_debounce: bool,
6770 window: &mut Window,
6771 cx: &mut Context<Editor>,
6772 ) -> Task<()> {
6773 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6774 cx.spawn_in(window, async move |editor, cx| {
6775 if use_debounce {
6776 cx.background_executor()
6777 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6778 .await;
6779 }
6780 let match_task = cx.background_spawn(async move {
6781 let buffer_ranges = multi_buffer_snapshot
6782 .range_to_buffer_ranges(multi_buffer_range_to_query)
6783 .into_iter()
6784 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6785 let mut match_ranges = Vec::new();
6786 let Ok(regex) = project::search::SearchQuery::text(
6787 query_text.clone(),
6788 false,
6789 false,
6790 false,
6791 Default::default(),
6792 Default::default(),
6793 false,
6794 None,
6795 ) else {
6796 return Vec::default();
6797 };
6798 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6799 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6800 match_ranges.extend(
6801 regex
6802 .search(buffer_snapshot, Some(search_range.clone()))
6803 .await
6804 .into_iter()
6805 .filter_map(|match_range| {
6806 let match_start = buffer_snapshot
6807 .anchor_after(search_range.start + match_range.start);
6808 let match_end = buffer_snapshot
6809 .anchor_before(search_range.start + match_range.end);
6810 let match_anchor_range = Anchor::range_in_buffer(
6811 excerpt_id,
6812 buffer_snapshot.remote_id(),
6813 match_start..match_end,
6814 );
6815 (match_anchor_range != query_range).then_some(match_anchor_range)
6816 }),
6817 );
6818 }
6819 match_ranges
6820 });
6821 let match_ranges = match_task.await;
6822 editor
6823 .update_in(cx, |editor, _, cx| {
6824 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6825 if !match_ranges.is_empty() {
6826 editor.highlight_background::<SelectedTextHighlight>(
6827 &match_ranges,
6828 |theme| theme.colors().editor_document_highlight_bracket_background,
6829 cx,
6830 )
6831 }
6832 })
6833 .log_err();
6834 })
6835 }
6836
6837 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6838 struct NewlineFold;
6839 let type_id = std::any::TypeId::of::<NewlineFold>();
6840 if !self.mode.is_single_line() {
6841 return;
6842 }
6843 let snapshot = self.snapshot(window, cx);
6844 if snapshot.buffer_snapshot().max_point().row == 0 {
6845 return;
6846 }
6847 let task = cx.background_spawn(async move {
6848 let new_newlines = snapshot
6849 .buffer_chars_at(0)
6850 .filter_map(|(c, i)| {
6851 if c == '\n' {
6852 Some(
6853 snapshot.buffer_snapshot().anchor_after(i)
6854 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6855 )
6856 } else {
6857 None
6858 }
6859 })
6860 .collect::<Vec<_>>();
6861 let existing_newlines = snapshot
6862 .folds_in_range(0..snapshot.buffer_snapshot().len())
6863 .filter_map(|fold| {
6864 if fold.placeholder.type_tag == Some(type_id) {
6865 Some(fold.range.start..fold.range.end)
6866 } else {
6867 None
6868 }
6869 })
6870 .collect::<Vec<_>>();
6871
6872 (new_newlines, existing_newlines)
6873 });
6874 self.folding_newlines = cx.spawn(async move |this, cx| {
6875 let (new_newlines, existing_newlines) = task.await;
6876 if new_newlines == existing_newlines {
6877 return;
6878 }
6879 let placeholder = FoldPlaceholder {
6880 render: Arc::new(move |_, _, cx| {
6881 div()
6882 .bg(cx.theme().status().hint_background)
6883 .border_b_1()
6884 .size_full()
6885 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6886 .border_color(cx.theme().status().hint)
6887 .child("\\n")
6888 .into_any()
6889 }),
6890 constrain_width: false,
6891 merge_adjacent: false,
6892 type_tag: Some(type_id),
6893 };
6894 let creases = new_newlines
6895 .into_iter()
6896 .map(|range| Crease::simple(range, placeholder.clone()))
6897 .collect();
6898 this.update(cx, |this, cx| {
6899 this.display_map.update(cx, |display_map, cx| {
6900 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6901 display_map.fold(creases, cx);
6902 });
6903 })
6904 .ok();
6905 });
6906 }
6907
6908 fn refresh_selected_text_highlights(
6909 &mut self,
6910 on_buffer_edit: bool,
6911 window: &mut Window,
6912 cx: &mut Context<Editor>,
6913 ) {
6914 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6915 else {
6916 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6917 self.quick_selection_highlight_task.take();
6918 self.debounced_selection_highlight_task.take();
6919 return;
6920 };
6921 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6922 if on_buffer_edit
6923 || self
6924 .quick_selection_highlight_task
6925 .as_ref()
6926 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6927 {
6928 let multi_buffer_visible_start = self
6929 .scroll_manager
6930 .anchor()
6931 .anchor
6932 .to_point(&multi_buffer_snapshot);
6933 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6934 multi_buffer_visible_start
6935 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6936 Bias::Left,
6937 );
6938 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6939 self.quick_selection_highlight_task = Some((
6940 query_range.clone(),
6941 self.update_selection_occurrence_highlights(
6942 query_text.clone(),
6943 query_range.clone(),
6944 multi_buffer_visible_range,
6945 false,
6946 window,
6947 cx,
6948 ),
6949 ));
6950 }
6951 if on_buffer_edit
6952 || self
6953 .debounced_selection_highlight_task
6954 .as_ref()
6955 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6956 {
6957 let multi_buffer_start = multi_buffer_snapshot
6958 .anchor_before(0)
6959 .to_point(&multi_buffer_snapshot);
6960 let multi_buffer_end = multi_buffer_snapshot
6961 .anchor_after(multi_buffer_snapshot.len())
6962 .to_point(&multi_buffer_snapshot);
6963 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6964 self.debounced_selection_highlight_task = Some((
6965 query_range.clone(),
6966 self.update_selection_occurrence_highlights(
6967 query_text,
6968 query_range,
6969 multi_buffer_full_range,
6970 true,
6971 window,
6972 cx,
6973 ),
6974 ));
6975 }
6976 }
6977
6978 pub fn refresh_edit_prediction(
6979 &mut self,
6980 debounce: bool,
6981 user_requested: bool,
6982 window: &mut Window,
6983 cx: &mut Context<Self>,
6984 ) -> Option<()> {
6985 if DisableAiSettings::get_global(cx).disable_ai {
6986 return None;
6987 }
6988
6989 let provider = self.edit_prediction_provider()?;
6990 let cursor = self.selections.newest_anchor().head();
6991 let (buffer, cursor_buffer_position) =
6992 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6993
6994 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6995 self.discard_edit_prediction(false, cx);
6996 return None;
6997 }
6998
6999 self.update_visible_edit_prediction(window, cx);
7000
7001 if !user_requested
7002 && (!self.should_show_edit_predictions()
7003 || !self.is_focused(window)
7004 || buffer.read(cx).is_empty())
7005 {
7006 self.discard_edit_prediction(false, cx);
7007 return None;
7008 }
7009
7010 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7011 Some(())
7012 }
7013
7014 fn show_edit_predictions_in_menu(&self) -> bool {
7015 match self.edit_prediction_settings {
7016 EditPredictionSettings::Disabled => false,
7017 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7018 }
7019 }
7020
7021 pub fn edit_predictions_enabled(&self) -> bool {
7022 match self.edit_prediction_settings {
7023 EditPredictionSettings::Disabled => false,
7024 EditPredictionSettings::Enabled { .. } => true,
7025 }
7026 }
7027
7028 fn edit_prediction_requires_modifier(&self) -> bool {
7029 match self.edit_prediction_settings {
7030 EditPredictionSettings::Disabled => false,
7031 EditPredictionSettings::Enabled {
7032 preview_requires_modifier,
7033 ..
7034 } => preview_requires_modifier,
7035 }
7036 }
7037
7038 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7039 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7040 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7041 self.discard_edit_prediction(false, cx);
7042 } else {
7043 let selection = self.selections.newest_anchor();
7044 let cursor = selection.head();
7045
7046 if let Some((buffer, cursor_buffer_position)) =
7047 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7048 {
7049 self.edit_prediction_settings =
7050 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7051 }
7052 }
7053 }
7054
7055 fn edit_prediction_settings_at_position(
7056 &self,
7057 buffer: &Entity<Buffer>,
7058 buffer_position: language::Anchor,
7059 cx: &App,
7060 ) -> EditPredictionSettings {
7061 if !self.mode.is_full()
7062 || !self.show_edit_predictions_override.unwrap_or(true)
7063 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7064 {
7065 return EditPredictionSettings::Disabled;
7066 }
7067
7068 let buffer = buffer.read(cx);
7069
7070 let file = buffer.file();
7071
7072 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7073 return EditPredictionSettings::Disabled;
7074 };
7075
7076 let by_provider = matches!(
7077 self.menu_edit_predictions_policy,
7078 MenuEditPredictionsPolicy::ByProvider
7079 );
7080
7081 let show_in_menu = by_provider
7082 && self
7083 .edit_prediction_provider
7084 .as_ref()
7085 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7086
7087 let preview_requires_modifier =
7088 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7089
7090 EditPredictionSettings::Enabled {
7091 show_in_menu,
7092 preview_requires_modifier,
7093 }
7094 }
7095
7096 fn should_show_edit_predictions(&self) -> bool {
7097 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7098 }
7099
7100 pub fn edit_prediction_preview_is_active(&self) -> bool {
7101 matches!(
7102 self.edit_prediction_preview,
7103 EditPredictionPreview::Active { .. }
7104 )
7105 }
7106
7107 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7108 let cursor = self.selections.newest_anchor().head();
7109 if let Some((buffer, cursor_position)) =
7110 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7111 {
7112 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7113 } else {
7114 false
7115 }
7116 }
7117
7118 pub fn supports_minimap(&self, cx: &App) -> bool {
7119 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7120 }
7121
7122 fn edit_predictions_enabled_in_buffer(
7123 &self,
7124 buffer: &Entity<Buffer>,
7125 buffer_position: language::Anchor,
7126 cx: &App,
7127 ) -> bool {
7128 maybe!({
7129 if self.read_only(cx) {
7130 return Some(false);
7131 }
7132 let provider = self.edit_prediction_provider()?;
7133 if !provider.is_enabled(buffer, buffer_position, cx) {
7134 return Some(false);
7135 }
7136 let buffer = buffer.read(cx);
7137 let Some(file) = buffer.file() else {
7138 return Some(true);
7139 };
7140 let settings = all_language_settings(Some(file), cx);
7141 Some(settings.edit_predictions_enabled_for_file(file, cx))
7142 })
7143 .unwrap_or(false)
7144 }
7145
7146 fn cycle_edit_prediction(
7147 &mut self,
7148 direction: Direction,
7149 window: &mut Window,
7150 cx: &mut Context<Self>,
7151 ) -> Option<()> {
7152 let provider = self.edit_prediction_provider()?;
7153 let cursor = self.selections.newest_anchor().head();
7154 let (buffer, cursor_buffer_position) =
7155 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7156 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7157 return None;
7158 }
7159
7160 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7161 self.update_visible_edit_prediction(window, cx);
7162
7163 Some(())
7164 }
7165
7166 pub fn show_edit_prediction(
7167 &mut self,
7168 _: &ShowEditPrediction,
7169 window: &mut Window,
7170 cx: &mut Context<Self>,
7171 ) {
7172 if !self.has_active_edit_prediction() {
7173 self.refresh_edit_prediction(false, true, window, cx);
7174 return;
7175 }
7176
7177 self.update_visible_edit_prediction(window, cx);
7178 }
7179
7180 pub fn display_cursor_names(
7181 &mut self,
7182 _: &DisplayCursorNames,
7183 window: &mut Window,
7184 cx: &mut Context<Self>,
7185 ) {
7186 self.show_cursor_names(window, cx);
7187 }
7188
7189 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7190 self.show_cursor_names = true;
7191 cx.notify();
7192 cx.spawn_in(window, async move |this, cx| {
7193 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7194 this.update(cx, |this, cx| {
7195 this.show_cursor_names = false;
7196 cx.notify()
7197 })
7198 .ok()
7199 })
7200 .detach();
7201 }
7202
7203 pub fn next_edit_prediction(
7204 &mut self,
7205 _: &NextEditPrediction,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 if self.has_active_edit_prediction() {
7210 self.cycle_edit_prediction(Direction::Next, window, cx);
7211 } else {
7212 let is_copilot_disabled = self
7213 .refresh_edit_prediction(false, true, window, cx)
7214 .is_none();
7215 if is_copilot_disabled {
7216 cx.propagate();
7217 }
7218 }
7219 }
7220
7221 pub fn previous_edit_prediction(
7222 &mut self,
7223 _: &PreviousEditPrediction,
7224 window: &mut Window,
7225 cx: &mut Context<Self>,
7226 ) {
7227 if self.has_active_edit_prediction() {
7228 self.cycle_edit_prediction(Direction::Prev, window, cx);
7229 } else {
7230 let is_copilot_disabled = self
7231 .refresh_edit_prediction(false, true, window, cx)
7232 .is_none();
7233 if is_copilot_disabled {
7234 cx.propagate();
7235 }
7236 }
7237 }
7238
7239 pub fn accept_edit_prediction(
7240 &mut self,
7241 _: &AcceptEditPrediction,
7242 window: &mut Window,
7243 cx: &mut Context<Self>,
7244 ) {
7245 if self.show_edit_predictions_in_menu() {
7246 self.hide_context_menu(window, cx);
7247 }
7248
7249 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7250 return;
7251 };
7252
7253 match &active_edit_prediction.completion {
7254 EditPrediction::MoveWithin { target, .. } => {
7255 let target = *target;
7256
7257 if let Some(position_map) = &self.last_position_map {
7258 if position_map
7259 .visible_row_range
7260 .contains(&target.to_display_point(&position_map.snapshot).row())
7261 || !self.edit_prediction_requires_modifier()
7262 {
7263 self.unfold_ranges(&[target..target], true, false, cx);
7264 // Note that this is also done in vim's handler of the Tab action.
7265 self.change_selections(
7266 SelectionEffects::scroll(Autoscroll::newest()),
7267 window,
7268 cx,
7269 |selections| {
7270 selections.select_anchor_ranges([target..target]);
7271 },
7272 );
7273 self.clear_row_highlights::<EditPredictionPreview>();
7274
7275 self.edit_prediction_preview
7276 .set_previous_scroll_position(None);
7277 } else {
7278 self.edit_prediction_preview
7279 .set_previous_scroll_position(Some(
7280 position_map.snapshot.scroll_anchor,
7281 ));
7282
7283 self.highlight_rows::<EditPredictionPreview>(
7284 target..target,
7285 cx.theme().colors().editor_highlighted_line_background,
7286 RowHighlightOptions {
7287 autoscroll: true,
7288 ..Default::default()
7289 },
7290 cx,
7291 );
7292 self.request_autoscroll(Autoscroll::fit(), cx);
7293 }
7294 }
7295 }
7296 EditPrediction::MoveOutside { snapshot, target } => {
7297 if let Some(workspace) = self.workspace() {
7298 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7299 .detach_and_log_err(cx);
7300 }
7301 }
7302 EditPrediction::Edit { edits, .. } => {
7303 self.report_edit_prediction_event(
7304 active_edit_prediction.completion_id.clone(),
7305 true,
7306 cx,
7307 );
7308
7309 if let Some(provider) = self.edit_prediction_provider() {
7310 provider.accept(cx);
7311 }
7312
7313 // Store the transaction ID and selections before applying the edit
7314 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7315
7316 let snapshot = self.buffer.read(cx).snapshot(cx);
7317 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7318
7319 self.buffer.update(cx, |buffer, cx| {
7320 buffer.edit(edits.iter().cloned(), None, cx)
7321 });
7322
7323 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7324 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7325 });
7326
7327 let selections = self.selections.disjoint_anchors_arc();
7328 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7329 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7330 if has_new_transaction {
7331 self.selection_history
7332 .insert_transaction(transaction_id_now, selections);
7333 }
7334 }
7335
7336 self.update_visible_edit_prediction(window, cx);
7337 if self.active_edit_prediction.is_none() {
7338 self.refresh_edit_prediction(true, true, window, cx);
7339 }
7340
7341 cx.notify();
7342 }
7343 }
7344
7345 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7346 }
7347
7348 pub fn accept_partial_edit_prediction(
7349 &mut self,
7350 _: &AcceptPartialEditPrediction,
7351 window: &mut Window,
7352 cx: &mut Context<Self>,
7353 ) {
7354 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7355 return;
7356 };
7357 if self.selections.count() != 1 {
7358 return;
7359 }
7360
7361 match &active_edit_prediction.completion {
7362 EditPrediction::MoveWithin { target, .. } => {
7363 let target = *target;
7364 self.change_selections(
7365 SelectionEffects::scroll(Autoscroll::newest()),
7366 window,
7367 cx,
7368 |selections| {
7369 selections.select_anchor_ranges([target..target]);
7370 },
7371 );
7372 }
7373 EditPrediction::MoveOutside { snapshot, target } => {
7374 if let Some(workspace) = self.workspace() {
7375 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7376 .detach_and_log_err(cx);
7377 }
7378 }
7379 EditPrediction::Edit { edits, .. } => {
7380 self.report_edit_prediction_event(
7381 active_edit_prediction.completion_id.clone(),
7382 true,
7383 cx,
7384 );
7385
7386 // Find an insertion that starts at the cursor position.
7387 let snapshot = self.buffer.read(cx).snapshot(cx);
7388 let cursor_offset = self
7389 .selections
7390 .newest::<usize>(&self.display_snapshot(cx))
7391 .head();
7392 let insertion = edits.iter().find_map(|(range, text)| {
7393 let range = range.to_offset(&snapshot);
7394 if range.is_empty() && range.start == cursor_offset {
7395 Some(text)
7396 } else {
7397 None
7398 }
7399 });
7400
7401 if let Some(text) = insertion {
7402 let mut partial_completion = text
7403 .chars()
7404 .by_ref()
7405 .take_while(|c| c.is_alphabetic())
7406 .collect::<String>();
7407 if partial_completion.is_empty() {
7408 partial_completion = text
7409 .chars()
7410 .by_ref()
7411 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7412 .collect::<String>();
7413 }
7414
7415 cx.emit(EditorEvent::InputHandled {
7416 utf16_range_to_replace: None,
7417 text: partial_completion.clone().into(),
7418 });
7419
7420 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7421
7422 self.refresh_edit_prediction(true, true, window, cx);
7423 cx.notify();
7424 } else {
7425 self.accept_edit_prediction(&Default::default(), window, cx);
7426 }
7427 }
7428 }
7429 }
7430
7431 fn discard_edit_prediction(
7432 &mut self,
7433 should_report_edit_prediction_event: bool,
7434 cx: &mut Context<Self>,
7435 ) -> bool {
7436 if should_report_edit_prediction_event {
7437 let completion_id = self
7438 .active_edit_prediction
7439 .as_ref()
7440 .and_then(|active_completion| active_completion.completion_id.clone());
7441
7442 self.report_edit_prediction_event(completion_id, false, cx);
7443 }
7444
7445 if let Some(provider) = self.edit_prediction_provider() {
7446 provider.discard(cx);
7447 }
7448
7449 self.take_active_edit_prediction(cx)
7450 }
7451
7452 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7453 let Some(provider) = self.edit_prediction_provider() else {
7454 return;
7455 };
7456
7457 let Some((_, buffer, _)) = self
7458 .buffer
7459 .read(cx)
7460 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7461 else {
7462 return;
7463 };
7464
7465 let extension = buffer
7466 .read(cx)
7467 .file()
7468 .and_then(|file| Some(file.path().extension()?.to_string()));
7469
7470 let event_type = match accepted {
7471 true => "Edit Prediction Accepted",
7472 false => "Edit Prediction Discarded",
7473 };
7474 telemetry::event!(
7475 event_type,
7476 provider = provider.name(),
7477 prediction_id = id,
7478 suggestion_accepted = accepted,
7479 file_extension = extension,
7480 );
7481 }
7482
7483 fn open_editor_at_anchor(
7484 snapshot: &language::BufferSnapshot,
7485 target: language::Anchor,
7486 workspace: &Entity<Workspace>,
7487 window: &mut Window,
7488 cx: &mut App,
7489 ) -> Task<Result<()>> {
7490 workspace.update(cx, |workspace, cx| {
7491 let path = snapshot.file().map(|file| file.full_path(cx));
7492 let Some(path) =
7493 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7494 else {
7495 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7496 };
7497 let target = text::ToPoint::to_point(&target, snapshot);
7498 let item = workspace.open_path(path, None, true, window, cx);
7499 window.spawn(cx, async move |cx| {
7500 let Some(editor) = item.await?.downcast::<Editor>() else {
7501 return Ok(());
7502 };
7503 editor
7504 .update_in(cx, |editor, window, cx| {
7505 editor.go_to_singleton_buffer_point(target, window, cx);
7506 })
7507 .ok();
7508 anyhow::Ok(())
7509 })
7510 })
7511 }
7512
7513 pub fn has_active_edit_prediction(&self) -> bool {
7514 self.active_edit_prediction.is_some()
7515 }
7516
7517 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7518 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7519 return false;
7520 };
7521
7522 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7523 self.clear_highlights::<EditPredictionHighlight>(cx);
7524 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7525 true
7526 }
7527
7528 /// Returns true when we're displaying the edit prediction popover below the cursor
7529 /// like we are not previewing and the LSP autocomplete menu is visible
7530 /// or we are in `when_holding_modifier` mode.
7531 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7532 if self.edit_prediction_preview_is_active()
7533 || !self.show_edit_predictions_in_menu()
7534 || !self.edit_predictions_enabled()
7535 {
7536 return false;
7537 }
7538
7539 if self.has_visible_completions_menu() {
7540 return true;
7541 }
7542
7543 has_completion && self.edit_prediction_requires_modifier()
7544 }
7545
7546 fn handle_modifiers_changed(
7547 &mut self,
7548 modifiers: Modifiers,
7549 position_map: &PositionMap,
7550 window: &mut Window,
7551 cx: &mut Context<Self>,
7552 ) {
7553 if self.show_edit_predictions_in_menu() {
7554 self.update_edit_prediction_preview(&modifiers, window, cx);
7555 }
7556
7557 self.update_selection_mode(&modifiers, position_map, window, cx);
7558
7559 let mouse_position = window.mouse_position();
7560 if !position_map.text_hitbox.is_hovered(window) {
7561 return;
7562 }
7563
7564 self.update_hovered_link(
7565 position_map.point_for_position(mouse_position),
7566 &position_map.snapshot,
7567 modifiers,
7568 window,
7569 cx,
7570 )
7571 }
7572
7573 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7574 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7575 if invert {
7576 match multi_cursor_setting {
7577 MultiCursorModifier::Alt => modifiers.alt,
7578 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7579 }
7580 } else {
7581 match multi_cursor_setting {
7582 MultiCursorModifier::Alt => modifiers.secondary(),
7583 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7584 }
7585 }
7586 }
7587
7588 fn columnar_selection_mode(
7589 modifiers: &Modifiers,
7590 cx: &mut Context<Self>,
7591 ) -> Option<ColumnarMode> {
7592 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7593 if Self::multi_cursor_modifier(false, modifiers, cx) {
7594 Some(ColumnarMode::FromMouse)
7595 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7596 Some(ColumnarMode::FromSelection)
7597 } else {
7598 None
7599 }
7600 } else {
7601 None
7602 }
7603 }
7604
7605 fn update_selection_mode(
7606 &mut self,
7607 modifiers: &Modifiers,
7608 position_map: &PositionMap,
7609 window: &mut Window,
7610 cx: &mut Context<Self>,
7611 ) {
7612 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7613 return;
7614 };
7615 if self.selections.pending_anchor().is_none() {
7616 return;
7617 }
7618
7619 let mouse_position = window.mouse_position();
7620 let point_for_position = position_map.point_for_position(mouse_position);
7621 let position = point_for_position.previous_valid;
7622
7623 self.select(
7624 SelectPhase::BeginColumnar {
7625 position,
7626 reset: false,
7627 mode,
7628 goal_column: point_for_position.exact_unclipped.column(),
7629 },
7630 window,
7631 cx,
7632 );
7633 }
7634
7635 fn update_edit_prediction_preview(
7636 &mut self,
7637 modifiers: &Modifiers,
7638 window: &mut Window,
7639 cx: &mut Context<Self>,
7640 ) {
7641 let mut modifiers_held = false;
7642 if let Some(accept_keystroke) = self
7643 .accept_edit_prediction_keybind(false, window, cx)
7644 .keystroke()
7645 {
7646 modifiers_held = modifiers_held
7647 || (accept_keystroke.modifiers() == modifiers
7648 && accept_keystroke.modifiers().modified());
7649 };
7650 if let Some(accept_partial_keystroke) = self
7651 .accept_edit_prediction_keybind(true, window, cx)
7652 .keystroke()
7653 {
7654 modifiers_held = modifiers_held
7655 || (accept_partial_keystroke.modifiers() == modifiers
7656 && accept_partial_keystroke.modifiers().modified());
7657 }
7658
7659 if modifiers_held {
7660 if matches!(
7661 self.edit_prediction_preview,
7662 EditPredictionPreview::Inactive { .. }
7663 ) {
7664 self.edit_prediction_preview = EditPredictionPreview::Active {
7665 previous_scroll_position: None,
7666 since: Instant::now(),
7667 };
7668
7669 self.update_visible_edit_prediction(window, cx);
7670 cx.notify();
7671 }
7672 } else if let EditPredictionPreview::Active {
7673 previous_scroll_position,
7674 since,
7675 } = self.edit_prediction_preview
7676 {
7677 if let (Some(previous_scroll_position), Some(position_map)) =
7678 (previous_scroll_position, self.last_position_map.as_ref())
7679 {
7680 self.set_scroll_position(
7681 previous_scroll_position
7682 .scroll_position(&position_map.snapshot.display_snapshot),
7683 window,
7684 cx,
7685 );
7686 }
7687
7688 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7689 released_too_fast: since.elapsed() < Duration::from_millis(200),
7690 };
7691 self.clear_row_highlights::<EditPredictionPreview>();
7692 self.update_visible_edit_prediction(window, cx);
7693 cx.notify();
7694 }
7695 }
7696
7697 fn update_visible_edit_prediction(
7698 &mut self,
7699 _window: &mut Window,
7700 cx: &mut Context<Self>,
7701 ) -> Option<()> {
7702 if DisableAiSettings::get_global(cx).disable_ai {
7703 return None;
7704 }
7705
7706 if self.ime_transaction.is_some() {
7707 self.discard_edit_prediction(false, cx);
7708 return None;
7709 }
7710
7711 let selection = self.selections.newest_anchor();
7712 let cursor = selection.head();
7713 let multibuffer = self.buffer.read(cx).snapshot(cx);
7714 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7715 let excerpt_id = cursor.excerpt_id;
7716
7717 let show_in_menu = self.show_edit_predictions_in_menu();
7718 let completions_menu_has_precedence = !show_in_menu
7719 && (self.context_menu.borrow().is_some()
7720 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7721
7722 if completions_menu_has_precedence
7723 || !offset_selection.is_empty()
7724 || self
7725 .active_edit_prediction
7726 .as_ref()
7727 .is_some_and(|completion| {
7728 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7729 return false;
7730 };
7731 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7732 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7733 !invalidation_range.contains(&offset_selection.head())
7734 })
7735 {
7736 self.discard_edit_prediction(false, cx);
7737 return None;
7738 }
7739
7740 self.take_active_edit_prediction(cx);
7741 let Some(provider) = self.edit_prediction_provider() else {
7742 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7743 return None;
7744 };
7745
7746 let (buffer, cursor_buffer_position) =
7747 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7748
7749 self.edit_prediction_settings =
7750 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7751
7752 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7753
7754 if self.edit_prediction_indent_conflict {
7755 let cursor_point = cursor.to_point(&multibuffer);
7756
7757 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7758
7759 if let Some((_, indent)) = indents.iter().next()
7760 && indent.len == cursor_point.column
7761 {
7762 self.edit_prediction_indent_conflict = false;
7763 }
7764 }
7765
7766 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7767
7768 let (completion_id, edits, edit_preview) = match edit_prediction {
7769 edit_prediction::EditPrediction::Local {
7770 id,
7771 edits,
7772 edit_preview,
7773 } => (id, edits, edit_preview),
7774 edit_prediction::EditPrediction::Jump {
7775 id,
7776 snapshot,
7777 target,
7778 } => {
7779 self.stale_edit_prediction_in_menu = None;
7780 self.active_edit_prediction = Some(EditPredictionState {
7781 inlay_ids: vec![],
7782 completion: EditPrediction::MoveOutside { snapshot, target },
7783 completion_id: id,
7784 invalidation_range: None,
7785 });
7786 cx.notify();
7787 return Some(());
7788 }
7789 };
7790
7791 let edits = edits
7792 .into_iter()
7793 .flat_map(|(range, new_text)| {
7794 Some((
7795 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7796 new_text,
7797 ))
7798 })
7799 .collect::<Vec<_>>();
7800 if edits.is_empty() {
7801 return None;
7802 }
7803
7804 let first_edit_start = edits.first().unwrap().0.start;
7805 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7806 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7807
7808 let last_edit_end = edits.last().unwrap().0.end;
7809 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7810 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7811
7812 let cursor_row = cursor.to_point(&multibuffer).row;
7813
7814 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7815
7816 let mut inlay_ids = Vec::new();
7817 let invalidation_row_range;
7818 let move_invalidation_row_range = if cursor_row < edit_start_row {
7819 Some(cursor_row..edit_end_row)
7820 } else if cursor_row > edit_end_row {
7821 Some(edit_start_row..cursor_row)
7822 } else {
7823 None
7824 };
7825 let supports_jump = self
7826 .edit_prediction_provider
7827 .as_ref()
7828 .map(|provider| provider.provider.supports_jump_to_edit())
7829 .unwrap_or(true);
7830
7831 let is_move = supports_jump
7832 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7833 let completion = if is_move {
7834 invalidation_row_range =
7835 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7836 let target = first_edit_start;
7837 EditPrediction::MoveWithin { target, snapshot }
7838 } else {
7839 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7840 && !self.edit_predictions_hidden_for_vim_mode;
7841
7842 if show_completions_in_buffer {
7843 if edits
7844 .iter()
7845 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7846 {
7847 let mut inlays = Vec::new();
7848 for (range, new_text) in &edits {
7849 let inlay = Inlay::edit_prediction(
7850 post_inc(&mut self.next_inlay_id),
7851 range.start,
7852 new_text.as_str(),
7853 );
7854 inlay_ids.push(inlay.id);
7855 inlays.push(inlay);
7856 }
7857
7858 self.splice_inlays(&[], inlays, cx);
7859 } else {
7860 let background_color = cx.theme().status().deleted_background;
7861 self.highlight_text::<EditPredictionHighlight>(
7862 edits.iter().map(|(range, _)| range.clone()).collect(),
7863 HighlightStyle {
7864 background_color: Some(background_color),
7865 ..Default::default()
7866 },
7867 cx,
7868 );
7869 }
7870 }
7871
7872 invalidation_row_range = edit_start_row..edit_end_row;
7873
7874 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7875 if provider.show_tab_accept_marker() {
7876 EditDisplayMode::TabAccept
7877 } else {
7878 EditDisplayMode::Inline
7879 }
7880 } else {
7881 EditDisplayMode::DiffPopover
7882 };
7883
7884 EditPrediction::Edit {
7885 edits,
7886 edit_preview,
7887 display_mode,
7888 snapshot,
7889 }
7890 };
7891
7892 let invalidation_range = multibuffer
7893 .anchor_before(Point::new(invalidation_row_range.start, 0))
7894 ..multibuffer.anchor_after(Point::new(
7895 invalidation_row_range.end,
7896 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7897 ));
7898
7899 self.stale_edit_prediction_in_menu = None;
7900 self.active_edit_prediction = Some(EditPredictionState {
7901 inlay_ids,
7902 completion,
7903 completion_id,
7904 invalidation_range: Some(invalidation_range),
7905 });
7906
7907 cx.notify();
7908
7909 Some(())
7910 }
7911
7912 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7913 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7914 }
7915
7916 fn clear_tasks(&mut self) {
7917 self.tasks.clear()
7918 }
7919
7920 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7921 if self.tasks.insert(key, value).is_some() {
7922 // This case should hopefully be rare, but just in case...
7923 log::error!(
7924 "multiple different run targets found on a single line, only the last target will be rendered"
7925 )
7926 }
7927 }
7928
7929 /// Get all display points of breakpoints that will be rendered within editor
7930 ///
7931 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7932 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7933 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7934 fn active_breakpoints(
7935 &self,
7936 range: Range<DisplayRow>,
7937 window: &mut Window,
7938 cx: &mut Context<Self>,
7939 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7940 let mut breakpoint_display_points = HashMap::default();
7941
7942 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7943 return breakpoint_display_points;
7944 };
7945
7946 let snapshot = self.snapshot(window, cx);
7947
7948 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
7949 let Some(project) = self.project() else {
7950 return breakpoint_display_points;
7951 };
7952
7953 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7954 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7955
7956 for (buffer_snapshot, range, excerpt_id) in
7957 multi_buffer_snapshot.range_to_buffer_ranges(range)
7958 {
7959 let Some(buffer) = project
7960 .read(cx)
7961 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7962 else {
7963 continue;
7964 };
7965 let breakpoints = breakpoint_store.read(cx).breakpoints(
7966 &buffer,
7967 Some(
7968 buffer_snapshot.anchor_before(range.start)
7969 ..buffer_snapshot.anchor_after(range.end),
7970 ),
7971 buffer_snapshot,
7972 cx,
7973 );
7974 for (breakpoint, state) in breakpoints {
7975 let multi_buffer_anchor =
7976 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7977 let position = multi_buffer_anchor
7978 .to_point(multi_buffer_snapshot)
7979 .to_display_point(&snapshot);
7980
7981 breakpoint_display_points.insert(
7982 position.row(),
7983 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7984 );
7985 }
7986 }
7987
7988 breakpoint_display_points
7989 }
7990
7991 fn breakpoint_context_menu(
7992 &self,
7993 anchor: Anchor,
7994 window: &mut Window,
7995 cx: &mut Context<Self>,
7996 ) -> Entity<ui::ContextMenu> {
7997 let weak_editor = cx.weak_entity();
7998 let focus_handle = self.focus_handle(cx);
7999
8000 let row = self
8001 .buffer
8002 .read(cx)
8003 .snapshot(cx)
8004 .summary_for_anchor::<Point>(&anchor)
8005 .row;
8006
8007 let breakpoint = self
8008 .breakpoint_at_row(row, window, cx)
8009 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8010
8011 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8012 "Edit Log Breakpoint"
8013 } else {
8014 "Set Log Breakpoint"
8015 };
8016
8017 let condition_breakpoint_msg = if breakpoint
8018 .as_ref()
8019 .is_some_and(|bp| bp.1.condition.is_some())
8020 {
8021 "Edit Condition Breakpoint"
8022 } else {
8023 "Set Condition Breakpoint"
8024 };
8025
8026 let hit_condition_breakpoint_msg = if breakpoint
8027 .as_ref()
8028 .is_some_and(|bp| bp.1.hit_condition.is_some())
8029 {
8030 "Edit Hit Condition Breakpoint"
8031 } else {
8032 "Set Hit Condition Breakpoint"
8033 };
8034
8035 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8036 "Unset Breakpoint"
8037 } else {
8038 "Set Breakpoint"
8039 };
8040
8041 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8042
8043 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8044 BreakpointState::Enabled => Some("Disable"),
8045 BreakpointState::Disabled => Some("Enable"),
8046 });
8047
8048 let (anchor, breakpoint) =
8049 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8050
8051 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8052 menu.on_blur_subscription(Subscription::new(|| {}))
8053 .context(focus_handle)
8054 .when(run_to_cursor, |this| {
8055 let weak_editor = weak_editor.clone();
8056 this.entry("Run to cursor", None, move |window, cx| {
8057 weak_editor
8058 .update(cx, |editor, cx| {
8059 editor.change_selections(
8060 SelectionEffects::no_scroll(),
8061 window,
8062 cx,
8063 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8064 );
8065 })
8066 .ok();
8067
8068 window.dispatch_action(Box::new(RunToCursor), cx);
8069 })
8070 .separator()
8071 })
8072 .when_some(toggle_state_msg, |this, msg| {
8073 this.entry(msg, None, {
8074 let weak_editor = weak_editor.clone();
8075 let breakpoint = breakpoint.clone();
8076 move |_window, cx| {
8077 weak_editor
8078 .update(cx, |this, cx| {
8079 this.edit_breakpoint_at_anchor(
8080 anchor,
8081 breakpoint.as_ref().clone(),
8082 BreakpointEditAction::InvertState,
8083 cx,
8084 );
8085 })
8086 .log_err();
8087 }
8088 })
8089 })
8090 .entry(set_breakpoint_msg, None, {
8091 let weak_editor = weak_editor.clone();
8092 let breakpoint = breakpoint.clone();
8093 move |_window, cx| {
8094 weak_editor
8095 .update(cx, |this, cx| {
8096 this.edit_breakpoint_at_anchor(
8097 anchor,
8098 breakpoint.as_ref().clone(),
8099 BreakpointEditAction::Toggle,
8100 cx,
8101 );
8102 })
8103 .log_err();
8104 }
8105 })
8106 .entry(log_breakpoint_msg, None, {
8107 let breakpoint = breakpoint.clone();
8108 let weak_editor = weak_editor.clone();
8109 move |window, cx| {
8110 weak_editor
8111 .update(cx, |this, cx| {
8112 this.add_edit_breakpoint_block(
8113 anchor,
8114 breakpoint.as_ref(),
8115 BreakpointPromptEditAction::Log,
8116 window,
8117 cx,
8118 );
8119 })
8120 .log_err();
8121 }
8122 })
8123 .entry(condition_breakpoint_msg, None, {
8124 let breakpoint = breakpoint.clone();
8125 let weak_editor = weak_editor.clone();
8126 move |window, cx| {
8127 weak_editor
8128 .update(cx, |this, cx| {
8129 this.add_edit_breakpoint_block(
8130 anchor,
8131 breakpoint.as_ref(),
8132 BreakpointPromptEditAction::Condition,
8133 window,
8134 cx,
8135 );
8136 })
8137 .log_err();
8138 }
8139 })
8140 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8141 weak_editor
8142 .update(cx, |this, cx| {
8143 this.add_edit_breakpoint_block(
8144 anchor,
8145 breakpoint.as_ref(),
8146 BreakpointPromptEditAction::HitCondition,
8147 window,
8148 cx,
8149 );
8150 })
8151 .log_err();
8152 })
8153 })
8154 }
8155
8156 fn render_breakpoint(
8157 &self,
8158 position: Anchor,
8159 row: DisplayRow,
8160 breakpoint: &Breakpoint,
8161 state: Option<BreakpointSessionState>,
8162 cx: &mut Context<Self>,
8163 ) -> IconButton {
8164 let is_rejected = state.is_some_and(|s| !s.verified);
8165 // Is it a breakpoint that shows up when hovering over gutter?
8166 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8167 (false, false),
8168 |PhantomBreakpointIndicator {
8169 is_active,
8170 display_row,
8171 collides_with_existing_breakpoint,
8172 }| {
8173 (
8174 is_active && display_row == row,
8175 collides_with_existing_breakpoint,
8176 )
8177 },
8178 );
8179
8180 let (color, icon) = {
8181 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8182 (false, false) => ui::IconName::DebugBreakpoint,
8183 (true, false) => ui::IconName::DebugLogBreakpoint,
8184 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8185 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8186 };
8187
8188 let color = if is_phantom {
8189 Color::Hint
8190 } else if is_rejected {
8191 Color::Disabled
8192 } else {
8193 Color::Debugger
8194 };
8195
8196 (color, icon)
8197 };
8198
8199 let breakpoint = Arc::from(breakpoint.clone());
8200
8201 let alt_as_text = gpui::Keystroke {
8202 modifiers: Modifiers::secondary_key(),
8203 ..Default::default()
8204 };
8205 let primary_action_text = if breakpoint.is_disabled() {
8206 "Enable breakpoint"
8207 } else if is_phantom && !collides_with_existing {
8208 "Set breakpoint"
8209 } else {
8210 "Unset breakpoint"
8211 };
8212 let focus_handle = self.focus_handle.clone();
8213
8214 let meta = if is_rejected {
8215 SharedString::from("No executable code is associated with this line.")
8216 } else if collides_with_existing && !breakpoint.is_disabled() {
8217 SharedString::from(format!(
8218 "{alt_as_text}-click to disable,\nright-click for more options."
8219 ))
8220 } else {
8221 SharedString::from("Right-click for more options.")
8222 };
8223 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8224 .icon_size(IconSize::XSmall)
8225 .size(ui::ButtonSize::None)
8226 .when(is_rejected, |this| {
8227 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8228 })
8229 .icon_color(color)
8230 .style(ButtonStyle::Transparent)
8231 .on_click(cx.listener({
8232 move |editor, event: &ClickEvent, window, cx| {
8233 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8234 BreakpointEditAction::InvertState
8235 } else {
8236 BreakpointEditAction::Toggle
8237 };
8238
8239 window.focus(&editor.focus_handle(cx));
8240 editor.edit_breakpoint_at_anchor(
8241 position,
8242 breakpoint.as_ref().clone(),
8243 edit_action,
8244 cx,
8245 );
8246 }
8247 }))
8248 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8249 editor.set_breakpoint_context_menu(
8250 row,
8251 Some(position),
8252 event.position(),
8253 window,
8254 cx,
8255 );
8256 }))
8257 .tooltip(move |_window, cx| {
8258 Tooltip::with_meta_in(
8259 primary_action_text,
8260 Some(&ToggleBreakpoint),
8261 meta.clone(),
8262 &focus_handle,
8263 cx,
8264 )
8265 })
8266 }
8267
8268 fn build_tasks_context(
8269 project: &Entity<Project>,
8270 buffer: &Entity<Buffer>,
8271 buffer_row: u32,
8272 tasks: &Arc<RunnableTasks>,
8273 cx: &mut Context<Self>,
8274 ) -> Task<Option<task::TaskContext>> {
8275 let position = Point::new(buffer_row, tasks.column);
8276 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8277 let location = Location {
8278 buffer: buffer.clone(),
8279 range: range_start..range_start,
8280 };
8281 // Fill in the environmental variables from the tree-sitter captures
8282 let mut captured_task_variables = TaskVariables::default();
8283 for (capture_name, value) in tasks.extra_variables.clone() {
8284 captured_task_variables.insert(
8285 task::VariableName::Custom(capture_name.into()),
8286 value.clone(),
8287 );
8288 }
8289 project.update(cx, |project, cx| {
8290 project.task_store().update(cx, |task_store, cx| {
8291 task_store.task_context_for_location(captured_task_variables, location, cx)
8292 })
8293 })
8294 }
8295
8296 pub fn spawn_nearest_task(
8297 &mut self,
8298 action: &SpawnNearestTask,
8299 window: &mut Window,
8300 cx: &mut Context<Self>,
8301 ) {
8302 let Some((workspace, _)) = self.workspace.clone() else {
8303 return;
8304 };
8305 let Some(project) = self.project.clone() else {
8306 return;
8307 };
8308
8309 // Try to find a closest, enclosing node using tree-sitter that has a task
8310 let Some((buffer, buffer_row, tasks)) = self
8311 .find_enclosing_node_task(cx)
8312 // Or find the task that's closest in row-distance.
8313 .or_else(|| self.find_closest_task(cx))
8314 else {
8315 return;
8316 };
8317
8318 let reveal_strategy = action.reveal;
8319 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8320 cx.spawn_in(window, async move |_, cx| {
8321 let context = task_context.await?;
8322 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8323
8324 let resolved = &mut resolved_task.resolved;
8325 resolved.reveal = reveal_strategy;
8326
8327 workspace
8328 .update_in(cx, |workspace, window, cx| {
8329 workspace.schedule_resolved_task(
8330 task_source_kind,
8331 resolved_task,
8332 false,
8333 window,
8334 cx,
8335 );
8336 })
8337 .ok()
8338 })
8339 .detach();
8340 }
8341
8342 fn find_closest_task(
8343 &mut self,
8344 cx: &mut Context<Self>,
8345 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8346 let cursor_row = self
8347 .selections
8348 .newest_adjusted(&self.display_snapshot(cx))
8349 .head()
8350 .row;
8351
8352 let ((buffer_id, row), tasks) = self
8353 .tasks
8354 .iter()
8355 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8356
8357 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8358 let tasks = Arc::new(tasks.to_owned());
8359 Some((buffer, *row, tasks))
8360 }
8361
8362 fn find_enclosing_node_task(
8363 &mut self,
8364 cx: &mut Context<Self>,
8365 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8366 let snapshot = self.buffer.read(cx).snapshot(cx);
8367 let offset = self
8368 .selections
8369 .newest::<usize>(&self.display_snapshot(cx))
8370 .head();
8371 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8372 let buffer_id = excerpt.buffer().remote_id();
8373
8374 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8375 let mut cursor = layer.node().walk();
8376
8377 while cursor.goto_first_child_for_byte(offset).is_some() {
8378 if cursor.node().end_byte() == offset {
8379 cursor.goto_next_sibling();
8380 }
8381 }
8382
8383 // Ascend to the smallest ancestor that contains the range and has a task.
8384 loop {
8385 let node = cursor.node();
8386 let node_range = node.byte_range();
8387 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8388
8389 // Check if this node contains our offset
8390 if node_range.start <= offset && node_range.end >= offset {
8391 // If it contains offset, check for task
8392 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8393 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8394 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8395 }
8396 }
8397
8398 if !cursor.goto_parent() {
8399 break;
8400 }
8401 }
8402 None
8403 }
8404
8405 fn render_run_indicator(
8406 &self,
8407 _style: &EditorStyle,
8408 is_active: bool,
8409 row: DisplayRow,
8410 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8411 cx: &mut Context<Self>,
8412 ) -> IconButton {
8413 let color = Color::Muted;
8414 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8415
8416 IconButton::new(
8417 ("run_indicator", row.0 as usize),
8418 ui::IconName::PlayOutlined,
8419 )
8420 .shape(ui::IconButtonShape::Square)
8421 .icon_size(IconSize::XSmall)
8422 .icon_color(color)
8423 .toggle_state(is_active)
8424 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8425 let quick_launch = match e {
8426 ClickEvent::Keyboard(_) => true,
8427 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8428 };
8429
8430 window.focus(&editor.focus_handle(cx));
8431 editor.toggle_code_actions(
8432 &ToggleCodeActions {
8433 deployed_from: Some(CodeActionSource::RunMenu(row)),
8434 quick_launch,
8435 },
8436 window,
8437 cx,
8438 );
8439 }))
8440 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8441 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8442 }))
8443 }
8444
8445 pub fn context_menu_visible(&self) -> bool {
8446 !self.edit_prediction_preview_is_active()
8447 && self
8448 .context_menu
8449 .borrow()
8450 .as_ref()
8451 .is_some_and(|menu| menu.visible())
8452 }
8453
8454 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8455 self.context_menu
8456 .borrow()
8457 .as_ref()
8458 .map(|menu| menu.origin())
8459 }
8460
8461 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8462 self.context_menu_options = Some(options);
8463 }
8464
8465 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8466 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8467
8468 fn render_edit_prediction_popover(
8469 &mut self,
8470 text_bounds: &Bounds<Pixels>,
8471 content_origin: gpui::Point<Pixels>,
8472 right_margin: Pixels,
8473 editor_snapshot: &EditorSnapshot,
8474 visible_row_range: Range<DisplayRow>,
8475 scroll_top: ScrollOffset,
8476 scroll_bottom: ScrollOffset,
8477 line_layouts: &[LineWithInvisibles],
8478 line_height: Pixels,
8479 scroll_position: gpui::Point<ScrollOffset>,
8480 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8481 newest_selection_head: Option<DisplayPoint>,
8482 editor_width: Pixels,
8483 style: &EditorStyle,
8484 window: &mut Window,
8485 cx: &mut App,
8486 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8487 if self.mode().is_minimap() {
8488 return None;
8489 }
8490 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8491
8492 if self.edit_prediction_visible_in_cursor_popover(true) {
8493 return None;
8494 }
8495
8496 match &active_edit_prediction.completion {
8497 EditPrediction::MoveWithin { target, .. } => {
8498 let target_display_point = target.to_display_point(editor_snapshot);
8499
8500 if self.edit_prediction_requires_modifier() {
8501 if !self.edit_prediction_preview_is_active() {
8502 return None;
8503 }
8504
8505 self.render_edit_prediction_modifier_jump_popover(
8506 text_bounds,
8507 content_origin,
8508 visible_row_range,
8509 line_layouts,
8510 line_height,
8511 scroll_pixel_position,
8512 newest_selection_head,
8513 target_display_point,
8514 window,
8515 cx,
8516 )
8517 } else {
8518 self.render_edit_prediction_eager_jump_popover(
8519 text_bounds,
8520 content_origin,
8521 editor_snapshot,
8522 visible_row_range,
8523 scroll_top,
8524 scroll_bottom,
8525 line_height,
8526 scroll_pixel_position,
8527 target_display_point,
8528 editor_width,
8529 window,
8530 cx,
8531 )
8532 }
8533 }
8534 EditPrediction::Edit {
8535 display_mode: EditDisplayMode::Inline,
8536 ..
8537 } => None,
8538 EditPrediction::Edit {
8539 display_mode: EditDisplayMode::TabAccept,
8540 edits,
8541 ..
8542 } => {
8543 let range = &edits.first()?.0;
8544 let target_display_point = range.end.to_display_point(editor_snapshot);
8545
8546 self.render_edit_prediction_end_of_line_popover(
8547 "Accept",
8548 editor_snapshot,
8549 visible_row_range,
8550 target_display_point,
8551 line_height,
8552 scroll_pixel_position,
8553 content_origin,
8554 editor_width,
8555 window,
8556 cx,
8557 )
8558 }
8559 EditPrediction::Edit {
8560 edits,
8561 edit_preview,
8562 display_mode: EditDisplayMode::DiffPopover,
8563 snapshot,
8564 } => self.render_edit_prediction_diff_popover(
8565 text_bounds,
8566 content_origin,
8567 right_margin,
8568 editor_snapshot,
8569 visible_row_range,
8570 line_layouts,
8571 line_height,
8572 scroll_position,
8573 scroll_pixel_position,
8574 newest_selection_head,
8575 editor_width,
8576 style,
8577 edits,
8578 edit_preview,
8579 snapshot,
8580 window,
8581 cx,
8582 ),
8583 EditPrediction::MoveOutside { snapshot, .. } => {
8584 let file_name = snapshot
8585 .file()
8586 .map(|file| file.file_name(cx))
8587 .unwrap_or("untitled");
8588 let mut element = self
8589 .render_edit_prediction_line_popover(
8590 format!("Jump to {file_name}"),
8591 Some(IconName::ZedPredict),
8592 window,
8593 cx,
8594 )
8595 .into_any();
8596
8597 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8598 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8599 let origin_y = text_bounds.size.height - size.height - px(30.);
8600 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8601 element.prepaint_at(origin, window, cx);
8602
8603 Some((element, origin))
8604 }
8605 }
8606 }
8607
8608 fn render_edit_prediction_modifier_jump_popover(
8609 &mut self,
8610 text_bounds: &Bounds<Pixels>,
8611 content_origin: gpui::Point<Pixels>,
8612 visible_row_range: Range<DisplayRow>,
8613 line_layouts: &[LineWithInvisibles],
8614 line_height: Pixels,
8615 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8616 newest_selection_head: Option<DisplayPoint>,
8617 target_display_point: DisplayPoint,
8618 window: &mut Window,
8619 cx: &mut App,
8620 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8621 let scrolled_content_origin =
8622 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8623
8624 const SCROLL_PADDING_Y: Pixels = px(12.);
8625
8626 if target_display_point.row() < visible_row_range.start {
8627 return self.render_edit_prediction_scroll_popover(
8628 |_| SCROLL_PADDING_Y,
8629 IconName::ArrowUp,
8630 visible_row_range,
8631 line_layouts,
8632 newest_selection_head,
8633 scrolled_content_origin,
8634 window,
8635 cx,
8636 );
8637 } else if target_display_point.row() >= visible_row_range.end {
8638 return self.render_edit_prediction_scroll_popover(
8639 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8640 IconName::ArrowDown,
8641 visible_row_range,
8642 line_layouts,
8643 newest_selection_head,
8644 scrolled_content_origin,
8645 window,
8646 cx,
8647 );
8648 }
8649
8650 const POLE_WIDTH: Pixels = px(2.);
8651
8652 let line_layout =
8653 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8654 let target_column = target_display_point.column() as usize;
8655
8656 let target_x = line_layout.x_for_index(target_column);
8657 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8658 - scroll_pixel_position.y;
8659
8660 let flag_on_right = target_x < text_bounds.size.width / 2.;
8661
8662 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8663 border_color.l += 0.001;
8664
8665 let mut element = v_flex()
8666 .items_end()
8667 .when(flag_on_right, |el| el.items_start())
8668 .child(if flag_on_right {
8669 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8670 .rounded_bl(px(0.))
8671 .rounded_tl(px(0.))
8672 .border_l_2()
8673 .border_color(border_color)
8674 } else {
8675 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8676 .rounded_br(px(0.))
8677 .rounded_tr(px(0.))
8678 .border_r_2()
8679 .border_color(border_color)
8680 })
8681 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8682 .into_any();
8683
8684 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8685
8686 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8687 - point(
8688 if flag_on_right {
8689 POLE_WIDTH
8690 } else {
8691 size.width - POLE_WIDTH
8692 },
8693 size.height - line_height,
8694 );
8695
8696 origin.x = origin.x.max(content_origin.x);
8697
8698 element.prepaint_at(origin, window, cx);
8699
8700 Some((element, origin))
8701 }
8702
8703 fn render_edit_prediction_scroll_popover(
8704 &mut self,
8705 to_y: impl Fn(Size<Pixels>) -> Pixels,
8706 scroll_icon: IconName,
8707 visible_row_range: Range<DisplayRow>,
8708 line_layouts: &[LineWithInvisibles],
8709 newest_selection_head: Option<DisplayPoint>,
8710 scrolled_content_origin: gpui::Point<Pixels>,
8711 window: &mut Window,
8712 cx: &mut App,
8713 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8714 let mut element = self
8715 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8716 .into_any();
8717
8718 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8719
8720 let cursor = newest_selection_head?;
8721 let cursor_row_layout =
8722 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8723 let cursor_column = cursor.column() as usize;
8724
8725 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8726
8727 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8728
8729 element.prepaint_at(origin, window, cx);
8730 Some((element, origin))
8731 }
8732
8733 fn render_edit_prediction_eager_jump_popover(
8734 &mut self,
8735 text_bounds: &Bounds<Pixels>,
8736 content_origin: gpui::Point<Pixels>,
8737 editor_snapshot: &EditorSnapshot,
8738 visible_row_range: Range<DisplayRow>,
8739 scroll_top: ScrollOffset,
8740 scroll_bottom: ScrollOffset,
8741 line_height: Pixels,
8742 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8743 target_display_point: DisplayPoint,
8744 editor_width: Pixels,
8745 window: &mut Window,
8746 cx: &mut App,
8747 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8748 if target_display_point.row().as_f64() < scroll_top {
8749 let mut element = self
8750 .render_edit_prediction_line_popover(
8751 "Jump to Edit",
8752 Some(IconName::ArrowUp),
8753 window,
8754 cx,
8755 )
8756 .into_any();
8757
8758 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8759 let offset = point(
8760 (text_bounds.size.width - size.width) / 2.,
8761 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8762 );
8763
8764 let origin = text_bounds.origin + offset;
8765 element.prepaint_at(origin, window, cx);
8766 Some((element, origin))
8767 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8768 let mut element = self
8769 .render_edit_prediction_line_popover(
8770 "Jump to Edit",
8771 Some(IconName::ArrowDown),
8772 window,
8773 cx,
8774 )
8775 .into_any();
8776
8777 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8778 let offset = point(
8779 (text_bounds.size.width - size.width) / 2.,
8780 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8781 );
8782
8783 let origin = text_bounds.origin + offset;
8784 element.prepaint_at(origin, window, cx);
8785 Some((element, origin))
8786 } else {
8787 self.render_edit_prediction_end_of_line_popover(
8788 "Jump to Edit",
8789 editor_snapshot,
8790 visible_row_range,
8791 target_display_point,
8792 line_height,
8793 scroll_pixel_position,
8794 content_origin,
8795 editor_width,
8796 window,
8797 cx,
8798 )
8799 }
8800 }
8801
8802 fn render_edit_prediction_end_of_line_popover(
8803 self: &mut Editor,
8804 label: &'static str,
8805 editor_snapshot: &EditorSnapshot,
8806 visible_row_range: Range<DisplayRow>,
8807 target_display_point: DisplayPoint,
8808 line_height: Pixels,
8809 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8810 content_origin: gpui::Point<Pixels>,
8811 editor_width: Pixels,
8812 window: &mut Window,
8813 cx: &mut App,
8814 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8815 let target_line_end = DisplayPoint::new(
8816 target_display_point.row(),
8817 editor_snapshot.line_len(target_display_point.row()),
8818 );
8819
8820 let mut element = self
8821 .render_edit_prediction_line_popover(label, None, window, cx)
8822 .into_any();
8823
8824 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8825
8826 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8827
8828 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8829 let mut origin = start_point
8830 + line_origin
8831 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8832 origin.x = origin.x.max(content_origin.x);
8833
8834 let max_x = content_origin.x + editor_width - size.width;
8835
8836 if origin.x > max_x {
8837 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8838
8839 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8840 origin.y += offset;
8841 IconName::ArrowUp
8842 } else {
8843 origin.y -= offset;
8844 IconName::ArrowDown
8845 };
8846
8847 element = self
8848 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8849 .into_any();
8850
8851 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8852
8853 origin.x = content_origin.x + editor_width - size.width - px(2.);
8854 }
8855
8856 element.prepaint_at(origin, window, cx);
8857 Some((element, origin))
8858 }
8859
8860 fn render_edit_prediction_diff_popover(
8861 self: &Editor,
8862 text_bounds: &Bounds<Pixels>,
8863 content_origin: gpui::Point<Pixels>,
8864 right_margin: Pixels,
8865 editor_snapshot: &EditorSnapshot,
8866 visible_row_range: Range<DisplayRow>,
8867 line_layouts: &[LineWithInvisibles],
8868 line_height: Pixels,
8869 scroll_position: gpui::Point<ScrollOffset>,
8870 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8871 newest_selection_head: Option<DisplayPoint>,
8872 editor_width: Pixels,
8873 style: &EditorStyle,
8874 edits: &Vec<(Range<Anchor>, String)>,
8875 edit_preview: &Option<language::EditPreview>,
8876 snapshot: &language::BufferSnapshot,
8877 window: &mut Window,
8878 cx: &mut App,
8879 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8880 let edit_start = edits
8881 .first()
8882 .unwrap()
8883 .0
8884 .start
8885 .to_display_point(editor_snapshot);
8886 let edit_end = edits
8887 .last()
8888 .unwrap()
8889 .0
8890 .end
8891 .to_display_point(editor_snapshot);
8892
8893 let is_visible = visible_row_range.contains(&edit_start.row())
8894 || visible_row_range.contains(&edit_end.row());
8895 if !is_visible {
8896 return None;
8897 }
8898
8899 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8900 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8901 } else {
8902 // Fallback for providers without edit_preview
8903 crate::edit_prediction_fallback_text(edits, cx)
8904 };
8905
8906 let styled_text = highlighted_edits.to_styled_text(&style.text);
8907 let line_count = highlighted_edits.text.lines().count();
8908
8909 const BORDER_WIDTH: Pixels = px(1.);
8910
8911 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8912 let has_keybind = keybind.is_some();
8913
8914 let mut element = h_flex()
8915 .items_start()
8916 .child(
8917 h_flex()
8918 .bg(cx.theme().colors().editor_background)
8919 .border(BORDER_WIDTH)
8920 .shadow_xs()
8921 .border_color(cx.theme().colors().border)
8922 .rounded_l_lg()
8923 .when(line_count > 1, |el| el.rounded_br_lg())
8924 .pr_1()
8925 .child(styled_text),
8926 )
8927 .child(
8928 h_flex()
8929 .h(line_height + BORDER_WIDTH * 2.)
8930 .px_1p5()
8931 .gap_1()
8932 // Workaround: For some reason, there's a gap if we don't do this
8933 .ml(-BORDER_WIDTH)
8934 .shadow(vec![gpui::BoxShadow {
8935 color: gpui::black().opacity(0.05),
8936 offset: point(px(1.), px(1.)),
8937 blur_radius: px(2.),
8938 spread_radius: px(0.),
8939 }])
8940 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8941 .border(BORDER_WIDTH)
8942 .border_color(cx.theme().colors().border)
8943 .rounded_r_lg()
8944 .id("edit_prediction_diff_popover_keybind")
8945 .when(!has_keybind, |el| {
8946 let status_colors = cx.theme().status();
8947
8948 el.bg(status_colors.error_background)
8949 .border_color(status_colors.error.opacity(0.6))
8950 .child(Icon::new(IconName::Info).color(Color::Error))
8951 .cursor_default()
8952 .hoverable_tooltip(move |_window, cx| {
8953 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8954 })
8955 })
8956 .children(keybind),
8957 )
8958 .into_any();
8959
8960 let longest_row =
8961 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8962 let longest_line_width = if visible_row_range.contains(&longest_row) {
8963 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8964 } else {
8965 layout_line(
8966 longest_row,
8967 editor_snapshot,
8968 style,
8969 editor_width,
8970 |_| false,
8971 window,
8972 cx,
8973 )
8974 .width
8975 };
8976
8977 let viewport_bounds =
8978 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8979 right: -right_margin,
8980 ..Default::default()
8981 });
8982
8983 let x_after_longest = Pixels::from(
8984 ScrollPixelOffset::from(
8985 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
8986 ) - scroll_pixel_position.x,
8987 );
8988
8989 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8990
8991 // Fully visible if it can be displayed within the window (allow overlapping other
8992 // panes). However, this is only allowed if the popover starts within text_bounds.
8993 let can_position_to_the_right = x_after_longest < text_bounds.right()
8994 && x_after_longest + element_bounds.width < viewport_bounds.right();
8995
8996 let mut origin = if can_position_to_the_right {
8997 point(
8998 x_after_longest,
8999 text_bounds.origin.y
9000 + Pixels::from(
9001 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9002 - scroll_pixel_position.y,
9003 ),
9004 )
9005 } else {
9006 let cursor_row = newest_selection_head.map(|head| head.row());
9007 let above_edit = edit_start
9008 .row()
9009 .0
9010 .checked_sub(line_count as u32)
9011 .map(DisplayRow);
9012 let below_edit = Some(edit_end.row() + 1);
9013 let above_cursor =
9014 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9015 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9016
9017 // Place the edit popover adjacent to the edit if there is a location
9018 // available that is onscreen and does not obscure the cursor. Otherwise,
9019 // place it adjacent to the cursor.
9020 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9021 .into_iter()
9022 .flatten()
9023 .find(|&start_row| {
9024 let end_row = start_row + line_count as u32;
9025 visible_row_range.contains(&start_row)
9026 && visible_row_range.contains(&end_row)
9027 && cursor_row
9028 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9029 })?;
9030
9031 content_origin
9032 + point(
9033 Pixels::from(-scroll_pixel_position.x),
9034 Pixels::from(
9035 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9036 ),
9037 )
9038 };
9039
9040 origin.x -= BORDER_WIDTH;
9041
9042 window.defer_draw(element, origin, 1);
9043
9044 // Do not return an element, since it will already be drawn due to defer_draw.
9045 None
9046 }
9047
9048 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9049 px(30.)
9050 }
9051
9052 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9053 if self.read_only(cx) {
9054 cx.theme().players().read_only()
9055 } else {
9056 self.style.as_ref().unwrap().local_player
9057 }
9058 }
9059
9060 fn render_edit_prediction_accept_keybind(
9061 &self,
9062 window: &mut Window,
9063 cx: &mut App,
9064 ) -> Option<AnyElement> {
9065 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9066 let accept_keystroke = accept_binding.keystroke()?;
9067
9068 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9069
9070 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9071 Color::Accent
9072 } else {
9073 Color::Muted
9074 };
9075
9076 h_flex()
9077 .px_0p5()
9078 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9079 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9080 .text_size(TextSize::XSmall.rems(cx))
9081 .child(h_flex().children(ui::render_modifiers(
9082 accept_keystroke.modifiers(),
9083 PlatformStyle::platform(),
9084 Some(modifiers_color),
9085 Some(IconSize::XSmall.rems().into()),
9086 true,
9087 )))
9088 .when(is_platform_style_mac, |parent| {
9089 parent.child(accept_keystroke.key().to_string())
9090 })
9091 .when(!is_platform_style_mac, |parent| {
9092 parent.child(
9093 Key::new(
9094 util::capitalize(accept_keystroke.key()),
9095 Some(Color::Default),
9096 )
9097 .size(Some(IconSize::XSmall.rems().into())),
9098 )
9099 })
9100 .into_any()
9101 .into()
9102 }
9103
9104 fn render_edit_prediction_line_popover(
9105 &self,
9106 label: impl Into<SharedString>,
9107 icon: Option<IconName>,
9108 window: &mut Window,
9109 cx: &mut App,
9110 ) -> Stateful<Div> {
9111 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9112
9113 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9114 let has_keybind = keybind.is_some();
9115
9116 h_flex()
9117 .id("ep-line-popover")
9118 .py_0p5()
9119 .pl_1()
9120 .pr(padding_right)
9121 .gap_1()
9122 .rounded_md()
9123 .border_1()
9124 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9125 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9126 .shadow_xs()
9127 .when(!has_keybind, |el| {
9128 let status_colors = cx.theme().status();
9129
9130 el.bg(status_colors.error_background)
9131 .border_color(status_colors.error.opacity(0.6))
9132 .pl_2()
9133 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9134 .cursor_default()
9135 .hoverable_tooltip(move |_window, cx| {
9136 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9137 })
9138 })
9139 .children(keybind)
9140 .child(
9141 Label::new(label)
9142 .size(LabelSize::Small)
9143 .when(!has_keybind, |el| {
9144 el.color(cx.theme().status().error.into()).strikethrough()
9145 }),
9146 )
9147 .when(!has_keybind, |el| {
9148 el.child(
9149 h_flex().ml_1().child(
9150 Icon::new(IconName::Info)
9151 .size(IconSize::Small)
9152 .color(cx.theme().status().error.into()),
9153 ),
9154 )
9155 })
9156 .when_some(icon, |element, icon| {
9157 element.child(
9158 div()
9159 .mt(px(1.5))
9160 .child(Icon::new(icon).size(IconSize::Small)),
9161 )
9162 })
9163 }
9164
9165 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9166 let accent_color = cx.theme().colors().text_accent;
9167 let editor_bg_color = cx.theme().colors().editor_background;
9168 editor_bg_color.blend(accent_color.opacity(0.1))
9169 }
9170
9171 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9172 let accent_color = cx.theme().colors().text_accent;
9173 let editor_bg_color = cx.theme().colors().editor_background;
9174 editor_bg_color.blend(accent_color.opacity(0.6))
9175 }
9176 fn get_prediction_provider_icon_name(
9177 provider: &Option<RegisteredEditPredictionProvider>,
9178 ) -> IconName {
9179 match provider {
9180 Some(provider) => match provider.provider.name() {
9181 "copilot" => IconName::Copilot,
9182 "supermaven" => IconName::Supermaven,
9183 _ => IconName::ZedPredict,
9184 },
9185 None => IconName::ZedPredict,
9186 }
9187 }
9188
9189 fn render_edit_prediction_cursor_popover(
9190 &self,
9191 min_width: Pixels,
9192 max_width: Pixels,
9193 cursor_point: Point,
9194 style: &EditorStyle,
9195 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9196 _window: &Window,
9197 cx: &mut Context<Editor>,
9198 ) -> Option<AnyElement> {
9199 let provider = self.edit_prediction_provider.as_ref()?;
9200 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9201
9202 let is_refreshing = provider.provider.is_refreshing(cx);
9203
9204 fn pending_completion_container(icon: IconName) -> Div {
9205 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9206 }
9207
9208 let completion = match &self.active_edit_prediction {
9209 Some(prediction) => {
9210 if !self.has_visible_completions_menu() {
9211 const RADIUS: Pixels = px(6.);
9212 const BORDER_WIDTH: Pixels = px(1.);
9213
9214 return Some(
9215 h_flex()
9216 .elevation_2(cx)
9217 .border(BORDER_WIDTH)
9218 .border_color(cx.theme().colors().border)
9219 .when(accept_keystroke.is_none(), |el| {
9220 el.border_color(cx.theme().status().error)
9221 })
9222 .rounded(RADIUS)
9223 .rounded_tl(px(0.))
9224 .overflow_hidden()
9225 .child(div().px_1p5().child(match &prediction.completion {
9226 EditPrediction::MoveWithin { target, snapshot } => {
9227 use text::ToPoint as _;
9228 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9229 {
9230 Icon::new(IconName::ZedPredictDown)
9231 } else {
9232 Icon::new(IconName::ZedPredictUp)
9233 }
9234 }
9235 EditPrediction::MoveOutside { .. } => {
9236 // TODO [zeta2] custom icon for external jump?
9237 Icon::new(provider_icon)
9238 }
9239 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9240 }))
9241 .child(
9242 h_flex()
9243 .gap_1()
9244 .py_1()
9245 .px_2()
9246 .rounded_r(RADIUS - BORDER_WIDTH)
9247 .border_l_1()
9248 .border_color(cx.theme().colors().border)
9249 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9250 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9251 el.child(
9252 Label::new("Hold")
9253 .size(LabelSize::Small)
9254 .when(accept_keystroke.is_none(), |el| {
9255 el.strikethrough()
9256 })
9257 .line_height_style(LineHeightStyle::UiLabel),
9258 )
9259 })
9260 .id("edit_prediction_cursor_popover_keybind")
9261 .when(accept_keystroke.is_none(), |el| {
9262 let status_colors = cx.theme().status();
9263
9264 el.bg(status_colors.error_background)
9265 .border_color(status_colors.error.opacity(0.6))
9266 .child(Icon::new(IconName::Info).color(Color::Error))
9267 .cursor_default()
9268 .hoverable_tooltip(move |_window, cx| {
9269 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9270 .into()
9271 })
9272 })
9273 .when_some(
9274 accept_keystroke.as_ref(),
9275 |el, accept_keystroke| {
9276 el.child(h_flex().children(ui::render_modifiers(
9277 accept_keystroke.modifiers(),
9278 PlatformStyle::platform(),
9279 Some(Color::Default),
9280 Some(IconSize::XSmall.rems().into()),
9281 false,
9282 )))
9283 },
9284 ),
9285 )
9286 .into_any(),
9287 );
9288 }
9289
9290 self.render_edit_prediction_cursor_popover_preview(
9291 prediction,
9292 cursor_point,
9293 style,
9294 cx,
9295 )?
9296 }
9297
9298 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9299 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9300 stale_completion,
9301 cursor_point,
9302 style,
9303 cx,
9304 )?,
9305
9306 None => pending_completion_container(provider_icon)
9307 .child(Label::new("...").size(LabelSize::Small)),
9308 },
9309
9310 None => pending_completion_container(provider_icon)
9311 .child(Label::new("...").size(LabelSize::Small)),
9312 };
9313
9314 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9315 completion
9316 .with_animation(
9317 "loading-completion",
9318 Animation::new(Duration::from_secs(2))
9319 .repeat()
9320 .with_easing(pulsating_between(0.4, 0.8)),
9321 |label, delta| label.opacity(delta),
9322 )
9323 .into_any_element()
9324 } else {
9325 completion.into_any_element()
9326 };
9327
9328 let has_completion = self.active_edit_prediction.is_some();
9329
9330 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9331 Some(
9332 h_flex()
9333 .min_w(min_width)
9334 .max_w(max_width)
9335 .flex_1()
9336 .elevation_2(cx)
9337 .border_color(cx.theme().colors().border)
9338 .child(
9339 div()
9340 .flex_1()
9341 .py_1()
9342 .px_2()
9343 .overflow_hidden()
9344 .child(completion),
9345 )
9346 .when_some(accept_keystroke, |el, accept_keystroke| {
9347 if !accept_keystroke.modifiers().modified() {
9348 return el;
9349 }
9350
9351 el.child(
9352 h_flex()
9353 .h_full()
9354 .border_l_1()
9355 .rounded_r_lg()
9356 .border_color(cx.theme().colors().border)
9357 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9358 .gap_1()
9359 .py_1()
9360 .px_2()
9361 .child(
9362 h_flex()
9363 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9364 .when(is_platform_style_mac, |parent| parent.gap_1())
9365 .child(h_flex().children(ui::render_modifiers(
9366 accept_keystroke.modifiers(),
9367 PlatformStyle::platform(),
9368 Some(if !has_completion {
9369 Color::Muted
9370 } else {
9371 Color::Default
9372 }),
9373 None,
9374 false,
9375 ))),
9376 )
9377 .child(Label::new("Preview").into_any_element())
9378 .opacity(if has_completion { 1.0 } else { 0.4 }),
9379 )
9380 })
9381 .into_any(),
9382 )
9383 }
9384
9385 fn render_edit_prediction_cursor_popover_preview(
9386 &self,
9387 completion: &EditPredictionState,
9388 cursor_point: Point,
9389 style: &EditorStyle,
9390 cx: &mut Context<Editor>,
9391 ) -> Option<Div> {
9392 use text::ToPoint as _;
9393
9394 fn render_relative_row_jump(
9395 prefix: impl Into<String>,
9396 current_row: u32,
9397 target_row: u32,
9398 ) -> Div {
9399 let (row_diff, arrow) = if target_row < current_row {
9400 (current_row - target_row, IconName::ArrowUp)
9401 } else {
9402 (target_row - current_row, IconName::ArrowDown)
9403 };
9404
9405 h_flex()
9406 .child(
9407 Label::new(format!("{}{}", prefix.into(), row_diff))
9408 .color(Color::Muted)
9409 .size(LabelSize::Small),
9410 )
9411 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9412 }
9413
9414 let supports_jump = self
9415 .edit_prediction_provider
9416 .as_ref()
9417 .map(|provider| provider.provider.supports_jump_to_edit())
9418 .unwrap_or(true);
9419
9420 match &completion.completion {
9421 EditPrediction::MoveWithin {
9422 target, snapshot, ..
9423 } => {
9424 if !supports_jump {
9425 return None;
9426 }
9427
9428 Some(
9429 h_flex()
9430 .px_2()
9431 .gap_2()
9432 .flex_1()
9433 .child(
9434 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9435 Icon::new(IconName::ZedPredictDown)
9436 } else {
9437 Icon::new(IconName::ZedPredictUp)
9438 },
9439 )
9440 .child(Label::new("Jump to Edit")),
9441 )
9442 }
9443 EditPrediction::MoveOutside { snapshot, .. } => {
9444 let file_name = snapshot
9445 .file()
9446 .map(|file| file.file_name(cx))
9447 .unwrap_or("untitled");
9448 Some(
9449 h_flex()
9450 .px_2()
9451 .gap_2()
9452 .flex_1()
9453 .child(Icon::new(IconName::ZedPredict))
9454 .child(Label::new(format!("Jump to {file_name}"))),
9455 )
9456 }
9457 EditPrediction::Edit {
9458 edits,
9459 edit_preview,
9460 snapshot,
9461 display_mode: _,
9462 } => {
9463 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9464
9465 let (highlighted_edits, has_more_lines) =
9466 if let Some(edit_preview) = edit_preview.as_ref() {
9467 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9468 .first_line_preview()
9469 } else {
9470 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9471 };
9472
9473 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9474 .with_default_highlights(&style.text, highlighted_edits.highlights);
9475
9476 let preview = h_flex()
9477 .gap_1()
9478 .min_w_16()
9479 .child(styled_text)
9480 .when(has_more_lines, |parent| parent.child("…"));
9481
9482 let left = if supports_jump && first_edit_row != cursor_point.row {
9483 render_relative_row_jump("", cursor_point.row, first_edit_row)
9484 .into_any_element()
9485 } else {
9486 let icon_name =
9487 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9488 Icon::new(icon_name).into_any_element()
9489 };
9490
9491 Some(
9492 h_flex()
9493 .h_full()
9494 .flex_1()
9495 .gap_2()
9496 .pr_1()
9497 .overflow_x_hidden()
9498 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9499 .child(left)
9500 .child(preview),
9501 )
9502 }
9503 }
9504 }
9505
9506 pub fn render_context_menu(
9507 &self,
9508 style: &EditorStyle,
9509 max_height_in_lines: u32,
9510 window: &mut Window,
9511 cx: &mut Context<Editor>,
9512 ) -> Option<AnyElement> {
9513 let menu = self.context_menu.borrow();
9514 let menu = menu.as_ref()?;
9515 if !menu.visible() {
9516 return None;
9517 };
9518 Some(menu.render(style, max_height_in_lines, window, cx))
9519 }
9520
9521 fn render_context_menu_aside(
9522 &mut self,
9523 max_size: Size<Pixels>,
9524 window: &mut Window,
9525 cx: &mut Context<Editor>,
9526 ) -> Option<AnyElement> {
9527 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9528 if menu.visible() {
9529 menu.render_aside(max_size, window, cx)
9530 } else {
9531 None
9532 }
9533 })
9534 }
9535
9536 fn hide_context_menu(
9537 &mut self,
9538 window: &mut Window,
9539 cx: &mut Context<Self>,
9540 ) -> Option<CodeContextMenu> {
9541 cx.notify();
9542 self.completion_tasks.clear();
9543 let context_menu = self.context_menu.borrow_mut().take();
9544 self.stale_edit_prediction_in_menu.take();
9545 self.update_visible_edit_prediction(window, cx);
9546 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9547 && let Some(completion_provider) = &self.completion_provider
9548 {
9549 completion_provider.selection_changed(None, window, cx);
9550 }
9551 context_menu
9552 }
9553
9554 fn show_snippet_choices(
9555 &mut self,
9556 choices: &Vec<String>,
9557 selection: Range<Anchor>,
9558 cx: &mut Context<Self>,
9559 ) {
9560 let Some((_, buffer, _)) = self
9561 .buffer()
9562 .read(cx)
9563 .excerpt_containing(selection.start, cx)
9564 else {
9565 return;
9566 };
9567 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9568 else {
9569 return;
9570 };
9571 if buffer != end_buffer {
9572 log::error!("expected anchor range to have matching buffer IDs");
9573 return;
9574 }
9575
9576 let id = post_inc(&mut self.next_completion_id);
9577 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9578 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9579 CompletionsMenu::new_snippet_choices(
9580 id,
9581 true,
9582 choices,
9583 selection,
9584 buffer,
9585 snippet_sort_order,
9586 ),
9587 ));
9588 }
9589
9590 pub fn insert_snippet(
9591 &mut self,
9592 insertion_ranges: &[Range<usize>],
9593 snippet: Snippet,
9594 window: &mut Window,
9595 cx: &mut Context<Self>,
9596 ) -> Result<()> {
9597 struct Tabstop<T> {
9598 is_end_tabstop: bool,
9599 ranges: Vec<Range<T>>,
9600 choices: Option<Vec<String>>,
9601 }
9602
9603 let tabstops = self.buffer.update(cx, |buffer, cx| {
9604 let snippet_text: Arc<str> = snippet.text.clone().into();
9605 let edits = insertion_ranges
9606 .iter()
9607 .cloned()
9608 .map(|range| (range, snippet_text.clone()));
9609 let autoindent_mode = AutoindentMode::Block {
9610 original_indent_columns: Vec::new(),
9611 };
9612 buffer.edit(edits, Some(autoindent_mode), cx);
9613
9614 let snapshot = &*buffer.read(cx);
9615 let snippet = &snippet;
9616 snippet
9617 .tabstops
9618 .iter()
9619 .map(|tabstop| {
9620 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9621 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9622 });
9623 let mut tabstop_ranges = tabstop
9624 .ranges
9625 .iter()
9626 .flat_map(|tabstop_range| {
9627 let mut delta = 0_isize;
9628 insertion_ranges.iter().map(move |insertion_range| {
9629 let insertion_start = insertion_range.start as isize + delta;
9630 delta +=
9631 snippet.text.len() as isize - insertion_range.len() as isize;
9632
9633 let start = ((insertion_start + tabstop_range.start) as usize)
9634 .min(snapshot.len());
9635 let end = ((insertion_start + tabstop_range.end) as usize)
9636 .min(snapshot.len());
9637 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9638 })
9639 })
9640 .collect::<Vec<_>>();
9641 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9642
9643 Tabstop {
9644 is_end_tabstop,
9645 ranges: tabstop_ranges,
9646 choices: tabstop.choices.clone(),
9647 }
9648 })
9649 .collect::<Vec<_>>()
9650 });
9651 if let Some(tabstop) = tabstops.first() {
9652 self.change_selections(Default::default(), window, cx, |s| {
9653 // Reverse order so that the first range is the newest created selection.
9654 // Completions will use it and autoscroll will prioritize it.
9655 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9656 });
9657
9658 if let Some(choices) = &tabstop.choices
9659 && let Some(selection) = tabstop.ranges.first()
9660 {
9661 self.show_snippet_choices(choices, selection.clone(), cx)
9662 }
9663
9664 // If we're already at the last tabstop and it's at the end of the snippet,
9665 // we're done, we don't need to keep the state around.
9666 if !tabstop.is_end_tabstop {
9667 let choices = tabstops
9668 .iter()
9669 .map(|tabstop| tabstop.choices.clone())
9670 .collect();
9671
9672 let ranges = tabstops
9673 .into_iter()
9674 .map(|tabstop| tabstop.ranges)
9675 .collect::<Vec<_>>();
9676
9677 self.snippet_stack.push(SnippetState {
9678 active_index: 0,
9679 ranges,
9680 choices,
9681 });
9682 }
9683
9684 // Check whether the just-entered snippet ends with an auto-closable bracket.
9685 if self.autoclose_regions.is_empty() {
9686 let snapshot = self.buffer.read(cx).snapshot(cx);
9687 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9688 let selection_head = selection.head();
9689 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9690 continue;
9691 };
9692
9693 let mut bracket_pair = None;
9694 let max_lookup_length = scope
9695 .brackets()
9696 .map(|(pair, _)| {
9697 pair.start
9698 .as_str()
9699 .chars()
9700 .count()
9701 .max(pair.end.as_str().chars().count())
9702 })
9703 .max();
9704 if let Some(max_lookup_length) = max_lookup_length {
9705 let next_text = snapshot
9706 .chars_at(selection_head)
9707 .take(max_lookup_length)
9708 .collect::<String>();
9709 let prev_text = snapshot
9710 .reversed_chars_at(selection_head)
9711 .take(max_lookup_length)
9712 .collect::<String>();
9713
9714 for (pair, enabled) in scope.brackets() {
9715 if enabled
9716 && pair.close
9717 && prev_text.starts_with(pair.start.as_str())
9718 && next_text.starts_with(pair.end.as_str())
9719 {
9720 bracket_pair = Some(pair.clone());
9721 break;
9722 }
9723 }
9724 }
9725
9726 if let Some(pair) = bracket_pair {
9727 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9728 let autoclose_enabled =
9729 self.use_autoclose && snapshot_settings.use_autoclose;
9730 if autoclose_enabled {
9731 let start = snapshot.anchor_after(selection_head);
9732 let end = snapshot.anchor_after(selection_head);
9733 self.autoclose_regions.push(AutocloseRegion {
9734 selection_id: selection.id,
9735 range: start..end,
9736 pair,
9737 });
9738 }
9739 }
9740 }
9741 }
9742 }
9743 Ok(())
9744 }
9745
9746 pub fn move_to_next_snippet_tabstop(
9747 &mut self,
9748 window: &mut Window,
9749 cx: &mut Context<Self>,
9750 ) -> bool {
9751 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9752 }
9753
9754 pub fn move_to_prev_snippet_tabstop(
9755 &mut self,
9756 window: &mut Window,
9757 cx: &mut Context<Self>,
9758 ) -> bool {
9759 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9760 }
9761
9762 pub fn move_to_snippet_tabstop(
9763 &mut self,
9764 bias: Bias,
9765 window: &mut Window,
9766 cx: &mut Context<Self>,
9767 ) -> bool {
9768 if let Some(mut snippet) = self.snippet_stack.pop() {
9769 match bias {
9770 Bias::Left => {
9771 if snippet.active_index > 0 {
9772 snippet.active_index -= 1;
9773 } else {
9774 self.snippet_stack.push(snippet);
9775 return false;
9776 }
9777 }
9778 Bias::Right => {
9779 if snippet.active_index + 1 < snippet.ranges.len() {
9780 snippet.active_index += 1;
9781 } else {
9782 self.snippet_stack.push(snippet);
9783 return false;
9784 }
9785 }
9786 }
9787 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9788 self.change_selections(Default::default(), window, cx, |s| {
9789 // Reverse order so that the first range is the newest created selection.
9790 // Completions will use it and autoscroll will prioritize it.
9791 s.select_ranges(current_ranges.iter().rev().cloned())
9792 });
9793
9794 if let Some(choices) = &snippet.choices[snippet.active_index]
9795 && let Some(selection) = current_ranges.first()
9796 {
9797 self.show_snippet_choices(choices, selection.clone(), cx);
9798 }
9799
9800 // If snippet state is not at the last tabstop, push it back on the stack
9801 if snippet.active_index + 1 < snippet.ranges.len() {
9802 self.snippet_stack.push(snippet);
9803 }
9804 return true;
9805 }
9806 }
9807
9808 false
9809 }
9810
9811 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9812 self.transact(window, cx, |this, window, cx| {
9813 this.select_all(&SelectAll, window, cx);
9814 this.insert("", window, cx);
9815 });
9816 }
9817
9818 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9819 if self.read_only(cx) {
9820 return;
9821 }
9822 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9823 self.transact(window, cx, |this, window, cx| {
9824 this.select_autoclose_pair(window, cx);
9825
9826 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9827
9828 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9829 if !this.linked_edit_ranges.is_empty() {
9830 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9831 let snapshot = this.buffer.read(cx).snapshot(cx);
9832
9833 for selection in selections.iter() {
9834 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9835 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9836 if selection_start.buffer_id != selection_end.buffer_id {
9837 continue;
9838 }
9839 if let Some(ranges) =
9840 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9841 {
9842 for (buffer, entries) in ranges {
9843 linked_ranges.entry(buffer).or_default().extend(entries);
9844 }
9845 }
9846 }
9847 }
9848
9849 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9850 for selection in &mut selections {
9851 if selection.is_empty() {
9852 let old_head = selection.head();
9853 let mut new_head =
9854 movement::left(&display_map, old_head.to_display_point(&display_map))
9855 .to_point(&display_map);
9856 if let Some((buffer, line_buffer_range)) = display_map
9857 .buffer_snapshot()
9858 .buffer_line_for_row(MultiBufferRow(old_head.row))
9859 {
9860 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9861 let indent_len = match indent_size.kind {
9862 IndentKind::Space => {
9863 buffer.settings_at(line_buffer_range.start, cx).tab_size
9864 }
9865 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9866 };
9867 if old_head.column <= indent_size.len && old_head.column > 0 {
9868 let indent_len = indent_len.get();
9869 new_head = cmp::min(
9870 new_head,
9871 MultiBufferPoint::new(
9872 old_head.row,
9873 ((old_head.column - 1) / indent_len) * indent_len,
9874 ),
9875 );
9876 }
9877 }
9878
9879 selection.set_head(new_head, SelectionGoal::None);
9880 }
9881 }
9882
9883 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9884 this.insert("", window, cx);
9885 let empty_str: Arc<str> = Arc::from("");
9886 for (buffer, edits) in linked_ranges {
9887 let snapshot = buffer.read(cx).snapshot();
9888 use text::ToPoint as TP;
9889
9890 let edits = edits
9891 .into_iter()
9892 .map(|range| {
9893 let end_point = TP::to_point(&range.end, &snapshot);
9894 let mut start_point = TP::to_point(&range.start, &snapshot);
9895
9896 if end_point == start_point {
9897 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9898 .saturating_sub(1);
9899 start_point =
9900 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9901 };
9902
9903 (start_point..end_point, empty_str.clone())
9904 })
9905 .sorted_by_key(|(range, _)| range.start)
9906 .collect::<Vec<_>>();
9907 buffer.update(cx, |this, cx| {
9908 this.edit(edits, None, cx);
9909 })
9910 }
9911 this.refresh_edit_prediction(true, false, window, cx);
9912 refresh_linked_ranges(this, window, cx);
9913 });
9914 }
9915
9916 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9917 if self.read_only(cx) {
9918 return;
9919 }
9920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9921 self.transact(window, cx, |this, window, cx| {
9922 this.change_selections(Default::default(), window, cx, |s| {
9923 s.move_with(|map, selection| {
9924 if selection.is_empty() {
9925 let cursor = movement::right(map, selection.head());
9926 selection.end = cursor;
9927 selection.reversed = true;
9928 selection.goal = SelectionGoal::None;
9929 }
9930 })
9931 });
9932 this.insert("", window, cx);
9933 this.refresh_edit_prediction(true, false, window, cx);
9934 });
9935 }
9936
9937 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9938 if self.mode.is_single_line() {
9939 cx.propagate();
9940 return;
9941 }
9942
9943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9944 if self.move_to_prev_snippet_tabstop(window, cx) {
9945 return;
9946 }
9947 self.outdent(&Outdent, window, cx);
9948 }
9949
9950 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9951 if self.mode.is_single_line() {
9952 cx.propagate();
9953 return;
9954 }
9955
9956 if self.move_to_next_snippet_tabstop(window, cx) {
9957 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9958 return;
9959 }
9960 if self.read_only(cx) {
9961 return;
9962 }
9963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9964 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
9965 let buffer = self.buffer.read(cx);
9966 let snapshot = buffer.snapshot(cx);
9967 let rows_iter = selections.iter().map(|s| s.head().row);
9968 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9969
9970 let has_some_cursor_in_whitespace = selections
9971 .iter()
9972 .filter(|selection| selection.is_empty())
9973 .any(|selection| {
9974 let cursor = selection.head();
9975 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9976 cursor.column < current_indent.len
9977 });
9978
9979 let mut edits = Vec::new();
9980 let mut prev_edited_row = 0;
9981 let mut row_delta = 0;
9982 for selection in &mut selections {
9983 if selection.start.row != prev_edited_row {
9984 row_delta = 0;
9985 }
9986 prev_edited_row = selection.end.row;
9987
9988 // If the selection is non-empty, then increase the indentation of the selected lines.
9989 if !selection.is_empty() {
9990 row_delta =
9991 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9992 continue;
9993 }
9994
9995 let cursor = selection.head();
9996 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9997 if let Some(suggested_indent) =
9998 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9999 {
10000 // Don't do anything if already at suggested indent
10001 // and there is any other cursor which is not
10002 if has_some_cursor_in_whitespace
10003 && cursor.column == current_indent.len
10004 && current_indent.len == suggested_indent.len
10005 {
10006 continue;
10007 }
10008
10009 // Adjust line and move cursor to suggested indent
10010 // if cursor is not at suggested indent
10011 if cursor.column < suggested_indent.len
10012 && cursor.column <= current_indent.len
10013 && current_indent.len <= suggested_indent.len
10014 {
10015 selection.start = Point::new(cursor.row, suggested_indent.len);
10016 selection.end = selection.start;
10017 if row_delta == 0 {
10018 edits.extend(Buffer::edit_for_indent_size_adjustment(
10019 cursor.row,
10020 current_indent,
10021 suggested_indent,
10022 ));
10023 row_delta = suggested_indent.len - current_indent.len;
10024 }
10025 continue;
10026 }
10027
10028 // If current indent is more than suggested indent
10029 // only move cursor to current indent and skip indent
10030 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10031 selection.start = Point::new(cursor.row, current_indent.len);
10032 selection.end = selection.start;
10033 continue;
10034 }
10035 }
10036
10037 // Otherwise, insert a hard or soft tab.
10038 let settings = buffer.language_settings_at(cursor, cx);
10039 let tab_size = if settings.hard_tabs {
10040 IndentSize::tab()
10041 } else {
10042 let tab_size = settings.tab_size.get();
10043 let indent_remainder = snapshot
10044 .text_for_range(Point::new(cursor.row, 0)..cursor)
10045 .flat_map(str::chars)
10046 .fold(row_delta % tab_size, |counter: u32, c| {
10047 if c == '\t' {
10048 0
10049 } else {
10050 (counter + 1) % tab_size
10051 }
10052 });
10053
10054 let chars_to_next_tab_stop = tab_size - indent_remainder;
10055 IndentSize::spaces(chars_to_next_tab_stop)
10056 };
10057 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10058 selection.end = selection.start;
10059 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10060 row_delta += tab_size.len;
10061 }
10062
10063 self.transact(window, cx, |this, window, cx| {
10064 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10065 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10066 this.refresh_edit_prediction(true, false, window, cx);
10067 });
10068 }
10069
10070 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10071 if self.read_only(cx) {
10072 return;
10073 }
10074 if self.mode.is_single_line() {
10075 cx.propagate();
10076 return;
10077 }
10078
10079 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10080 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10081 let mut prev_edited_row = 0;
10082 let mut row_delta = 0;
10083 let mut edits = Vec::new();
10084 let buffer = self.buffer.read(cx);
10085 let snapshot = buffer.snapshot(cx);
10086 for selection in &mut selections {
10087 if selection.start.row != prev_edited_row {
10088 row_delta = 0;
10089 }
10090 prev_edited_row = selection.end.row;
10091
10092 row_delta =
10093 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10094 }
10095
10096 self.transact(window, cx, |this, window, cx| {
10097 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10098 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10099 });
10100 }
10101
10102 fn indent_selection(
10103 buffer: &MultiBuffer,
10104 snapshot: &MultiBufferSnapshot,
10105 selection: &mut Selection<Point>,
10106 edits: &mut Vec<(Range<Point>, String)>,
10107 delta_for_start_row: u32,
10108 cx: &App,
10109 ) -> u32 {
10110 let settings = buffer.language_settings_at(selection.start, cx);
10111 let tab_size = settings.tab_size.get();
10112 let indent_kind = if settings.hard_tabs {
10113 IndentKind::Tab
10114 } else {
10115 IndentKind::Space
10116 };
10117 let mut start_row = selection.start.row;
10118 let mut end_row = selection.end.row + 1;
10119
10120 // If a selection ends at the beginning of a line, don't indent
10121 // that last line.
10122 if selection.end.column == 0 && selection.end.row > selection.start.row {
10123 end_row -= 1;
10124 }
10125
10126 // Avoid re-indenting a row that has already been indented by a
10127 // previous selection, but still update this selection's column
10128 // to reflect that indentation.
10129 if delta_for_start_row > 0 {
10130 start_row += 1;
10131 selection.start.column += delta_for_start_row;
10132 if selection.end.row == selection.start.row {
10133 selection.end.column += delta_for_start_row;
10134 }
10135 }
10136
10137 let mut delta_for_end_row = 0;
10138 let has_multiple_rows = start_row + 1 != end_row;
10139 for row in start_row..end_row {
10140 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10141 let indent_delta = match (current_indent.kind, indent_kind) {
10142 (IndentKind::Space, IndentKind::Space) => {
10143 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10144 IndentSize::spaces(columns_to_next_tab_stop)
10145 }
10146 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10147 (_, IndentKind::Tab) => IndentSize::tab(),
10148 };
10149
10150 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10151 0
10152 } else {
10153 selection.start.column
10154 };
10155 let row_start = Point::new(row, start);
10156 edits.push((
10157 row_start..row_start,
10158 indent_delta.chars().collect::<String>(),
10159 ));
10160
10161 // Update this selection's endpoints to reflect the indentation.
10162 if row == selection.start.row {
10163 selection.start.column += indent_delta.len;
10164 }
10165 if row == selection.end.row {
10166 selection.end.column += indent_delta.len;
10167 delta_for_end_row = indent_delta.len;
10168 }
10169 }
10170
10171 if selection.start.row == selection.end.row {
10172 delta_for_start_row + delta_for_end_row
10173 } else {
10174 delta_for_end_row
10175 }
10176 }
10177
10178 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10179 if self.read_only(cx) {
10180 return;
10181 }
10182 if self.mode.is_single_line() {
10183 cx.propagate();
10184 return;
10185 }
10186
10187 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10189 let selections = self.selections.all::<Point>(&display_map);
10190 let mut deletion_ranges = Vec::new();
10191 let mut last_outdent = None;
10192 {
10193 let buffer = self.buffer.read(cx);
10194 let snapshot = buffer.snapshot(cx);
10195 for selection in &selections {
10196 let settings = buffer.language_settings_at(selection.start, cx);
10197 let tab_size = settings.tab_size.get();
10198 let mut rows = selection.spanned_rows(false, &display_map);
10199
10200 // Avoid re-outdenting a row that has already been outdented by a
10201 // previous selection.
10202 if let Some(last_row) = last_outdent
10203 && last_row == rows.start
10204 {
10205 rows.start = rows.start.next_row();
10206 }
10207 let has_multiple_rows = rows.len() > 1;
10208 for row in rows.iter_rows() {
10209 let indent_size = snapshot.indent_size_for_line(row);
10210 if indent_size.len > 0 {
10211 let deletion_len = match indent_size.kind {
10212 IndentKind::Space => {
10213 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10214 if columns_to_prev_tab_stop == 0 {
10215 tab_size
10216 } else {
10217 columns_to_prev_tab_stop
10218 }
10219 }
10220 IndentKind::Tab => 1,
10221 };
10222 let start = if has_multiple_rows
10223 || deletion_len > selection.start.column
10224 || indent_size.len < selection.start.column
10225 {
10226 0
10227 } else {
10228 selection.start.column - deletion_len
10229 };
10230 deletion_ranges.push(
10231 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10232 );
10233 last_outdent = Some(row);
10234 }
10235 }
10236 }
10237 }
10238
10239 self.transact(window, cx, |this, window, cx| {
10240 this.buffer.update(cx, |buffer, cx| {
10241 let empty_str: Arc<str> = Arc::default();
10242 buffer.edit(
10243 deletion_ranges
10244 .into_iter()
10245 .map(|range| (range, empty_str.clone())),
10246 None,
10247 cx,
10248 );
10249 });
10250 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10251 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10252 });
10253 }
10254
10255 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10256 if self.read_only(cx) {
10257 return;
10258 }
10259 if self.mode.is_single_line() {
10260 cx.propagate();
10261 return;
10262 }
10263
10264 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10265 let selections = self
10266 .selections
10267 .all::<usize>(&self.display_snapshot(cx))
10268 .into_iter()
10269 .map(|s| s.range());
10270
10271 self.transact(window, cx, |this, window, cx| {
10272 this.buffer.update(cx, |buffer, cx| {
10273 buffer.autoindent_ranges(selections, cx);
10274 });
10275 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10276 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10277 });
10278 }
10279
10280 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10282 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10283 let selections = self.selections.all::<Point>(&display_map);
10284
10285 let mut new_cursors = Vec::new();
10286 let mut edit_ranges = Vec::new();
10287 let mut selections = selections.iter().peekable();
10288 while let Some(selection) = selections.next() {
10289 let mut rows = selection.spanned_rows(false, &display_map);
10290
10291 // Accumulate contiguous regions of rows that we want to delete.
10292 while let Some(next_selection) = selections.peek() {
10293 let next_rows = next_selection.spanned_rows(false, &display_map);
10294 if next_rows.start <= rows.end {
10295 rows.end = next_rows.end;
10296 selections.next().unwrap();
10297 } else {
10298 break;
10299 }
10300 }
10301
10302 let buffer = display_map.buffer_snapshot();
10303 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10304 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10305 // If there's a line after the range, delete the \n from the end of the row range
10306 (
10307 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10308 rows.end,
10309 )
10310 } else {
10311 // If there isn't a line after the range, delete the \n from the line before the
10312 // start of the row range
10313 edit_start = edit_start.saturating_sub(1);
10314 (buffer.len(), rows.start.previous_row())
10315 };
10316
10317 let text_layout_details = self.text_layout_details(window);
10318 let x = display_map.x_for_display_point(
10319 selection.head().to_display_point(&display_map),
10320 &text_layout_details,
10321 );
10322 let row = Point::new(target_row.0, 0)
10323 .to_display_point(&display_map)
10324 .row();
10325 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10326
10327 new_cursors.push((
10328 selection.id,
10329 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10330 SelectionGoal::None,
10331 ));
10332 edit_ranges.push(edit_start..edit_end);
10333 }
10334
10335 self.transact(window, cx, |this, window, cx| {
10336 let buffer = this.buffer.update(cx, |buffer, cx| {
10337 let empty_str: Arc<str> = Arc::default();
10338 buffer.edit(
10339 edit_ranges
10340 .into_iter()
10341 .map(|range| (range, empty_str.clone())),
10342 None,
10343 cx,
10344 );
10345 buffer.snapshot(cx)
10346 });
10347 let new_selections = new_cursors
10348 .into_iter()
10349 .map(|(id, cursor, goal)| {
10350 let cursor = cursor.to_point(&buffer);
10351 Selection {
10352 id,
10353 start: cursor,
10354 end: cursor,
10355 reversed: false,
10356 goal,
10357 }
10358 })
10359 .collect();
10360
10361 this.change_selections(Default::default(), window, cx, |s| {
10362 s.select(new_selections);
10363 });
10364 });
10365 }
10366
10367 pub fn join_lines_impl(
10368 &mut self,
10369 insert_whitespace: bool,
10370 window: &mut Window,
10371 cx: &mut Context<Self>,
10372 ) {
10373 if self.read_only(cx) {
10374 return;
10375 }
10376 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10377 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10378 let start = MultiBufferRow(selection.start.row);
10379 // Treat single line selections as if they include the next line. Otherwise this action
10380 // would do nothing for single line selections individual cursors.
10381 let end = if selection.start.row == selection.end.row {
10382 MultiBufferRow(selection.start.row + 1)
10383 } else {
10384 MultiBufferRow(selection.end.row)
10385 };
10386
10387 if let Some(last_row_range) = row_ranges.last_mut()
10388 && start <= last_row_range.end
10389 {
10390 last_row_range.end = end;
10391 continue;
10392 }
10393 row_ranges.push(start..end);
10394 }
10395
10396 let snapshot = self.buffer.read(cx).snapshot(cx);
10397 let mut cursor_positions = Vec::new();
10398 for row_range in &row_ranges {
10399 let anchor = snapshot.anchor_before(Point::new(
10400 row_range.end.previous_row().0,
10401 snapshot.line_len(row_range.end.previous_row()),
10402 ));
10403 cursor_positions.push(anchor..anchor);
10404 }
10405
10406 self.transact(window, cx, |this, window, cx| {
10407 for row_range in row_ranges.into_iter().rev() {
10408 for row in row_range.iter_rows().rev() {
10409 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10410 let next_line_row = row.next_row();
10411 let indent = snapshot.indent_size_for_line(next_line_row);
10412 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10413
10414 let replace =
10415 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10416 " "
10417 } else {
10418 ""
10419 };
10420
10421 this.buffer.update(cx, |buffer, cx| {
10422 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10423 });
10424 }
10425 }
10426
10427 this.change_selections(Default::default(), window, cx, |s| {
10428 s.select_anchor_ranges(cursor_positions)
10429 });
10430 });
10431 }
10432
10433 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10435 self.join_lines_impl(true, window, cx);
10436 }
10437
10438 pub fn sort_lines_case_sensitive(
10439 &mut self,
10440 _: &SortLinesCaseSensitive,
10441 window: &mut Window,
10442 cx: &mut Context<Self>,
10443 ) {
10444 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10445 }
10446
10447 pub fn sort_lines_by_length(
10448 &mut self,
10449 _: &SortLinesByLength,
10450 window: &mut Window,
10451 cx: &mut Context<Self>,
10452 ) {
10453 self.manipulate_immutable_lines(window, cx, |lines| {
10454 lines.sort_by_key(|&line| line.chars().count())
10455 })
10456 }
10457
10458 pub fn sort_lines_case_insensitive(
10459 &mut self,
10460 _: &SortLinesCaseInsensitive,
10461 window: &mut Window,
10462 cx: &mut Context<Self>,
10463 ) {
10464 self.manipulate_immutable_lines(window, cx, |lines| {
10465 lines.sort_by_key(|line| line.to_lowercase())
10466 })
10467 }
10468
10469 pub fn unique_lines_case_insensitive(
10470 &mut self,
10471 _: &UniqueLinesCaseInsensitive,
10472 window: &mut Window,
10473 cx: &mut Context<Self>,
10474 ) {
10475 self.manipulate_immutable_lines(window, cx, |lines| {
10476 let mut seen = HashSet::default();
10477 lines.retain(|line| seen.insert(line.to_lowercase()));
10478 })
10479 }
10480
10481 pub fn unique_lines_case_sensitive(
10482 &mut self,
10483 _: &UniqueLinesCaseSensitive,
10484 window: &mut Window,
10485 cx: &mut Context<Self>,
10486 ) {
10487 self.manipulate_immutable_lines(window, cx, |lines| {
10488 let mut seen = HashSet::default();
10489 lines.retain(|line| seen.insert(*line));
10490 })
10491 }
10492
10493 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10494 let snapshot = self.buffer.read(cx).snapshot(cx);
10495 for selection in self.selections.disjoint_anchors_arc().iter() {
10496 if snapshot
10497 .language_at(selection.start)
10498 .and_then(|lang| lang.config().wrap_characters.as_ref())
10499 .is_some()
10500 {
10501 return true;
10502 }
10503 }
10504 false
10505 }
10506
10507 fn wrap_selections_in_tag(
10508 &mut self,
10509 _: &WrapSelectionsInTag,
10510 window: &mut Window,
10511 cx: &mut Context<Self>,
10512 ) {
10513 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10514
10515 let snapshot = self.buffer.read(cx).snapshot(cx);
10516
10517 let mut edits = Vec::new();
10518 let mut boundaries = Vec::new();
10519
10520 for selection in self
10521 .selections
10522 .all::<Point>(&self.display_snapshot(cx))
10523 .iter()
10524 {
10525 let Some(wrap_config) = snapshot
10526 .language_at(selection.start)
10527 .and_then(|lang| lang.config().wrap_characters.clone())
10528 else {
10529 continue;
10530 };
10531
10532 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10533 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10534
10535 let start_before = snapshot.anchor_before(selection.start);
10536 let end_after = snapshot.anchor_after(selection.end);
10537
10538 edits.push((start_before..start_before, open_tag));
10539 edits.push((end_after..end_after, close_tag));
10540
10541 boundaries.push((
10542 start_before,
10543 end_after,
10544 wrap_config.start_prefix.len(),
10545 wrap_config.end_suffix.len(),
10546 ));
10547 }
10548
10549 if edits.is_empty() {
10550 return;
10551 }
10552
10553 self.transact(window, cx, |this, window, cx| {
10554 let buffer = this.buffer.update(cx, |buffer, cx| {
10555 buffer.edit(edits, None, cx);
10556 buffer.snapshot(cx)
10557 });
10558
10559 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10560 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10561 boundaries.into_iter()
10562 {
10563 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10564 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10565 new_selections.push(open_offset..open_offset);
10566 new_selections.push(close_offset..close_offset);
10567 }
10568
10569 this.change_selections(Default::default(), window, cx, |s| {
10570 s.select_ranges(new_selections);
10571 });
10572
10573 this.request_autoscroll(Autoscroll::fit(), cx);
10574 });
10575 }
10576
10577 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10578 let Some(project) = self.project.clone() else {
10579 return;
10580 };
10581 self.reload(project, window, cx)
10582 .detach_and_notify_err(window, cx);
10583 }
10584
10585 pub fn restore_file(
10586 &mut self,
10587 _: &::git::RestoreFile,
10588 window: &mut Window,
10589 cx: &mut Context<Self>,
10590 ) {
10591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10592 let mut buffer_ids = HashSet::default();
10593 let snapshot = self.buffer().read(cx).snapshot(cx);
10594 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10595 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10596 }
10597
10598 let buffer = self.buffer().read(cx);
10599 let ranges = buffer_ids
10600 .into_iter()
10601 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10602 .collect::<Vec<_>>();
10603
10604 self.restore_hunks_in_ranges(ranges, window, cx);
10605 }
10606
10607 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10609 let selections = self
10610 .selections
10611 .all(&self.display_snapshot(cx))
10612 .into_iter()
10613 .map(|s| s.range())
10614 .collect();
10615 self.restore_hunks_in_ranges(selections, window, cx);
10616 }
10617
10618 pub fn restore_hunks_in_ranges(
10619 &mut self,
10620 ranges: Vec<Range<Point>>,
10621 window: &mut Window,
10622 cx: &mut Context<Editor>,
10623 ) {
10624 let mut revert_changes = HashMap::default();
10625 let chunk_by = self
10626 .snapshot(window, cx)
10627 .hunks_for_ranges(ranges)
10628 .into_iter()
10629 .chunk_by(|hunk| hunk.buffer_id);
10630 for (buffer_id, hunks) in &chunk_by {
10631 let hunks = hunks.collect::<Vec<_>>();
10632 for hunk in &hunks {
10633 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10634 }
10635 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10636 }
10637 drop(chunk_by);
10638 if !revert_changes.is_empty() {
10639 self.transact(window, cx, |editor, window, cx| {
10640 editor.restore(revert_changes, window, cx);
10641 });
10642 }
10643 }
10644
10645 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10646 if let Some(status) = self
10647 .addons
10648 .iter()
10649 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10650 {
10651 return Some(status);
10652 }
10653 self.project
10654 .as_ref()?
10655 .read(cx)
10656 .status_for_buffer_id(buffer_id, cx)
10657 }
10658
10659 pub fn open_active_item_in_terminal(
10660 &mut self,
10661 _: &OpenInTerminal,
10662 window: &mut Window,
10663 cx: &mut Context<Self>,
10664 ) {
10665 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10666 let project_path = buffer.read(cx).project_path(cx)?;
10667 let project = self.project()?.read(cx);
10668 let entry = project.entry_for_path(&project_path, cx)?;
10669 let parent = match &entry.canonical_path {
10670 Some(canonical_path) => canonical_path.to_path_buf(),
10671 None => project.absolute_path(&project_path, cx)?,
10672 }
10673 .parent()?
10674 .to_path_buf();
10675 Some(parent)
10676 }) {
10677 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10678 }
10679 }
10680
10681 fn set_breakpoint_context_menu(
10682 &mut self,
10683 display_row: DisplayRow,
10684 position: Option<Anchor>,
10685 clicked_point: gpui::Point<Pixels>,
10686 window: &mut Window,
10687 cx: &mut Context<Self>,
10688 ) {
10689 let source = self
10690 .buffer
10691 .read(cx)
10692 .snapshot(cx)
10693 .anchor_before(Point::new(display_row.0, 0u32));
10694
10695 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10696
10697 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10698 self,
10699 source,
10700 clicked_point,
10701 context_menu,
10702 window,
10703 cx,
10704 );
10705 }
10706
10707 fn add_edit_breakpoint_block(
10708 &mut self,
10709 anchor: Anchor,
10710 breakpoint: &Breakpoint,
10711 edit_action: BreakpointPromptEditAction,
10712 window: &mut Window,
10713 cx: &mut Context<Self>,
10714 ) {
10715 let weak_editor = cx.weak_entity();
10716 let bp_prompt = cx.new(|cx| {
10717 BreakpointPromptEditor::new(
10718 weak_editor,
10719 anchor,
10720 breakpoint.clone(),
10721 edit_action,
10722 window,
10723 cx,
10724 )
10725 });
10726
10727 let height = bp_prompt.update(cx, |this, cx| {
10728 this.prompt
10729 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10730 });
10731 let cloned_prompt = bp_prompt.clone();
10732 let blocks = vec![BlockProperties {
10733 style: BlockStyle::Sticky,
10734 placement: BlockPlacement::Above(anchor),
10735 height: Some(height),
10736 render: Arc::new(move |cx| {
10737 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10738 cloned_prompt.clone().into_any_element()
10739 }),
10740 priority: 0,
10741 }];
10742
10743 let focus_handle = bp_prompt.focus_handle(cx);
10744 window.focus(&focus_handle);
10745
10746 let block_ids = self.insert_blocks(blocks, None, cx);
10747 bp_prompt.update(cx, |prompt, _| {
10748 prompt.add_block_ids(block_ids);
10749 });
10750 }
10751
10752 pub(crate) fn breakpoint_at_row(
10753 &self,
10754 row: u32,
10755 window: &mut Window,
10756 cx: &mut Context<Self>,
10757 ) -> Option<(Anchor, Breakpoint)> {
10758 let snapshot = self.snapshot(window, cx);
10759 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10760
10761 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10762 }
10763
10764 pub(crate) fn breakpoint_at_anchor(
10765 &self,
10766 breakpoint_position: Anchor,
10767 snapshot: &EditorSnapshot,
10768 cx: &mut Context<Self>,
10769 ) -> Option<(Anchor, Breakpoint)> {
10770 let buffer = self
10771 .buffer
10772 .read(cx)
10773 .buffer_for_anchor(breakpoint_position, cx)?;
10774
10775 let enclosing_excerpt = breakpoint_position.excerpt_id;
10776 let buffer_snapshot = buffer.read(cx).snapshot();
10777
10778 let row = buffer_snapshot
10779 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10780 .row;
10781
10782 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10783 let anchor_end = snapshot
10784 .buffer_snapshot()
10785 .anchor_after(Point::new(row, line_len));
10786
10787 self.breakpoint_store
10788 .as_ref()?
10789 .read_with(cx, |breakpoint_store, cx| {
10790 breakpoint_store
10791 .breakpoints(
10792 &buffer,
10793 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10794 &buffer_snapshot,
10795 cx,
10796 )
10797 .next()
10798 .and_then(|(bp, _)| {
10799 let breakpoint_row = buffer_snapshot
10800 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10801 .row;
10802
10803 if breakpoint_row == row {
10804 snapshot
10805 .buffer_snapshot()
10806 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10807 .map(|position| (position, bp.bp.clone()))
10808 } else {
10809 None
10810 }
10811 })
10812 })
10813 }
10814
10815 pub fn edit_log_breakpoint(
10816 &mut self,
10817 _: &EditLogBreakpoint,
10818 window: &mut Window,
10819 cx: &mut Context<Self>,
10820 ) {
10821 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10822 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10823 message: None,
10824 state: BreakpointState::Enabled,
10825 condition: None,
10826 hit_condition: None,
10827 });
10828
10829 self.add_edit_breakpoint_block(
10830 anchor,
10831 &breakpoint,
10832 BreakpointPromptEditAction::Log,
10833 window,
10834 cx,
10835 );
10836 }
10837 }
10838
10839 fn breakpoints_at_cursors(
10840 &self,
10841 window: &mut Window,
10842 cx: &mut Context<Self>,
10843 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10844 let snapshot = self.snapshot(window, cx);
10845 let cursors = self
10846 .selections
10847 .disjoint_anchors_arc()
10848 .iter()
10849 .map(|selection| {
10850 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10851
10852 let breakpoint_position = self
10853 .breakpoint_at_row(cursor_position.row, window, cx)
10854 .map(|bp| bp.0)
10855 .unwrap_or_else(|| {
10856 snapshot
10857 .display_snapshot
10858 .buffer_snapshot()
10859 .anchor_after(Point::new(cursor_position.row, 0))
10860 });
10861
10862 let breakpoint = self
10863 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10864 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10865
10866 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10867 })
10868 // 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.
10869 .collect::<HashMap<Anchor, _>>();
10870
10871 cursors.into_iter().collect()
10872 }
10873
10874 pub fn enable_breakpoint(
10875 &mut self,
10876 _: &crate::actions::EnableBreakpoint,
10877 window: &mut Window,
10878 cx: &mut Context<Self>,
10879 ) {
10880 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10881 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10882 continue;
10883 };
10884 self.edit_breakpoint_at_anchor(
10885 anchor,
10886 breakpoint,
10887 BreakpointEditAction::InvertState,
10888 cx,
10889 );
10890 }
10891 }
10892
10893 pub fn disable_breakpoint(
10894 &mut self,
10895 _: &crate::actions::DisableBreakpoint,
10896 window: &mut Window,
10897 cx: &mut Context<Self>,
10898 ) {
10899 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10900 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10901 continue;
10902 };
10903 self.edit_breakpoint_at_anchor(
10904 anchor,
10905 breakpoint,
10906 BreakpointEditAction::InvertState,
10907 cx,
10908 );
10909 }
10910 }
10911
10912 pub fn toggle_breakpoint(
10913 &mut self,
10914 _: &crate::actions::ToggleBreakpoint,
10915 window: &mut Window,
10916 cx: &mut Context<Self>,
10917 ) {
10918 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10919 if let Some(breakpoint) = breakpoint {
10920 self.edit_breakpoint_at_anchor(
10921 anchor,
10922 breakpoint,
10923 BreakpointEditAction::Toggle,
10924 cx,
10925 );
10926 } else {
10927 self.edit_breakpoint_at_anchor(
10928 anchor,
10929 Breakpoint::new_standard(),
10930 BreakpointEditAction::Toggle,
10931 cx,
10932 );
10933 }
10934 }
10935 }
10936
10937 pub fn edit_breakpoint_at_anchor(
10938 &mut self,
10939 breakpoint_position: Anchor,
10940 breakpoint: Breakpoint,
10941 edit_action: BreakpointEditAction,
10942 cx: &mut Context<Self>,
10943 ) {
10944 let Some(breakpoint_store) = &self.breakpoint_store else {
10945 return;
10946 };
10947
10948 let Some(buffer) = self
10949 .buffer
10950 .read(cx)
10951 .buffer_for_anchor(breakpoint_position, cx)
10952 else {
10953 return;
10954 };
10955
10956 breakpoint_store.update(cx, |breakpoint_store, cx| {
10957 breakpoint_store.toggle_breakpoint(
10958 buffer,
10959 BreakpointWithPosition {
10960 position: breakpoint_position.text_anchor,
10961 bp: breakpoint,
10962 },
10963 edit_action,
10964 cx,
10965 );
10966 });
10967
10968 cx.notify();
10969 }
10970
10971 #[cfg(any(test, feature = "test-support"))]
10972 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10973 self.breakpoint_store.clone()
10974 }
10975
10976 pub fn prepare_restore_change(
10977 &self,
10978 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10979 hunk: &MultiBufferDiffHunk,
10980 cx: &mut App,
10981 ) -> Option<()> {
10982 if hunk.is_created_file() {
10983 return None;
10984 }
10985 let buffer = self.buffer.read(cx);
10986 let diff = buffer.diff_for(hunk.buffer_id)?;
10987 let buffer = buffer.buffer(hunk.buffer_id)?;
10988 let buffer = buffer.read(cx);
10989 let original_text = diff
10990 .read(cx)
10991 .base_text()
10992 .as_rope()
10993 .slice(hunk.diff_base_byte_range.clone());
10994 let buffer_snapshot = buffer.snapshot();
10995 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10996 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10997 probe
10998 .0
10999 .start
11000 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11001 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11002 }) {
11003 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11004 Some(())
11005 } else {
11006 None
11007 }
11008 }
11009
11010 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11011 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11012 }
11013
11014 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11015 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11016 }
11017
11018 fn manipulate_lines<M>(
11019 &mut self,
11020 window: &mut Window,
11021 cx: &mut Context<Self>,
11022 mut manipulate: M,
11023 ) where
11024 M: FnMut(&str) -> LineManipulationResult,
11025 {
11026 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11027
11028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11029 let buffer = self.buffer.read(cx).snapshot(cx);
11030
11031 let mut edits = Vec::new();
11032
11033 let selections = self.selections.all::<Point>(&display_map);
11034 let mut selections = selections.iter().peekable();
11035 let mut contiguous_row_selections = Vec::new();
11036 let mut new_selections = Vec::new();
11037 let mut added_lines = 0;
11038 let mut removed_lines = 0;
11039
11040 while let Some(selection) = selections.next() {
11041 let (start_row, end_row) = consume_contiguous_rows(
11042 &mut contiguous_row_selections,
11043 selection,
11044 &display_map,
11045 &mut selections,
11046 );
11047
11048 let start_point = Point::new(start_row.0, 0);
11049 let end_point = Point::new(
11050 end_row.previous_row().0,
11051 buffer.line_len(end_row.previous_row()),
11052 );
11053 let text = buffer
11054 .text_for_range(start_point..end_point)
11055 .collect::<String>();
11056
11057 let LineManipulationResult {
11058 new_text,
11059 line_count_before,
11060 line_count_after,
11061 } = manipulate(&text);
11062
11063 edits.push((start_point..end_point, new_text));
11064
11065 // Selections must change based on added and removed line count
11066 let start_row =
11067 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11068 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11069 new_selections.push(Selection {
11070 id: selection.id,
11071 start: start_row,
11072 end: end_row,
11073 goal: SelectionGoal::None,
11074 reversed: selection.reversed,
11075 });
11076
11077 if line_count_after > line_count_before {
11078 added_lines += line_count_after - line_count_before;
11079 } else if line_count_before > line_count_after {
11080 removed_lines += line_count_before - line_count_after;
11081 }
11082 }
11083
11084 self.transact(window, cx, |this, window, cx| {
11085 let buffer = this.buffer.update(cx, |buffer, cx| {
11086 buffer.edit(edits, None, cx);
11087 buffer.snapshot(cx)
11088 });
11089
11090 // Recalculate offsets on newly edited buffer
11091 let new_selections = new_selections
11092 .iter()
11093 .map(|s| {
11094 let start_point = Point::new(s.start.0, 0);
11095 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11096 Selection {
11097 id: s.id,
11098 start: buffer.point_to_offset(start_point),
11099 end: buffer.point_to_offset(end_point),
11100 goal: s.goal,
11101 reversed: s.reversed,
11102 }
11103 })
11104 .collect();
11105
11106 this.change_selections(Default::default(), window, cx, |s| {
11107 s.select(new_selections);
11108 });
11109
11110 this.request_autoscroll(Autoscroll::fit(), cx);
11111 });
11112 }
11113
11114 fn manipulate_immutable_lines<Fn>(
11115 &mut self,
11116 window: &mut Window,
11117 cx: &mut Context<Self>,
11118 mut callback: Fn,
11119 ) where
11120 Fn: FnMut(&mut Vec<&str>),
11121 {
11122 self.manipulate_lines(window, cx, |text| {
11123 let mut lines: Vec<&str> = text.split('\n').collect();
11124 let line_count_before = lines.len();
11125
11126 callback(&mut lines);
11127
11128 LineManipulationResult {
11129 new_text: lines.join("\n"),
11130 line_count_before,
11131 line_count_after: lines.len(),
11132 }
11133 });
11134 }
11135
11136 fn manipulate_mutable_lines<Fn>(
11137 &mut self,
11138 window: &mut Window,
11139 cx: &mut Context<Self>,
11140 mut callback: Fn,
11141 ) where
11142 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11143 {
11144 self.manipulate_lines(window, cx, |text| {
11145 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11146 let line_count_before = lines.len();
11147
11148 callback(&mut lines);
11149
11150 LineManipulationResult {
11151 new_text: lines.join("\n"),
11152 line_count_before,
11153 line_count_after: lines.len(),
11154 }
11155 });
11156 }
11157
11158 pub fn convert_indentation_to_spaces(
11159 &mut self,
11160 _: &ConvertIndentationToSpaces,
11161 window: &mut Window,
11162 cx: &mut Context<Self>,
11163 ) {
11164 let settings = self.buffer.read(cx).language_settings(cx);
11165 let tab_size = settings.tab_size.get() as usize;
11166
11167 self.manipulate_mutable_lines(window, cx, |lines| {
11168 // Allocates a reasonably sized scratch buffer once for the whole loop
11169 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11170 // Avoids recomputing spaces that could be inserted many times
11171 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11172 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11173 .collect();
11174
11175 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11176 let mut chars = line.as_ref().chars();
11177 let mut col = 0;
11178 let mut changed = false;
11179
11180 for ch in chars.by_ref() {
11181 match ch {
11182 ' ' => {
11183 reindented_line.push(' ');
11184 col += 1;
11185 }
11186 '\t' => {
11187 // \t are converted to spaces depending on the current column
11188 let spaces_len = tab_size - (col % tab_size);
11189 reindented_line.extend(&space_cache[spaces_len - 1]);
11190 col += spaces_len;
11191 changed = true;
11192 }
11193 _ => {
11194 // If we dont append before break, the character is consumed
11195 reindented_line.push(ch);
11196 break;
11197 }
11198 }
11199 }
11200
11201 if !changed {
11202 reindented_line.clear();
11203 continue;
11204 }
11205 // Append the rest of the line and replace old reference with new one
11206 reindented_line.extend(chars);
11207 *line = Cow::Owned(reindented_line.clone());
11208 reindented_line.clear();
11209 }
11210 });
11211 }
11212
11213 pub fn convert_indentation_to_tabs(
11214 &mut self,
11215 _: &ConvertIndentationToTabs,
11216 window: &mut Window,
11217 cx: &mut Context<Self>,
11218 ) {
11219 let settings = self.buffer.read(cx).language_settings(cx);
11220 let tab_size = settings.tab_size.get() as usize;
11221
11222 self.manipulate_mutable_lines(window, cx, |lines| {
11223 // Allocates a reasonably sized buffer once for the whole loop
11224 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11225 // Avoids recomputing spaces that could be inserted many times
11226 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11227 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11228 .collect();
11229
11230 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11231 let mut chars = line.chars();
11232 let mut spaces_count = 0;
11233 let mut first_non_indent_char = None;
11234 let mut changed = false;
11235
11236 for ch in chars.by_ref() {
11237 match ch {
11238 ' ' => {
11239 // Keep track of spaces. Append \t when we reach tab_size
11240 spaces_count += 1;
11241 changed = true;
11242 if spaces_count == tab_size {
11243 reindented_line.push('\t');
11244 spaces_count = 0;
11245 }
11246 }
11247 '\t' => {
11248 reindented_line.push('\t');
11249 spaces_count = 0;
11250 }
11251 _ => {
11252 // Dont append it yet, we might have remaining spaces
11253 first_non_indent_char = Some(ch);
11254 break;
11255 }
11256 }
11257 }
11258
11259 if !changed {
11260 reindented_line.clear();
11261 continue;
11262 }
11263 // Remaining spaces that didn't make a full tab stop
11264 if spaces_count > 0 {
11265 reindented_line.extend(&space_cache[spaces_count - 1]);
11266 }
11267 // If we consume an extra character that was not indentation, add it back
11268 if let Some(extra_char) = first_non_indent_char {
11269 reindented_line.push(extra_char);
11270 }
11271 // Append the rest of the line and replace old reference with new one
11272 reindented_line.extend(chars);
11273 *line = Cow::Owned(reindented_line.clone());
11274 reindented_line.clear();
11275 }
11276 });
11277 }
11278
11279 pub fn convert_to_upper_case(
11280 &mut self,
11281 _: &ConvertToUpperCase,
11282 window: &mut Window,
11283 cx: &mut Context<Self>,
11284 ) {
11285 self.manipulate_text(window, cx, |text| text.to_uppercase())
11286 }
11287
11288 pub fn convert_to_lower_case(
11289 &mut self,
11290 _: &ConvertToLowerCase,
11291 window: &mut Window,
11292 cx: &mut Context<Self>,
11293 ) {
11294 self.manipulate_text(window, cx, |text| text.to_lowercase())
11295 }
11296
11297 pub fn convert_to_title_case(
11298 &mut self,
11299 _: &ConvertToTitleCase,
11300 window: &mut Window,
11301 cx: &mut Context<Self>,
11302 ) {
11303 self.manipulate_text(window, cx, |text| {
11304 text.split('\n')
11305 .map(|line| line.to_case(Case::Title))
11306 .join("\n")
11307 })
11308 }
11309
11310 pub fn convert_to_snake_case(
11311 &mut self,
11312 _: &ConvertToSnakeCase,
11313 window: &mut Window,
11314 cx: &mut Context<Self>,
11315 ) {
11316 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11317 }
11318
11319 pub fn convert_to_kebab_case(
11320 &mut self,
11321 _: &ConvertToKebabCase,
11322 window: &mut Window,
11323 cx: &mut Context<Self>,
11324 ) {
11325 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11326 }
11327
11328 pub fn convert_to_upper_camel_case(
11329 &mut self,
11330 _: &ConvertToUpperCamelCase,
11331 window: &mut Window,
11332 cx: &mut Context<Self>,
11333 ) {
11334 self.manipulate_text(window, cx, |text| {
11335 text.split('\n')
11336 .map(|line| line.to_case(Case::UpperCamel))
11337 .join("\n")
11338 })
11339 }
11340
11341 pub fn convert_to_lower_camel_case(
11342 &mut self,
11343 _: &ConvertToLowerCamelCase,
11344 window: &mut Window,
11345 cx: &mut Context<Self>,
11346 ) {
11347 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11348 }
11349
11350 pub fn convert_to_opposite_case(
11351 &mut self,
11352 _: &ConvertToOppositeCase,
11353 window: &mut Window,
11354 cx: &mut Context<Self>,
11355 ) {
11356 self.manipulate_text(window, cx, |text| {
11357 text.chars()
11358 .fold(String::with_capacity(text.len()), |mut t, c| {
11359 if c.is_uppercase() {
11360 t.extend(c.to_lowercase());
11361 } else {
11362 t.extend(c.to_uppercase());
11363 }
11364 t
11365 })
11366 })
11367 }
11368
11369 pub fn convert_to_sentence_case(
11370 &mut self,
11371 _: &ConvertToSentenceCase,
11372 window: &mut Window,
11373 cx: &mut Context<Self>,
11374 ) {
11375 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11376 }
11377
11378 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11379 self.manipulate_text(window, cx, |text| {
11380 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11381 if has_upper_case_characters {
11382 text.to_lowercase()
11383 } else {
11384 text.to_uppercase()
11385 }
11386 })
11387 }
11388
11389 pub fn convert_to_rot13(
11390 &mut self,
11391 _: &ConvertToRot13,
11392 window: &mut Window,
11393 cx: &mut Context<Self>,
11394 ) {
11395 self.manipulate_text(window, cx, |text| {
11396 text.chars()
11397 .map(|c| match c {
11398 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11399 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11400 _ => c,
11401 })
11402 .collect()
11403 })
11404 }
11405
11406 pub fn convert_to_rot47(
11407 &mut self,
11408 _: &ConvertToRot47,
11409 window: &mut Window,
11410 cx: &mut Context<Self>,
11411 ) {
11412 self.manipulate_text(window, cx, |text| {
11413 text.chars()
11414 .map(|c| {
11415 let code_point = c as u32;
11416 if code_point >= 33 && code_point <= 126 {
11417 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11418 }
11419 c
11420 })
11421 .collect()
11422 })
11423 }
11424
11425 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11426 where
11427 Fn: FnMut(&str) -> String,
11428 {
11429 let buffer = self.buffer.read(cx).snapshot(cx);
11430
11431 let mut new_selections = Vec::new();
11432 let mut edits = Vec::new();
11433 let mut selection_adjustment = 0i32;
11434
11435 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11436 let selection_is_empty = selection.is_empty();
11437
11438 let (start, end) = if selection_is_empty {
11439 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11440 (word_range.start, word_range.end)
11441 } else {
11442 (
11443 buffer.point_to_offset(selection.start),
11444 buffer.point_to_offset(selection.end),
11445 )
11446 };
11447
11448 let text = buffer.text_for_range(start..end).collect::<String>();
11449 let old_length = text.len() as i32;
11450 let text = callback(&text);
11451
11452 new_selections.push(Selection {
11453 start: (start as i32 - selection_adjustment) as usize,
11454 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11455 goal: SelectionGoal::None,
11456 id: selection.id,
11457 reversed: selection.reversed,
11458 });
11459
11460 selection_adjustment += old_length - text.len() as i32;
11461
11462 edits.push((start..end, text));
11463 }
11464
11465 self.transact(window, cx, |this, window, cx| {
11466 this.buffer.update(cx, |buffer, cx| {
11467 buffer.edit(edits, None, cx);
11468 });
11469
11470 this.change_selections(Default::default(), window, cx, |s| {
11471 s.select(new_selections);
11472 });
11473
11474 this.request_autoscroll(Autoscroll::fit(), cx);
11475 });
11476 }
11477
11478 pub fn move_selection_on_drop(
11479 &mut self,
11480 selection: &Selection<Anchor>,
11481 target: DisplayPoint,
11482 is_cut: bool,
11483 window: &mut Window,
11484 cx: &mut Context<Self>,
11485 ) {
11486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11487 let buffer = display_map.buffer_snapshot();
11488 let mut edits = Vec::new();
11489 let insert_point = display_map
11490 .clip_point(target, Bias::Left)
11491 .to_point(&display_map);
11492 let text = buffer
11493 .text_for_range(selection.start..selection.end)
11494 .collect::<String>();
11495 if is_cut {
11496 edits.push(((selection.start..selection.end), String::new()));
11497 }
11498 let insert_anchor = buffer.anchor_before(insert_point);
11499 edits.push(((insert_anchor..insert_anchor), text));
11500 let last_edit_start = insert_anchor.bias_left(buffer);
11501 let last_edit_end = insert_anchor.bias_right(buffer);
11502 self.transact(window, cx, |this, window, cx| {
11503 this.buffer.update(cx, |buffer, cx| {
11504 buffer.edit(edits, None, cx);
11505 });
11506 this.change_selections(Default::default(), window, cx, |s| {
11507 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11508 });
11509 });
11510 }
11511
11512 pub fn clear_selection_drag_state(&mut self) {
11513 self.selection_drag_state = SelectionDragState::None;
11514 }
11515
11516 pub fn duplicate(
11517 &mut self,
11518 upwards: bool,
11519 whole_lines: bool,
11520 window: &mut Window,
11521 cx: &mut Context<Self>,
11522 ) {
11523 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11524
11525 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11526 let buffer = display_map.buffer_snapshot();
11527 let selections = self.selections.all::<Point>(&display_map);
11528
11529 let mut edits = Vec::new();
11530 let mut selections_iter = selections.iter().peekable();
11531 while let Some(selection) = selections_iter.next() {
11532 let mut rows = selection.spanned_rows(false, &display_map);
11533 // duplicate line-wise
11534 if whole_lines || selection.start == selection.end {
11535 // Avoid duplicating the same lines twice.
11536 while let Some(next_selection) = selections_iter.peek() {
11537 let next_rows = next_selection.spanned_rows(false, &display_map);
11538 if next_rows.start < rows.end {
11539 rows.end = next_rows.end;
11540 selections_iter.next().unwrap();
11541 } else {
11542 break;
11543 }
11544 }
11545
11546 // Copy the text from the selected row region and splice it either at the start
11547 // or end of the region.
11548 let start = Point::new(rows.start.0, 0);
11549 let end = Point::new(
11550 rows.end.previous_row().0,
11551 buffer.line_len(rows.end.previous_row()),
11552 );
11553
11554 let mut text = buffer.text_for_range(start..end).collect::<String>();
11555
11556 let insert_location = if upwards {
11557 // When duplicating upward, we need to insert before the current line.
11558 // If we're on the last line and it doesn't end with a newline,
11559 // we need to add a newline before the duplicated content.
11560 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11561 && buffer.max_point().column > 0
11562 && !text.ends_with('\n');
11563
11564 if needs_leading_newline {
11565 text.insert(0, '\n');
11566 end
11567 } else {
11568 text.push('\n');
11569 Point::new(rows.start.0, 0)
11570 }
11571 } else {
11572 text.push('\n');
11573 start
11574 };
11575 edits.push((insert_location..insert_location, text));
11576 } else {
11577 // duplicate character-wise
11578 let start = selection.start;
11579 let end = selection.end;
11580 let text = buffer.text_for_range(start..end).collect::<String>();
11581 edits.push((selection.end..selection.end, text));
11582 }
11583 }
11584
11585 self.transact(window, cx, |this, window, cx| {
11586 this.buffer.update(cx, |buffer, cx| {
11587 buffer.edit(edits, None, cx);
11588 });
11589
11590 // When duplicating upward with whole lines, move the cursor to the duplicated line
11591 if upwards && whole_lines {
11592 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11593
11594 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11595 let mut new_ranges = Vec::new();
11596 let selections = s.all::<Point>(&display_map);
11597 let mut selections_iter = selections.iter().peekable();
11598
11599 while let Some(first_selection) = selections_iter.next() {
11600 // Group contiguous selections together to find the total row span
11601 let mut group_selections = vec![first_selection];
11602 let mut rows = first_selection.spanned_rows(false, &display_map);
11603
11604 while let Some(next_selection) = selections_iter.peek() {
11605 let next_rows = next_selection.spanned_rows(false, &display_map);
11606 if next_rows.start < rows.end {
11607 rows.end = next_rows.end;
11608 group_selections.push(selections_iter.next().unwrap());
11609 } else {
11610 break;
11611 }
11612 }
11613
11614 let row_count = rows.end.0 - rows.start.0;
11615
11616 // Move all selections in this group up by the total number of duplicated rows
11617 for selection in group_selections {
11618 let new_start = Point::new(
11619 selection.start.row.saturating_sub(row_count),
11620 selection.start.column,
11621 );
11622
11623 let new_end = Point::new(
11624 selection.end.row.saturating_sub(row_count),
11625 selection.end.column,
11626 );
11627
11628 new_ranges.push(new_start..new_end);
11629 }
11630 }
11631
11632 s.select_ranges(new_ranges);
11633 });
11634 }
11635
11636 this.request_autoscroll(Autoscroll::fit(), cx);
11637 });
11638 }
11639
11640 pub fn duplicate_line_up(
11641 &mut self,
11642 _: &DuplicateLineUp,
11643 window: &mut Window,
11644 cx: &mut Context<Self>,
11645 ) {
11646 self.duplicate(true, true, window, cx);
11647 }
11648
11649 pub fn duplicate_line_down(
11650 &mut self,
11651 _: &DuplicateLineDown,
11652 window: &mut Window,
11653 cx: &mut Context<Self>,
11654 ) {
11655 self.duplicate(false, true, window, cx);
11656 }
11657
11658 pub fn duplicate_selection(
11659 &mut self,
11660 _: &DuplicateSelection,
11661 window: &mut Window,
11662 cx: &mut Context<Self>,
11663 ) {
11664 self.duplicate(false, false, window, cx);
11665 }
11666
11667 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11669 if self.mode.is_single_line() {
11670 cx.propagate();
11671 return;
11672 }
11673
11674 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11675 let buffer = self.buffer.read(cx).snapshot(cx);
11676
11677 let mut edits = Vec::new();
11678 let mut unfold_ranges = Vec::new();
11679 let mut refold_creases = Vec::new();
11680
11681 let selections = self.selections.all::<Point>(&display_map);
11682 let mut selections = selections.iter().peekable();
11683 let mut contiguous_row_selections = Vec::new();
11684 let mut new_selections = Vec::new();
11685
11686 while let Some(selection) = selections.next() {
11687 // Find all the selections that span a contiguous row range
11688 let (start_row, end_row) = consume_contiguous_rows(
11689 &mut contiguous_row_selections,
11690 selection,
11691 &display_map,
11692 &mut selections,
11693 );
11694
11695 // Move the text spanned by the row range to be before the line preceding the row range
11696 if start_row.0 > 0 {
11697 let range_to_move = Point::new(
11698 start_row.previous_row().0,
11699 buffer.line_len(start_row.previous_row()),
11700 )
11701 ..Point::new(
11702 end_row.previous_row().0,
11703 buffer.line_len(end_row.previous_row()),
11704 );
11705 let insertion_point = display_map
11706 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11707 .0;
11708
11709 // Don't move lines across excerpts
11710 if buffer
11711 .excerpt_containing(insertion_point..range_to_move.end)
11712 .is_some()
11713 {
11714 let text = buffer
11715 .text_for_range(range_to_move.clone())
11716 .flat_map(|s| s.chars())
11717 .skip(1)
11718 .chain(['\n'])
11719 .collect::<String>();
11720
11721 edits.push((
11722 buffer.anchor_after(range_to_move.start)
11723 ..buffer.anchor_before(range_to_move.end),
11724 String::new(),
11725 ));
11726 let insertion_anchor = buffer.anchor_after(insertion_point);
11727 edits.push((insertion_anchor..insertion_anchor, text));
11728
11729 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11730
11731 // Move selections up
11732 new_selections.extend(contiguous_row_selections.drain(..).map(
11733 |mut selection| {
11734 selection.start.row -= row_delta;
11735 selection.end.row -= row_delta;
11736 selection
11737 },
11738 ));
11739
11740 // Move folds up
11741 unfold_ranges.push(range_to_move.clone());
11742 for fold in display_map.folds_in_range(
11743 buffer.anchor_before(range_to_move.start)
11744 ..buffer.anchor_after(range_to_move.end),
11745 ) {
11746 let mut start = fold.range.start.to_point(&buffer);
11747 let mut end = fold.range.end.to_point(&buffer);
11748 start.row -= row_delta;
11749 end.row -= row_delta;
11750 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11751 }
11752 }
11753 }
11754
11755 // If we didn't move line(s), preserve the existing selections
11756 new_selections.append(&mut contiguous_row_selections);
11757 }
11758
11759 self.transact(window, cx, |this, window, cx| {
11760 this.unfold_ranges(&unfold_ranges, true, true, cx);
11761 this.buffer.update(cx, |buffer, cx| {
11762 for (range, text) in edits {
11763 buffer.edit([(range, text)], None, cx);
11764 }
11765 });
11766 this.fold_creases(refold_creases, true, window, cx);
11767 this.change_selections(Default::default(), window, cx, |s| {
11768 s.select(new_selections);
11769 })
11770 });
11771 }
11772
11773 pub fn move_line_down(
11774 &mut self,
11775 _: &MoveLineDown,
11776 window: &mut Window,
11777 cx: &mut Context<Self>,
11778 ) {
11779 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11780 if self.mode.is_single_line() {
11781 cx.propagate();
11782 return;
11783 }
11784
11785 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11786 let buffer = self.buffer.read(cx).snapshot(cx);
11787
11788 let mut edits = Vec::new();
11789 let mut unfold_ranges = Vec::new();
11790 let mut refold_creases = Vec::new();
11791
11792 let selections = self.selections.all::<Point>(&display_map);
11793 let mut selections = selections.iter().peekable();
11794 let mut contiguous_row_selections = Vec::new();
11795 let mut new_selections = Vec::new();
11796
11797 while let Some(selection) = selections.next() {
11798 // Find all the selections that span a contiguous row range
11799 let (start_row, end_row) = consume_contiguous_rows(
11800 &mut contiguous_row_selections,
11801 selection,
11802 &display_map,
11803 &mut selections,
11804 );
11805
11806 // Move the text spanned by the row range to be after the last line of the row range
11807 if end_row.0 <= buffer.max_point().row {
11808 let range_to_move =
11809 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11810 let insertion_point = display_map
11811 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11812 .0;
11813
11814 // Don't move lines across excerpt boundaries
11815 if buffer
11816 .excerpt_containing(range_to_move.start..insertion_point)
11817 .is_some()
11818 {
11819 let mut text = String::from("\n");
11820 text.extend(buffer.text_for_range(range_to_move.clone()));
11821 text.pop(); // Drop trailing newline
11822 edits.push((
11823 buffer.anchor_after(range_to_move.start)
11824 ..buffer.anchor_before(range_to_move.end),
11825 String::new(),
11826 ));
11827 let insertion_anchor = buffer.anchor_after(insertion_point);
11828 edits.push((insertion_anchor..insertion_anchor, text));
11829
11830 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11831
11832 // Move selections down
11833 new_selections.extend(contiguous_row_selections.drain(..).map(
11834 |mut selection| {
11835 selection.start.row += row_delta;
11836 selection.end.row += row_delta;
11837 selection
11838 },
11839 ));
11840
11841 // Move folds down
11842 unfold_ranges.push(range_to_move.clone());
11843 for fold in display_map.folds_in_range(
11844 buffer.anchor_before(range_to_move.start)
11845 ..buffer.anchor_after(range_to_move.end),
11846 ) {
11847 let mut start = fold.range.start.to_point(&buffer);
11848 let mut end = fold.range.end.to_point(&buffer);
11849 start.row += row_delta;
11850 end.row += row_delta;
11851 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11852 }
11853 }
11854 }
11855
11856 // If we didn't move line(s), preserve the existing selections
11857 new_selections.append(&mut contiguous_row_selections);
11858 }
11859
11860 self.transact(window, cx, |this, window, cx| {
11861 this.unfold_ranges(&unfold_ranges, true, true, cx);
11862 this.buffer.update(cx, |buffer, cx| {
11863 for (range, text) in edits {
11864 buffer.edit([(range, text)], None, cx);
11865 }
11866 });
11867 this.fold_creases(refold_creases, true, window, cx);
11868 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11869 });
11870 }
11871
11872 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11874 let text_layout_details = &self.text_layout_details(window);
11875 self.transact(window, cx, |this, window, cx| {
11876 let edits = this.change_selections(Default::default(), window, cx, |s| {
11877 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11878 s.move_with(|display_map, selection| {
11879 if !selection.is_empty() {
11880 return;
11881 }
11882
11883 let mut head = selection.head();
11884 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11885 if head.column() == display_map.line_len(head.row()) {
11886 transpose_offset = display_map
11887 .buffer_snapshot()
11888 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11889 }
11890
11891 if transpose_offset == 0 {
11892 return;
11893 }
11894
11895 *head.column_mut() += 1;
11896 head = display_map.clip_point(head, Bias::Right);
11897 let goal = SelectionGoal::HorizontalPosition(
11898 display_map
11899 .x_for_display_point(head, text_layout_details)
11900 .into(),
11901 );
11902 selection.collapse_to(head, goal);
11903
11904 let transpose_start = display_map
11905 .buffer_snapshot()
11906 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11907 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11908 let transpose_end = display_map
11909 .buffer_snapshot()
11910 .clip_offset(transpose_offset + 1, Bias::Right);
11911 if let Some(ch) = display_map
11912 .buffer_snapshot()
11913 .chars_at(transpose_start)
11914 .next()
11915 {
11916 edits.push((transpose_start..transpose_offset, String::new()));
11917 edits.push((transpose_end..transpose_end, ch.to_string()));
11918 }
11919 }
11920 });
11921 edits
11922 });
11923 this.buffer
11924 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11925 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11926 this.change_selections(Default::default(), window, cx, |s| {
11927 s.select(selections);
11928 });
11929 });
11930 }
11931
11932 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11934 if self.mode.is_single_line() {
11935 cx.propagate();
11936 return;
11937 }
11938
11939 self.rewrap_impl(RewrapOptions::default(), cx)
11940 }
11941
11942 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11943 let buffer = self.buffer.read(cx).snapshot(cx);
11944 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11945
11946 #[derive(Clone, Debug, PartialEq)]
11947 enum CommentFormat {
11948 /// single line comment, with prefix for line
11949 Line(String),
11950 /// single line within a block comment, with prefix for line
11951 BlockLine(String),
11952 /// a single line of a block comment that includes the initial delimiter
11953 BlockCommentWithStart(BlockCommentConfig),
11954 /// a single line of a block comment that includes the ending delimiter
11955 BlockCommentWithEnd(BlockCommentConfig),
11956 }
11957
11958 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11959 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11960 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11961 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11962 .peekable();
11963
11964 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11965 row
11966 } else {
11967 return Vec::new();
11968 };
11969
11970 let language_settings = buffer.language_settings_at(selection.head(), cx);
11971 let language_scope = buffer.language_scope_at(selection.head());
11972
11973 let indent_and_prefix_for_row =
11974 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11975 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11976 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11977 &language_scope
11978 {
11979 let indent_end = Point::new(row, indent.len);
11980 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11981 let line_text_after_indent = buffer
11982 .text_for_range(indent_end..line_end)
11983 .collect::<String>();
11984
11985 let is_within_comment_override = buffer
11986 .language_scope_at(indent_end)
11987 .is_some_and(|scope| scope.override_name() == Some("comment"));
11988 let comment_delimiters = if is_within_comment_override {
11989 // we are within a comment syntax node, but we don't
11990 // yet know what kind of comment: block, doc or line
11991 match (
11992 language_scope.documentation_comment(),
11993 language_scope.block_comment(),
11994 ) {
11995 (Some(config), _) | (_, Some(config))
11996 if buffer.contains_str_at(indent_end, &config.start) =>
11997 {
11998 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11999 }
12000 (Some(config), _) | (_, Some(config))
12001 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12002 {
12003 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12004 }
12005 (Some(config), _) | (_, Some(config))
12006 if buffer.contains_str_at(indent_end, &config.prefix) =>
12007 {
12008 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12009 }
12010 (_, _) => language_scope
12011 .line_comment_prefixes()
12012 .iter()
12013 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12014 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12015 }
12016 } else {
12017 // we not in an overridden comment node, but we may
12018 // be within a non-overridden line comment node
12019 language_scope
12020 .line_comment_prefixes()
12021 .iter()
12022 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12023 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12024 };
12025
12026 let rewrap_prefix = language_scope
12027 .rewrap_prefixes()
12028 .iter()
12029 .find_map(|prefix_regex| {
12030 prefix_regex.find(&line_text_after_indent).map(|mat| {
12031 if mat.start() == 0 {
12032 Some(mat.as_str().to_string())
12033 } else {
12034 None
12035 }
12036 })
12037 })
12038 .flatten();
12039 (comment_delimiters, rewrap_prefix)
12040 } else {
12041 (None, None)
12042 };
12043 (indent, comment_prefix, rewrap_prefix)
12044 };
12045
12046 let mut ranges = Vec::new();
12047 let from_empty_selection = selection.is_empty();
12048
12049 let mut current_range_start = first_row;
12050 let mut prev_row = first_row;
12051 let (
12052 mut current_range_indent,
12053 mut current_range_comment_delimiters,
12054 mut current_range_rewrap_prefix,
12055 ) = indent_and_prefix_for_row(first_row);
12056
12057 for row in non_blank_rows_iter.skip(1) {
12058 let has_paragraph_break = row > prev_row + 1;
12059
12060 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12061 indent_and_prefix_for_row(row);
12062
12063 let has_indent_change = row_indent != current_range_indent;
12064 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12065
12066 let has_boundary_change = has_comment_change
12067 || row_rewrap_prefix.is_some()
12068 || (has_indent_change && current_range_comment_delimiters.is_some());
12069
12070 if has_paragraph_break || has_boundary_change {
12071 ranges.push((
12072 language_settings.clone(),
12073 Point::new(current_range_start, 0)
12074 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12075 current_range_indent,
12076 current_range_comment_delimiters.clone(),
12077 current_range_rewrap_prefix.clone(),
12078 from_empty_selection,
12079 ));
12080 current_range_start = row;
12081 current_range_indent = row_indent;
12082 current_range_comment_delimiters = row_comment_delimiters;
12083 current_range_rewrap_prefix = row_rewrap_prefix;
12084 }
12085 prev_row = row;
12086 }
12087
12088 ranges.push((
12089 language_settings.clone(),
12090 Point::new(current_range_start, 0)
12091 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12092 current_range_indent,
12093 current_range_comment_delimiters,
12094 current_range_rewrap_prefix,
12095 from_empty_selection,
12096 ));
12097
12098 ranges
12099 });
12100
12101 let mut edits = Vec::new();
12102 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12103
12104 for (
12105 language_settings,
12106 wrap_range,
12107 mut indent_size,
12108 comment_prefix,
12109 rewrap_prefix,
12110 from_empty_selection,
12111 ) in wrap_ranges
12112 {
12113 let mut start_row = wrap_range.start.row;
12114 let mut end_row = wrap_range.end.row;
12115
12116 // Skip selections that overlap with a range that has already been rewrapped.
12117 let selection_range = start_row..end_row;
12118 if rewrapped_row_ranges
12119 .iter()
12120 .any(|range| range.overlaps(&selection_range))
12121 {
12122 continue;
12123 }
12124
12125 let tab_size = language_settings.tab_size;
12126
12127 let (line_prefix, inside_comment) = match &comment_prefix {
12128 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12129 (Some(prefix.as_str()), true)
12130 }
12131 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12132 (Some(prefix.as_ref()), true)
12133 }
12134 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12135 start: _,
12136 end: _,
12137 prefix,
12138 tab_size,
12139 })) => {
12140 indent_size.len += tab_size;
12141 (Some(prefix.as_ref()), true)
12142 }
12143 None => (None, false),
12144 };
12145 let indent_prefix = indent_size.chars().collect::<String>();
12146 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12147
12148 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12149 RewrapBehavior::InComments => inside_comment,
12150 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12151 RewrapBehavior::Anywhere => true,
12152 };
12153
12154 let should_rewrap = options.override_language_settings
12155 || allow_rewrap_based_on_language
12156 || self.hard_wrap.is_some();
12157 if !should_rewrap {
12158 continue;
12159 }
12160
12161 if from_empty_selection {
12162 'expand_upwards: while start_row > 0 {
12163 let prev_row = start_row - 1;
12164 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12165 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12166 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12167 {
12168 start_row = prev_row;
12169 } else {
12170 break 'expand_upwards;
12171 }
12172 }
12173
12174 'expand_downwards: while end_row < buffer.max_point().row {
12175 let next_row = end_row + 1;
12176 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12177 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12178 && !buffer.is_line_blank(MultiBufferRow(next_row))
12179 {
12180 end_row = next_row;
12181 } else {
12182 break 'expand_downwards;
12183 }
12184 }
12185 }
12186
12187 let start = Point::new(start_row, 0);
12188 let start_offset = ToOffset::to_offset(&start, &buffer);
12189 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12190 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12191 let mut first_line_delimiter = None;
12192 let mut last_line_delimiter = None;
12193 let Some(lines_without_prefixes) = selection_text
12194 .lines()
12195 .enumerate()
12196 .map(|(ix, line)| {
12197 let line_trimmed = line.trim_start();
12198 if rewrap_prefix.is_some() && ix > 0 {
12199 Ok(line_trimmed)
12200 } else if let Some(
12201 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12202 start,
12203 prefix,
12204 end,
12205 tab_size,
12206 })
12207 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12208 start,
12209 prefix,
12210 end,
12211 tab_size,
12212 }),
12213 ) = &comment_prefix
12214 {
12215 let line_trimmed = line_trimmed
12216 .strip_prefix(start.as_ref())
12217 .map(|s| {
12218 let mut indent_size = indent_size;
12219 indent_size.len -= tab_size;
12220 let indent_prefix: String = indent_size.chars().collect();
12221 first_line_delimiter = Some((indent_prefix, start));
12222 s.trim_start()
12223 })
12224 .unwrap_or(line_trimmed);
12225 let line_trimmed = line_trimmed
12226 .strip_suffix(end.as_ref())
12227 .map(|s| {
12228 last_line_delimiter = Some(end);
12229 s.trim_end()
12230 })
12231 .unwrap_or(line_trimmed);
12232 let line_trimmed = line_trimmed
12233 .strip_prefix(prefix.as_ref())
12234 .unwrap_or(line_trimmed);
12235 Ok(line_trimmed)
12236 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12237 line_trimmed.strip_prefix(prefix).with_context(|| {
12238 format!("line did not start with prefix {prefix:?}: {line:?}")
12239 })
12240 } else {
12241 line_trimmed
12242 .strip_prefix(&line_prefix.trim_start())
12243 .with_context(|| {
12244 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12245 })
12246 }
12247 })
12248 .collect::<Result<Vec<_>, _>>()
12249 .log_err()
12250 else {
12251 continue;
12252 };
12253
12254 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12255 buffer
12256 .language_settings_at(Point::new(start_row, 0), cx)
12257 .preferred_line_length as usize
12258 });
12259
12260 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12261 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12262 } else {
12263 line_prefix.clone()
12264 };
12265
12266 let wrapped_text = {
12267 let mut wrapped_text = wrap_with_prefix(
12268 line_prefix,
12269 subsequent_lines_prefix,
12270 lines_without_prefixes.join("\n"),
12271 wrap_column,
12272 tab_size,
12273 options.preserve_existing_whitespace,
12274 );
12275
12276 if let Some((indent, delimiter)) = first_line_delimiter {
12277 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12278 }
12279 if let Some(last_line) = last_line_delimiter {
12280 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12281 }
12282
12283 wrapped_text
12284 };
12285
12286 // TODO: should always use char-based diff while still supporting cursor behavior that
12287 // matches vim.
12288 let mut diff_options = DiffOptions::default();
12289 if options.override_language_settings {
12290 diff_options.max_word_diff_len = 0;
12291 diff_options.max_word_diff_line_count = 0;
12292 } else {
12293 diff_options.max_word_diff_len = usize::MAX;
12294 diff_options.max_word_diff_line_count = usize::MAX;
12295 }
12296
12297 for (old_range, new_text) in
12298 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12299 {
12300 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12301 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12302 edits.push((edit_start..edit_end, new_text));
12303 }
12304
12305 rewrapped_row_ranges.push(start_row..=end_row);
12306 }
12307
12308 self.buffer
12309 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12310 }
12311
12312 pub fn cut_common(
12313 &mut self,
12314 cut_no_selection_line: bool,
12315 window: &mut Window,
12316 cx: &mut Context<Self>,
12317 ) -> ClipboardItem {
12318 let mut text = String::new();
12319 let buffer = self.buffer.read(cx).snapshot(cx);
12320 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12321 let mut clipboard_selections = Vec::with_capacity(selections.len());
12322 {
12323 let max_point = buffer.max_point();
12324 let mut is_first = true;
12325 for selection in &mut selections {
12326 let is_entire_line =
12327 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12328 if is_entire_line {
12329 selection.start = Point::new(selection.start.row, 0);
12330 if !selection.is_empty() && selection.end.column == 0 {
12331 selection.end = cmp::min(max_point, selection.end);
12332 } else {
12333 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12334 }
12335 selection.goal = SelectionGoal::None;
12336 }
12337 if is_first {
12338 is_first = false;
12339 } else {
12340 text += "\n";
12341 }
12342 let mut len = 0;
12343 for chunk in buffer.text_for_range(selection.start..selection.end) {
12344 text.push_str(chunk);
12345 len += chunk.len();
12346 }
12347 clipboard_selections.push(ClipboardSelection {
12348 len,
12349 is_entire_line,
12350 first_line_indent: buffer
12351 .indent_size_for_line(MultiBufferRow(selection.start.row))
12352 .len,
12353 });
12354 }
12355 }
12356
12357 self.transact(window, cx, |this, window, cx| {
12358 this.change_selections(Default::default(), window, cx, |s| {
12359 s.select(selections);
12360 });
12361 this.insert("", window, cx);
12362 });
12363 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12364 }
12365
12366 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12367 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12368 let item = self.cut_common(true, window, cx);
12369 cx.write_to_clipboard(item);
12370 }
12371
12372 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12374 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12375 s.move_with(|snapshot, sel| {
12376 if sel.is_empty() {
12377 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12378 }
12379 if sel.is_empty() {
12380 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12381 }
12382 });
12383 });
12384 let item = self.cut_common(false, window, cx);
12385 cx.set_global(KillRing(item))
12386 }
12387
12388 pub fn kill_ring_yank(
12389 &mut self,
12390 _: &KillRingYank,
12391 window: &mut Window,
12392 cx: &mut Context<Self>,
12393 ) {
12394 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12395 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12396 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12397 (kill_ring.text().to_string(), kill_ring.metadata_json())
12398 } else {
12399 return;
12400 }
12401 } else {
12402 return;
12403 };
12404 self.do_paste(&text, metadata, false, window, cx);
12405 }
12406
12407 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12408 self.do_copy(true, cx);
12409 }
12410
12411 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12412 self.do_copy(false, cx);
12413 }
12414
12415 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12416 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12417 let buffer = self.buffer.read(cx).read(cx);
12418 let mut text = String::new();
12419
12420 let mut clipboard_selections = Vec::with_capacity(selections.len());
12421 {
12422 let max_point = buffer.max_point();
12423 let mut is_first = true;
12424 for selection in &selections {
12425 let mut start = selection.start;
12426 let mut end = selection.end;
12427 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12428 let mut add_trailing_newline = false;
12429 if is_entire_line {
12430 start = Point::new(start.row, 0);
12431 let next_line_start = Point::new(end.row + 1, 0);
12432 if next_line_start <= max_point {
12433 end = next_line_start;
12434 } else {
12435 // We're on the last line without a trailing newline.
12436 // Copy to the end of the line and add a newline afterwards.
12437 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12438 add_trailing_newline = true;
12439 }
12440 }
12441
12442 let mut trimmed_selections = Vec::new();
12443 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12444 let row = MultiBufferRow(start.row);
12445 let first_indent = buffer.indent_size_for_line(row);
12446 if first_indent.len == 0 || start.column > first_indent.len {
12447 trimmed_selections.push(start..end);
12448 } else {
12449 trimmed_selections.push(
12450 Point::new(row.0, first_indent.len)
12451 ..Point::new(row.0, buffer.line_len(row)),
12452 );
12453 for row in start.row + 1..=end.row {
12454 let mut line_len = buffer.line_len(MultiBufferRow(row));
12455 if row == end.row {
12456 line_len = end.column;
12457 }
12458 if line_len == 0 {
12459 trimmed_selections
12460 .push(Point::new(row, 0)..Point::new(row, line_len));
12461 continue;
12462 }
12463 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12464 if row_indent_size.len >= first_indent.len {
12465 trimmed_selections.push(
12466 Point::new(row, first_indent.len)..Point::new(row, line_len),
12467 );
12468 } else {
12469 trimmed_selections.clear();
12470 trimmed_selections.push(start..end);
12471 break;
12472 }
12473 }
12474 }
12475 } else {
12476 trimmed_selections.push(start..end);
12477 }
12478
12479 for trimmed_range in trimmed_selections {
12480 if is_first {
12481 is_first = false;
12482 } else {
12483 text += "\n";
12484 }
12485 let mut len = 0;
12486 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12487 text.push_str(chunk);
12488 len += chunk.len();
12489 }
12490 if add_trailing_newline {
12491 text.push('\n');
12492 len += 1;
12493 }
12494 clipboard_selections.push(ClipboardSelection {
12495 len,
12496 is_entire_line,
12497 first_line_indent: buffer
12498 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12499 .len,
12500 });
12501 }
12502 }
12503 }
12504
12505 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12506 text,
12507 clipboard_selections,
12508 ));
12509 }
12510
12511 pub fn do_paste(
12512 &mut self,
12513 text: &String,
12514 clipboard_selections: Option<Vec<ClipboardSelection>>,
12515 handle_entire_lines: bool,
12516 window: &mut Window,
12517 cx: &mut Context<Self>,
12518 ) {
12519 if self.read_only(cx) {
12520 return;
12521 }
12522
12523 let clipboard_text = Cow::Borrowed(text.as_str());
12524
12525 self.transact(window, cx, |this, window, cx| {
12526 let had_active_edit_prediction = this.has_active_edit_prediction();
12527 let display_map = this.display_snapshot(cx);
12528 let old_selections = this.selections.all::<usize>(&display_map);
12529 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12530
12531 if let Some(mut clipboard_selections) = clipboard_selections {
12532 let all_selections_were_entire_line =
12533 clipboard_selections.iter().all(|s| s.is_entire_line);
12534 let first_selection_indent_column =
12535 clipboard_selections.first().map(|s| s.first_line_indent);
12536 if clipboard_selections.len() != old_selections.len() {
12537 clipboard_selections.drain(..);
12538 }
12539 let mut auto_indent_on_paste = true;
12540
12541 this.buffer.update(cx, |buffer, cx| {
12542 let snapshot = buffer.read(cx);
12543 auto_indent_on_paste = snapshot
12544 .language_settings_at(cursor_offset, cx)
12545 .auto_indent_on_paste;
12546
12547 let mut start_offset = 0;
12548 let mut edits = Vec::new();
12549 let mut original_indent_columns = Vec::new();
12550 for (ix, selection) in old_selections.iter().enumerate() {
12551 let to_insert;
12552 let entire_line;
12553 let original_indent_column;
12554 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12555 let end_offset = start_offset + clipboard_selection.len;
12556 to_insert = &clipboard_text[start_offset..end_offset];
12557 entire_line = clipboard_selection.is_entire_line;
12558 start_offset = end_offset + 1;
12559 original_indent_column = Some(clipboard_selection.first_line_indent);
12560 } else {
12561 to_insert = &*clipboard_text;
12562 entire_line = all_selections_were_entire_line;
12563 original_indent_column = first_selection_indent_column
12564 }
12565
12566 let (range, to_insert) =
12567 if selection.is_empty() && handle_entire_lines && entire_line {
12568 // If the corresponding selection was empty when this slice of the
12569 // clipboard text was written, then the entire line containing the
12570 // selection was copied. If this selection is also currently empty,
12571 // then paste the line before the current line of the buffer.
12572 let column = selection.start.to_point(&snapshot).column as usize;
12573 let line_start = selection.start - column;
12574 (line_start..line_start, Cow::Borrowed(to_insert))
12575 } else {
12576 let language = snapshot.language_at(selection.head());
12577 let range = selection.range();
12578 if let Some(language) = language
12579 && language.name() == "Markdown".into()
12580 {
12581 edit_for_markdown_paste(
12582 &snapshot,
12583 range,
12584 to_insert,
12585 url::Url::parse(to_insert).ok(),
12586 )
12587 } else {
12588 (range, Cow::Borrowed(to_insert))
12589 }
12590 };
12591
12592 edits.push((range, to_insert));
12593 original_indent_columns.push(original_indent_column);
12594 }
12595 drop(snapshot);
12596
12597 buffer.edit(
12598 edits,
12599 if auto_indent_on_paste {
12600 Some(AutoindentMode::Block {
12601 original_indent_columns,
12602 })
12603 } else {
12604 None
12605 },
12606 cx,
12607 );
12608 });
12609
12610 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12611 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12612 } else {
12613 let url = url::Url::parse(&clipboard_text).ok();
12614
12615 let auto_indent_mode = if !clipboard_text.is_empty() {
12616 Some(AutoindentMode::Block {
12617 original_indent_columns: Vec::new(),
12618 })
12619 } else {
12620 None
12621 };
12622
12623 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12624 let snapshot = buffer.snapshot(cx);
12625
12626 let anchors = old_selections
12627 .iter()
12628 .map(|s| {
12629 let anchor = snapshot.anchor_after(s.head());
12630 s.map(|_| anchor)
12631 })
12632 .collect::<Vec<_>>();
12633
12634 let mut edits = Vec::new();
12635
12636 for selection in old_selections.iter() {
12637 let language = snapshot.language_at(selection.head());
12638 let range = selection.range();
12639
12640 let (edit_range, edit_text) = if let Some(language) = language
12641 && language.name() == "Markdown".into()
12642 {
12643 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12644 } else {
12645 (range, clipboard_text.clone())
12646 };
12647
12648 edits.push((edit_range, edit_text));
12649 }
12650
12651 drop(snapshot);
12652 buffer.edit(edits, auto_indent_mode, cx);
12653
12654 anchors
12655 });
12656
12657 this.change_selections(Default::default(), window, cx, |s| {
12658 s.select_anchors(selection_anchors);
12659 });
12660 }
12661
12662 let trigger_in_words =
12663 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12664
12665 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12666 });
12667 }
12668
12669 pub fn diff_clipboard_with_selection(
12670 &mut self,
12671 _: &DiffClipboardWithSelection,
12672 window: &mut Window,
12673 cx: &mut Context<Self>,
12674 ) {
12675 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12676
12677 if selections.is_empty() {
12678 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12679 return;
12680 };
12681
12682 let clipboard_text = match cx.read_from_clipboard() {
12683 Some(item) => match item.entries().first() {
12684 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12685 _ => None,
12686 },
12687 None => None,
12688 };
12689
12690 let Some(clipboard_text) = clipboard_text else {
12691 log::warn!("Clipboard doesn't contain text.");
12692 return;
12693 };
12694
12695 window.dispatch_action(
12696 Box::new(DiffClipboardWithSelectionData {
12697 clipboard_text,
12698 editor: cx.entity(),
12699 }),
12700 cx,
12701 );
12702 }
12703
12704 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12705 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12706 if let Some(item) = cx.read_from_clipboard() {
12707 let entries = item.entries();
12708
12709 match entries.first() {
12710 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12711 // of all the pasted entries.
12712 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12713 .do_paste(
12714 clipboard_string.text(),
12715 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12716 true,
12717 window,
12718 cx,
12719 ),
12720 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12721 }
12722 }
12723 }
12724
12725 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12726 if self.read_only(cx) {
12727 return;
12728 }
12729
12730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12731
12732 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12733 if let Some((selections, _)) =
12734 self.selection_history.transaction(transaction_id).cloned()
12735 {
12736 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12737 s.select_anchors(selections.to_vec());
12738 });
12739 } else {
12740 log::error!(
12741 "No entry in selection_history found for undo. \
12742 This may correspond to a bug where undo does not update the selection. \
12743 If this is occurring, please add details to \
12744 https://github.com/zed-industries/zed/issues/22692"
12745 );
12746 }
12747 self.request_autoscroll(Autoscroll::fit(), cx);
12748 self.unmark_text(window, cx);
12749 self.refresh_edit_prediction(true, false, window, cx);
12750 cx.emit(EditorEvent::Edited { transaction_id });
12751 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12752 }
12753 }
12754
12755 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12756 if self.read_only(cx) {
12757 return;
12758 }
12759
12760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12761
12762 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12763 if let Some((_, Some(selections))) =
12764 self.selection_history.transaction(transaction_id).cloned()
12765 {
12766 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12767 s.select_anchors(selections.to_vec());
12768 });
12769 } else {
12770 log::error!(
12771 "No entry in selection_history found for redo. \
12772 This may correspond to a bug where undo does not update the selection. \
12773 If this is occurring, please add details to \
12774 https://github.com/zed-industries/zed/issues/22692"
12775 );
12776 }
12777 self.request_autoscroll(Autoscroll::fit(), cx);
12778 self.unmark_text(window, cx);
12779 self.refresh_edit_prediction(true, false, window, cx);
12780 cx.emit(EditorEvent::Edited { transaction_id });
12781 }
12782 }
12783
12784 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12785 self.buffer
12786 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12787 }
12788
12789 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12790 self.buffer
12791 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12792 }
12793
12794 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12796 self.change_selections(Default::default(), window, cx, |s| {
12797 s.move_with(|map, selection| {
12798 let cursor = if selection.is_empty() {
12799 movement::left(map, selection.start)
12800 } else {
12801 selection.start
12802 };
12803 selection.collapse_to(cursor, SelectionGoal::None);
12804 });
12805 })
12806 }
12807
12808 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12809 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12810 self.change_selections(Default::default(), window, cx, |s| {
12811 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12812 })
12813 }
12814
12815 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12816 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12817 self.change_selections(Default::default(), window, cx, |s| {
12818 s.move_with(|map, selection| {
12819 let cursor = if selection.is_empty() {
12820 movement::right(map, selection.end)
12821 } else {
12822 selection.end
12823 };
12824 selection.collapse_to(cursor, SelectionGoal::None)
12825 });
12826 })
12827 }
12828
12829 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12831 self.change_selections(Default::default(), window, cx, |s| {
12832 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12833 });
12834 }
12835
12836 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12837 if self.take_rename(true, window, cx).is_some() {
12838 return;
12839 }
12840
12841 if self.mode.is_single_line() {
12842 cx.propagate();
12843 return;
12844 }
12845
12846 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12847
12848 let text_layout_details = &self.text_layout_details(window);
12849 let selection_count = self.selections.count();
12850 let first_selection = self.selections.first_anchor();
12851
12852 self.change_selections(Default::default(), window, cx, |s| {
12853 s.move_with(|map, selection| {
12854 if !selection.is_empty() {
12855 selection.goal = SelectionGoal::None;
12856 }
12857 let (cursor, goal) = movement::up(
12858 map,
12859 selection.start,
12860 selection.goal,
12861 false,
12862 text_layout_details,
12863 );
12864 selection.collapse_to(cursor, goal);
12865 });
12866 });
12867
12868 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12869 {
12870 cx.propagate();
12871 }
12872 }
12873
12874 pub fn move_up_by_lines(
12875 &mut self,
12876 action: &MoveUpByLines,
12877 window: &mut Window,
12878 cx: &mut Context<Self>,
12879 ) {
12880 if self.take_rename(true, window, cx).is_some() {
12881 return;
12882 }
12883
12884 if self.mode.is_single_line() {
12885 cx.propagate();
12886 return;
12887 }
12888
12889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12890
12891 let text_layout_details = &self.text_layout_details(window);
12892
12893 self.change_selections(Default::default(), window, cx, |s| {
12894 s.move_with(|map, selection| {
12895 if !selection.is_empty() {
12896 selection.goal = SelectionGoal::None;
12897 }
12898 let (cursor, goal) = movement::up_by_rows(
12899 map,
12900 selection.start,
12901 action.lines,
12902 selection.goal,
12903 false,
12904 text_layout_details,
12905 );
12906 selection.collapse_to(cursor, goal);
12907 });
12908 })
12909 }
12910
12911 pub fn move_down_by_lines(
12912 &mut self,
12913 action: &MoveDownByLines,
12914 window: &mut Window,
12915 cx: &mut Context<Self>,
12916 ) {
12917 if self.take_rename(true, window, cx).is_some() {
12918 return;
12919 }
12920
12921 if self.mode.is_single_line() {
12922 cx.propagate();
12923 return;
12924 }
12925
12926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12927
12928 let text_layout_details = &self.text_layout_details(window);
12929
12930 self.change_selections(Default::default(), window, cx, |s| {
12931 s.move_with(|map, selection| {
12932 if !selection.is_empty() {
12933 selection.goal = SelectionGoal::None;
12934 }
12935 let (cursor, goal) = movement::down_by_rows(
12936 map,
12937 selection.start,
12938 action.lines,
12939 selection.goal,
12940 false,
12941 text_layout_details,
12942 );
12943 selection.collapse_to(cursor, goal);
12944 });
12945 })
12946 }
12947
12948 pub fn select_down_by_lines(
12949 &mut self,
12950 action: &SelectDownByLines,
12951 window: &mut Window,
12952 cx: &mut Context<Self>,
12953 ) {
12954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12955 let text_layout_details = &self.text_layout_details(window);
12956 self.change_selections(Default::default(), window, cx, |s| {
12957 s.move_heads_with(|map, head, goal| {
12958 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12959 })
12960 })
12961 }
12962
12963 pub fn select_up_by_lines(
12964 &mut self,
12965 action: &SelectUpByLines,
12966 window: &mut Window,
12967 cx: &mut Context<Self>,
12968 ) {
12969 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12970 let text_layout_details = &self.text_layout_details(window);
12971 self.change_selections(Default::default(), window, cx, |s| {
12972 s.move_heads_with(|map, head, goal| {
12973 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12974 })
12975 })
12976 }
12977
12978 pub fn select_page_up(
12979 &mut self,
12980 _: &SelectPageUp,
12981 window: &mut Window,
12982 cx: &mut Context<Self>,
12983 ) {
12984 let Some(row_count) = self.visible_row_count() else {
12985 return;
12986 };
12987
12988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12989
12990 let text_layout_details = &self.text_layout_details(window);
12991
12992 self.change_selections(Default::default(), window, cx, |s| {
12993 s.move_heads_with(|map, head, goal| {
12994 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12995 })
12996 })
12997 }
12998
12999 pub fn move_page_up(
13000 &mut self,
13001 action: &MovePageUp,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) {
13005 if self.take_rename(true, window, cx).is_some() {
13006 return;
13007 }
13008
13009 if self
13010 .context_menu
13011 .borrow_mut()
13012 .as_mut()
13013 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13014 .unwrap_or(false)
13015 {
13016 return;
13017 }
13018
13019 if matches!(self.mode, EditorMode::SingleLine) {
13020 cx.propagate();
13021 return;
13022 }
13023
13024 let Some(row_count) = self.visible_row_count() else {
13025 return;
13026 };
13027
13028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13029
13030 let effects = if action.center_cursor {
13031 SelectionEffects::scroll(Autoscroll::center())
13032 } else {
13033 SelectionEffects::default()
13034 };
13035
13036 let text_layout_details = &self.text_layout_details(window);
13037
13038 self.change_selections(effects, window, cx, |s| {
13039 s.move_with(|map, selection| {
13040 if !selection.is_empty() {
13041 selection.goal = SelectionGoal::None;
13042 }
13043 let (cursor, goal) = movement::up_by_rows(
13044 map,
13045 selection.end,
13046 row_count,
13047 selection.goal,
13048 false,
13049 text_layout_details,
13050 );
13051 selection.collapse_to(cursor, goal);
13052 });
13053 });
13054 }
13055
13056 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13058 let text_layout_details = &self.text_layout_details(window);
13059 self.change_selections(Default::default(), window, cx, |s| {
13060 s.move_heads_with(|map, head, goal| {
13061 movement::up(map, head, goal, false, text_layout_details)
13062 })
13063 })
13064 }
13065
13066 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13067 self.take_rename(true, window, cx);
13068
13069 if self.mode.is_single_line() {
13070 cx.propagate();
13071 return;
13072 }
13073
13074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13075
13076 let text_layout_details = &self.text_layout_details(window);
13077 let selection_count = self.selections.count();
13078 let first_selection = self.selections.first_anchor();
13079
13080 self.change_selections(Default::default(), window, cx, |s| {
13081 s.move_with(|map, selection| {
13082 if !selection.is_empty() {
13083 selection.goal = SelectionGoal::None;
13084 }
13085 let (cursor, goal) = movement::down(
13086 map,
13087 selection.end,
13088 selection.goal,
13089 false,
13090 text_layout_details,
13091 );
13092 selection.collapse_to(cursor, goal);
13093 });
13094 });
13095
13096 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13097 {
13098 cx.propagate();
13099 }
13100 }
13101
13102 pub fn select_page_down(
13103 &mut self,
13104 _: &SelectPageDown,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) {
13108 let Some(row_count) = self.visible_row_count() else {
13109 return;
13110 };
13111
13112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13113
13114 let text_layout_details = &self.text_layout_details(window);
13115
13116 self.change_selections(Default::default(), window, cx, |s| {
13117 s.move_heads_with(|map, head, goal| {
13118 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13119 })
13120 })
13121 }
13122
13123 pub fn move_page_down(
13124 &mut self,
13125 action: &MovePageDown,
13126 window: &mut Window,
13127 cx: &mut Context<Self>,
13128 ) {
13129 if self.take_rename(true, window, cx).is_some() {
13130 return;
13131 }
13132
13133 if self
13134 .context_menu
13135 .borrow_mut()
13136 .as_mut()
13137 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13138 .unwrap_or(false)
13139 {
13140 return;
13141 }
13142
13143 if matches!(self.mode, EditorMode::SingleLine) {
13144 cx.propagate();
13145 return;
13146 }
13147
13148 let Some(row_count) = self.visible_row_count() else {
13149 return;
13150 };
13151
13152 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13153
13154 let effects = if action.center_cursor {
13155 SelectionEffects::scroll(Autoscroll::center())
13156 } else {
13157 SelectionEffects::default()
13158 };
13159
13160 let text_layout_details = &self.text_layout_details(window);
13161 self.change_selections(effects, window, cx, |s| {
13162 s.move_with(|map, selection| {
13163 if !selection.is_empty() {
13164 selection.goal = SelectionGoal::None;
13165 }
13166 let (cursor, goal) = movement::down_by_rows(
13167 map,
13168 selection.end,
13169 row_count,
13170 selection.goal,
13171 false,
13172 text_layout_details,
13173 );
13174 selection.collapse_to(cursor, goal);
13175 });
13176 });
13177 }
13178
13179 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13181 let text_layout_details = &self.text_layout_details(window);
13182 self.change_selections(Default::default(), window, cx, |s| {
13183 s.move_heads_with(|map, head, goal| {
13184 movement::down(map, head, goal, false, text_layout_details)
13185 })
13186 });
13187 }
13188
13189 pub fn context_menu_first(
13190 &mut self,
13191 _: &ContextMenuFirst,
13192 window: &mut Window,
13193 cx: &mut Context<Self>,
13194 ) {
13195 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13196 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13197 }
13198 }
13199
13200 pub fn context_menu_prev(
13201 &mut self,
13202 _: &ContextMenuPrevious,
13203 window: &mut Window,
13204 cx: &mut Context<Self>,
13205 ) {
13206 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13207 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13208 }
13209 }
13210
13211 pub fn context_menu_next(
13212 &mut self,
13213 _: &ContextMenuNext,
13214 window: &mut Window,
13215 cx: &mut Context<Self>,
13216 ) {
13217 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13218 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13219 }
13220 }
13221
13222 pub fn context_menu_last(
13223 &mut self,
13224 _: &ContextMenuLast,
13225 window: &mut Window,
13226 cx: &mut Context<Self>,
13227 ) {
13228 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13229 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13230 }
13231 }
13232
13233 pub fn signature_help_prev(
13234 &mut self,
13235 _: &SignatureHelpPrevious,
13236 _: &mut Window,
13237 cx: &mut Context<Self>,
13238 ) {
13239 if let Some(popover) = self.signature_help_state.popover_mut() {
13240 if popover.current_signature == 0 {
13241 popover.current_signature = popover.signatures.len() - 1;
13242 } else {
13243 popover.current_signature -= 1;
13244 }
13245 cx.notify();
13246 }
13247 }
13248
13249 pub fn signature_help_next(
13250 &mut self,
13251 _: &SignatureHelpNext,
13252 _: &mut Window,
13253 cx: &mut Context<Self>,
13254 ) {
13255 if let Some(popover) = self.signature_help_state.popover_mut() {
13256 if popover.current_signature + 1 == popover.signatures.len() {
13257 popover.current_signature = 0;
13258 } else {
13259 popover.current_signature += 1;
13260 }
13261 cx.notify();
13262 }
13263 }
13264
13265 pub fn move_to_previous_word_start(
13266 &mut self,
13267 _: &MoveToPreviousWordStart,
13268 window: &mut Window,
13269 cx: &mut Context<Self>,
13270 ) {
13271 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13272 self.change_selections(Default::default(), window, cx, |s| {
13273 s.move_cursors_with(|map, head, _| {
13274 (
13275 movement::previous_word_start(map, head),
13276 SelectionGoal::None,
13277 )
13278 });
13279 })
13280 }
13281
13282 pub fn move_to_previous_subword_start(
13283 &mut self,
13284 _: &MoveToPreviousSubwordStart,
13285 window: &mut Window,
13286 cx: &mut Context<Self>,
13287 ) {
13288 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13289 self.change_selections(Default::default(), window, cx, |s| {
13290 s.move_cursors_with(|map, head, _| {
13291 (
13292 movement::previous_subword_start(map, head),
13293 SelectionGoal::None,
13294 )
13295 });
13296 })
13297 }
13298
13299 pub fn select_to_previous_word_start(
13300 &mut self,
13301 _: &SelectToPreviousWordStart,
13302 window: &mut Window,
13303 cx: &mut Context<Self>,
13304 ) {
13305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13306 self.change_selections(Default::default(), window, cx, |s| {
13307 s.move_heads_with(|map, head, _| {
13308 (
13309 movement::previous_word_start(map, head),
13310 SelectionGoal::None,
13311 )
13312 });
13313 })
13314 }
13315
13316 pub fn select_to_previous_subword_start(
13317 &mut self,
13318 _: &SelectToPreviousSubwordStart,
13319 window: &mut Window,
13320 cx: &mut Context<Self>,
13321 ) {
13322 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13323 self.change_selections(Default::default(), window, cx, |s| {
13324 s.move_heads_with(|map, head, _| {
13325 (
13326 movement::previous_subword_start(map, head),
13327 SelectionGoal::None,
13328 )
13329 });
13330 })
13331 }
13332
13333 pub fn delete_to_previous_word_start(
13334 &mut self,
13335 action: &DeleteToPreviousWordStart,
13336 window: &mut Window,
13337 cx: &mut Context<Self>,
13338 ) {
13339 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13340 self.transact(window, cx, |this, window, cx| {
13341 this.select_autoclose_pair(window, cx);
13342 this.change_selections(Default::default(), window, cx, |s| {
13343 s.move_with(|map, selection| {
13344 if selection.is_empty() {
13345 let mut cursor = if action.ignore_newlines {
13346 movement::previous_word_start(map, selection.head())
13347 } else {
13348 movement::previous_word_start_or_newline(map, selection.head())
13349 };
13350 cursor = movement::adjust_greedy_deletion(
13351 map,
13352 selection.head(),
13353 cursor,
13354 action.ignore_brackets,
13355 );
13356 selection.set_head(cursor, SelectionGoal::None);
13357 }
13358 });
13359 });
13360 this.insert("", window, cx);
13361 });
13362 }
13363
13364 pub fn delete_to_previous_subword_start(
13365 &mut self,
13366 _: &DeleteToPreviousSubwordStart,
13367 window: &mut Window,
13368 cx: &mut Context<Self>,
13369 ) {
13370 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13371 self.transact(window, cx, |this, window, cx| {
13372 this.select_autoclose_pair(window, cx);
13373 this.change_selections(Default::default(), window, cx, |s| {
13374 s.move_with(|map, selection| {
13375 if selection.is_empty() {
13376 let mut cursor = movement::previous_subword_start(map, selection.head());
13377 cursor =
13378 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13379 selection.set_head(cursor, SelectionGoal::None);
13380 }
13381 });
13382 });
13383 this.insert("", window, cx);
13384 });
13385 }
13386
13387 pub fn move_to_next_word_end(
13388 &mut self,
13389 _: &MoveToNextWordEnd,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13394 self.change_selections(Default::default(), window, cx, |s| {
13395 s.move_cursors_with(|map, head, _| {
13396 (movement::next_word_end(map, head), SelectionGoal::None)
13397 });
13398 })
13399 }
13400
13401 pub fn move_to_next_subword_end(
13402 &mut self,
13403 _: &MoveToNextSubwordEnd,
13404 window: &mut Window,
13405 cx: &mut Context<Self>,
13406 ) {
13407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13408 self.change_selections(Default::default(), window, cx, |s| {
13409 s.move_cursors_with(|map, head, _| {
13410 (movement::next_subword_end(map, head), SelectionGoal::None)
13411 });
13412 })
13413 }
13414
13415 pub fn select_to_next_word_end(
13416 &mut self,
13417 _: &SelectToNextWordEnd,
13418 window: &mut Window,
13419 cx: &mut Context<Self>,
13420 ) {
13421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13422 self.change_selections(Default::default(), window, cx, |s| {
13423 s.move_heads_with(|map, head, _| {
13424 (movement::next_word_end(map, head), SelectionGoal::None)
13425 });
13426 })
13427 }
13428
13429 pub fn select_to_next_subword_end(
13430 &mut self,
13431 _: &SelectToNextSubwordEnd,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436 self.change_selections(Default::default(), window, cx, |s| {
13437 s.move_heads_with(|map, head, _| {
13438 (movement::next_subword_end(map, head), SelectionGoal::None)
13439 });
13440 })
13441 }
13442
13443 pub fn delete_to_next_word_end(
13444 &mut self,
13445 action: &DeleteToNextWordEnd,
13446 window: &mut Window,
13447 cx: &mut Context<Self>,
13448 ) {
13449 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13450 self.transact(window, cx, |this, window, cx| {
13451 this.change_selections(Default::default(), window, cx, |s| {
13452 s.move_with(|map, selection| {
13453 if selection.is_empty() {
13454 let mut cursor = if action.ignore_newlines {
13455 movement::next_word_end(map, selection.head())
13456 } else {
13457 movement::next_word_end_or_newline(map, selection.head())
13458 };
13459 cursor = movement::adjust_greedy_deletion(
13460 map,
13461 selection.head(),
13462 cursor,
13463 action.ignore_brackets,
13464 );
13465 selection.set_head(cursor, SelectionGoal::None);
13466 }
13467 });
13468 });
13469 this.insert("", window, cx);
13470 });
13471 }
13472
13473 pub fn delete_to_next_subword_end(
13474 &mut self,
13475 _: &DeleteToNextSubwordEnd,
13476 window: &mut Window,
13477 cx: &mut Context<Self>,
13478 ) {
13479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13480 self.transact(window, cx, |this, window, cx| {
13481 this.change_selections(Default::default(), window, cx, |s| {
13482 s.move_with(|map, selection| {
13483 if selection.is_empty() {
13484 let mut cursor = movement::next_subword_end(map, selection.head());
13485 cursor =
13486 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13487 selection.set_head(cursor, SelectionGoal::None);
13488 }
13489 });
13490 });
13491 this.insert("", window, cx);
13492 });
13493 }
13494
13495 pub fn move_to_beginning_of_line(
13496 &mut self,
13497 action: &MoveToBeginningOfLine,
13498 window: &mut Window,
13499 cx: &mut Context<Self>,
13500 ) {
13501 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13502 self.change_selections(Default::default(), window, cx, |s| {
13503 s.move_cursors_with(|map, head, _| {
13504 (
13505 movement::indented_line_beginning(
13506 map,
13507 head,
13508 action.stop_at_soft_wraps,
13509 action.stop_at_indent,
13510 ),
13511 SelectionGoal::None,
13512 )
13513 });
13514 })
13515 }
13516
13517 pub fn select_to_beginning_of_line(
13518 &mut self,
13519 action: &SelectToBeginningOfLine,
13520 window: &mut Window,
13521 cx: &mut Context<Self>,
13522 ) {
13523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13524 self.change_selections(Default::default(), window, cx, |s| {
13525 s.move_heads_with(|map, head, _| {
13526 (
13527 movement::indented_line_beginning(
13528 map,
13529 head,
13530 action.stop_at_soft_wraps,
13531 action.stop_at_indent,
13532 ),
13533 SelectionGoal::None,
13534 )
13535 });
13536 });
13537 }
13538
13539 pub fn delete_to_beginning_of_line(
13540 &mut self,
13541 action: &DeleteToBeginningOfLine,
13542 window: &mut Window,
13543 cx: &mut Context<Self>,
13544 ) {
13545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13546 self.transact(window, cx, |this, window, cx| {
13547 this.change_selections(Default::default(), window, cx, |s| {
13548 s.move_with(|_, selection| {
13549 selection.reversed = true;
13550 });
13551 });
13552
13553 this.select_to_beginning_of_line(
13554 &SelectToBeginningOfLine {
13555 stop_at_soft_wraps: false,
13556 stop_at_indent: action.stop_at_indent,
13557 },
13558 window,
13559 cx,
13560 );
13561 this.backspace(&Backspace, window, cx);
13562 });
13563 }
13564
13565 pub fn move_to_end_of_line(
13566 &mut self,
13567 action: &MoveToEndOfLine,
13568 window: &mut Window,
13569 cx: &mut Context<Self>,
13570 ) {
13571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13572 self.change_selections(Default::default(), window, cx, |s| {
13573 s.move_cursors_with(|map, head, _| {
13574 (
13575 movement::line_end(map, head, action.stop_at_soft_wraps),
13576 SelectionGoal::None,
13577 )
13578 });
13579 })
13580 }
13581
13582 pub fn select_to_end_of_line(
13583 &mut self,
13584 action: &SelectToEndOfLine,
13585 window: &mut Window,
13586 cx: &mut Context<Self>,
13587 ) {
13588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13589 self.change_selections(Default::default(), window, cx, |s| {
13590 s.move_heads_with(|map, head, _| {
13591 (
13592 movement::line_end(map, head, action.stop_at_soft_wraps),
13593 SelectionGoal::None,
13594 )
13595 });
13596 })
13597 }
13598
13599 pub fn delete_to_end_of_line(
13600 &mut self,
13601 _: &DeleteToEndOfLine,
13602 window: &mut Window,
13603 cx: &mut Context<Self>,
13604 ) {
13605 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13606 self.transact(window, cx, |this, window, cx| {
13607 this.select_to_end_of_line(
13608 &SelectToEndOfLine {
13609 stop_at_soft_wraps: false,
13610 },
13611 window,
13612 cx,
13613 );
13614 this.delete(&Delete, window, cx);
13615 });
13616 }
13617
13618 pub fn cut_to_end_of_line(
13619 &mut self,
13620 action: &CutToEndOfLine,
13621 window: &mut Window,
13622 cx: &mut Context<Self>,
13623 ) {
13624 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13625 self.transact(window, cx, |this, window, cx| {
13626 this.select_to_end_of_line(
13627 &SelectToEndOfLine {
13628 stop_at_soft_wraps: false,
13629 },
13630 window,
13631 cx,
13632 );
13633 if !action.stop_at_newlines {
13634 this.change_selections(Default::default(), window, cx, |s| {
13635 s.move_with(|_, sel| {
13636 if sel.is_empty() {
13637 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13638 }
13639 });
13640 });
13641 }
13642 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13643 let item = this.cut_common(false, window, cx);
13644 cx.write_to_clipboard(item);
13645 });
13646 }
13647
13648 pub fn move_to_start_of_paragraph(
13649 &mut self,
13650 _: &MoveToStartOfParagraph,
13651 window: &mut Window,
13652 cx: &mut Context<Self>,
13653 ) {
13654 if matches!(self.mode, EditorMode::SingleLine) {
13655 cx.propagate();
13656 return;
13657 }
13658 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13659 self.change_selections(Default::default(), window, cx, |s| {
13660 s.move_with(|map, selection| {
13661 selection.collapse_to(
13662 movement::start_of_paragraph(map, selection.head(), 1),
13663 SelectionGoal::None,
13664 )
13665 });
13666 })
13667 }
13668
13669 pub fn move_to_end_of_paragraph(
13670 &mut self,
13671 _: &MoveToEndOfParagraph,
13672 window: &mut Window,
13673 cx: &mut Context<Self>,
13674 ) {
13675 if matches!(self.mode, EditorMode::SingleLine) {
13676 cx.propagate();
13677 return;
13678 }
13679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13680 self.change_selections(Default::default(), window, cx, |s| {
13681 s.move_with(|map, selection| {
13682 selection.collapse_to(
13683 movement::end_of_paragraph(map, selection.head(), 1),
13684 SelectionGoal::None,
13685 )
13686 });
13687 })
13688 }
13689
13690 pub fn select_to_start_of_paragraph(
13691 &mut self,
13692 _: &SelectToStartOfParagraph,
13693 window: &mut Window,
13694 cx: &mut Context<Self>,
13695 ) {
13696 if matches!(self.mode, EditorMode::SingleLine) {
13697 cx.propagate();
13698 return;
13699 }
13700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13701 self.change_selections(Default::default(), window, cx, |s| {
13702 s.move_heads_with(|map, head, _| {
13703 (
13704 movement::start_of_paragraph(map, head, 1),
13705 SelectionGoal::None,
13706 )
13707 });
13708 })
13709 }
13710
13711 pub fn select_to_end_of_paragraph(
13712 &mut self,
13713 _: &SelectToEndOfParagraph,
13714 window: &mut Window,
13715 cx: &mut Context<Self>,
13716 ) {
13717 if matches!(self.mode, EditorMode::SingleLine) {
13718 cx.propagate();
13719 return;
13720 }
13721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13722 self.change_selections(Default::default(), window, cx, |s| {
13723 s.move_heads_with(|map, head, _| {
13724 (
13725 movement::end_of_paragraph(map, head, 1),
13726 SelectionGoal::None,
13727 )
13728 });
13729 })
13730 }
13731
13732 pub fn move_to_start_of_excerpt(
13733 &mut self,
13734 _: &MoveToStartOfExcerpt,
13735 window: &mut Window,
13736 cx: &mut Context<Self>,
13737 ) {
13738 if matches!(self.mode, EditorMode::SingleLine) {
13739 cx.propagate();
13740 return;
13741 }
13742 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13743 self.change_selections(Default::default(), window, cx, |s| {
13744 s.move_with(|map, selection| {
13745 selection.collapse_to(
13746 movement::start_of_excerpt(
13747 map,
13748 selection.head(),
13749 workspace::searchable::Direction::Prev,
13750 ),
13751 SelectionGoal::None,
13752 )
13753 });
13754 })
13755 }
13756
13757 pub fn move_to_start_of_next_excerpt(
13758 &mut self,
13759 _: &MoveToStartOfNextExcerpt,
13760 window: &mut Window,
13761 cx: &mut Context<Self>,
13762 ) {
13763 if matches!(self.mode, EditorMode::SingleLine) {
13764 cx.propagate();
13765 return;
13766 }
13767
13768 self.change_selections(Default::default(), window, cx, |s| {
13769 s.move_with(|map, selection| {
13770 selection.collapse_to(
13771 movement::start_of_excerpt(
13772 map,
13773 selection.head(),
13774 workspace::searchable::Direction::Next,
13775 ),
13776 SelectionGoal::None,
13777 )
13778 });
13779 })
13780 }
13781
13782 pub fn move_to_end_of_excerpt(
13783 &mut self,
13784 _: &MoveToEndOfExcerpt,
13785 window: &mut Window,
13786 cx: &mut Context<Self>,
13787 ) {
13788 if matches!(self.mode, EditorMode::SingleLine) {
13789 cx.propagate();
13790 return;
13791 }
13792 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13793 self.change_selections(Default::default(), window, cx, |s| {
13794 s.move_with(|map, selection| {
13795 selection.collapse_to(
13796 movement::end_of_excerpt(
13797 map,
13798 selection.head(),
13799 workspace::searchable::Direction::Next,
13800 ),
13801 SelectionGoal::None,
13802 )
13803 });
13804 })
13805 }
13806
13807 pub fn move_to_end_of_previous_excerpt(
13808 &mut self,
13809 _: &MoveToEndOfPreviousExcerpt,
13810 window: &mut Window,
13811 cx: &mut Context<Self>,
13812 ) {
13813 if matches!(self.mode, EditorMode::SingleLine) {
13814 cx.propagate();
13815 return;
13816 }
13817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13818 self.change_selections(Default::default(), window, cx, |s| {
13819 s.move_with(|map, selection| {
13820 selection.collapse_to(
13821 movement::end_of_excerpt(
13822 map,
13823 selection.head(),
13824 workspace::searchable::Direction::Prev,
13825 ),
13826 SelectionGoal::None,
13827 )
13828 });
13829 })
13830 }
13831
13832 pub fn select_to_start_of_excerpt(
13833 &mut self,
13834 _: &SelectToStartOfExcerpt,
13835 window: &mut Window,
13836 cx: &mut Context<Self>,
13837 ) {
13838 if matches!(self.mode, EditorMode::SingleLine) {
13839 cx.propagate();
13840 return;
13841 }
13842 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13843 self.change_selections(Default::default(), window, cx, |s| {
13844 s.move_heads_with(|map, head, _| {
13845 (
13846 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13847 SelectionGoal::None,
13848 )
13849 });
13850 })
13851 }
13852
13853 pub fn select_to_start_of_next_excerpt(
13854 &mut self,
13855 _: &SelectToStartOfNextExcerpt,
13856 window: &mut Window,
13857 cx: &mut Context<Self>,
13858 ) {
13859 if matches!(self.mode, EditorMode::SingleLine) {
13860 cx.propagate();
13861 return;
13862 }
13863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13864 self.change_selections(Default::default(), window, cx, |s| {
13865 s.move_heads_with(|map, head, _| {
13866 (
13867 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13868 SelectionGoal::None,
13869 )
13870 });
13871 })
13872 }
13873
13874 pub fn select_to_end_of_excerpt(
13875 &mut self,
13876 _: &SelectToEndOfExcerpt,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 if matches!(self.mode, EditorMode::SingleLine) {
13881 cx.propagate();
13882 return;
13883 }
13884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13885 self.change_selections(Default::default(), window, cx, |s| {
13886 s.move_heads_with(|map, head, _| {
13887 (
13888 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13889 SelectionGoal::None,
13890 )
13891 });
13892 })
13893 }
13894
13895 pub fn select_to_end_of_previous_excerpt(
13896 &mut self,
13897 _: &SelectToEndOfPreviousExcerpt,
13898 window: &mut Window,
13899 cx: &mut Context<Self>,
13900 ) {
13901 if matches!(self.mode, EditorMode::SingleLine) {
13902 cx.propagate();
13903 return;
13904 }
13905 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13906 self.change_selections(Default::default(), window, cx, |s| {
13907 s.move_heads_with(|map, head, _| {
13908 (
13909 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13910 SelectionGoal::None,
13911 )
13912 });
13913 })
13914 }
13915
13916 pub fn move_to_beginning(
13917 &mut self,
13918 _: &MoveToBeginning,
13919 window: &mut Window,
13920 cx: &mut Context<Self>,
13921 ) {
13922 if matches!(self.mode, EditorMode::SingleLine) {
13923 cx.propagate();
13924 return;
13925 }
13926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13927 self.change_selections(Default::default(), window, cx, |s| {
13928 s.select_ranges(vec![0..0]);
13929 });
13930 }
13931
13932 pub fn select_to_beginning(
13933 &mut self,
13934 _: &SelectToBeginning,
13935 window: &mut Window,
13936 cx: &mut Context<Self>,
13937 ) {
13938 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13939 selection.set_head(Point::zero(), SelectionGoal::None);
13940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13941 self.change_selections(Default::default(), window, cx, |s| {
13942 s.select(vec![selection]);
13943 });
13944 }
13945
13946 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13947 if matches!(self.mode, EditorMode::SingleLine) {
13948 cx.propagate();
13949 return;
13950 }
13951 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13952 let cursor = self.buffer.read(cx).read(cx).len();
13953 self.change_selections(Default::default(), window, cx, |s| {
13954 s.select_ranges(vec![cursor..cursor])
13955 });
13956 }
13957
13958 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13959 self.nav_history = nav_history;
13960 }
13961
13962 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13963 self.nav_history.as_ref()
13964 }
13965
13966 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13967 self.push_to_nav_history(
13968 self.selections.newest_anchor().head(),
13969 None,
13970 false,
13971 true,
13972 cx,
13973 );
13974 }
13975
13976 fn push_to_nav_history(
13977 &mut self,
13978 cursor_anchor: Anchor,
13979 new_position: Option<Point>,
13980 is_deactivate: bool,
13981 always: bool,
13982 cx: &mut Context<Self>,
13983 ) {
13984 if let Some(nav_history) = self.nav_history.as_mut() {
13985 let buffer = self.buffer.read(cx).read(cx);
13986 let cursor_position = cursor_anchor.to_point(&buffer);
13987 let scroll_state = self.scroll_manager.anchor();
13988 let scroll_top_row = scroll_state.top_row(&buffer);
13989 drop(buffer);
13990
13991 if let Some(new_position) = new_position {
13992 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13993 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13994 return;
13995 }
13996 }
13997
13998 nav_history.push(
13999 Some(NavigationData {
14000 cursor_anchor,
14001 cursor_position,
14002 scroll_anchor: scroll_state,
14003 scroll_top_row,
14004 }),
14005 cx,
14006 );
14007 cx.emit(EditorEvent::PushedToNavHistory {
14008 anchor: cursor_anchor,
14009 is_deactivate,
14010 })
14011 }
14012 }
14013
14014 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14016 let buffer = self.buffer.read(cx).snapshot(cx);
14017 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14018 selection.set_head(buffer.len(), SelectionGoal::None);
14019 self.change_selections(Default::default(), window, cx, |s| {
14020 s.select(vec![selection]);
14021 });
14022 }
14023
14024 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14026 let end = self.buffer.read(cx).read(cx).len();
14027 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14028 s.select_ranges(vec![0..end]);
14029 });
14030 }
14031
14032 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14033 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14034 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14035 let mut selections = self.selections.all::<Point>(&display_map);
14036 let max_point = display_map.buffer_snapshot().max_point();
14037 for selection in &mut selections {
14038 let rows = selection.spanned_rows(true, &display_map);
14039 selection.start = Point::new(rows.start.0, 0);
14040 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14041 selection.reversed = false;
14042 }
14043 self.change_selections(Default::default(), window, cx, |s| {
14044 s.select(selections);
14045 });
14046 }
14047
14048 pub fn split_selection_into_lines(
14049 &mut self,
14050 action: &SplitSelectionIntoLines,
14051 window: &mut Window,
14052 cx: &mut Context<Self>,
14053 ) {
14054 let selections = self
14055 .selections
14056 .all::<Point>(&self.display_snapshot(cx))
14057 .into_iter()
14058 .map(|selection| selection.start..selection.end)
14059 .collect::<Vec<_>>();
14060 self.unfold_ranges(&selections, true, true, cx);
14061
14062 let mut new_selection_ranges = Vec::new();
14063 {
14064 let buffer = self.buffer.read(cx).read(cx);
14065 for selection in selections {
14066 for row in selection.start.row..selection.end.row {
14067 let line_start = Point::new(row, 0);
14068 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14069
14070 if action.keep_selections {
14071 // Keep the selection range for each line
14072 let selection_start = if row == selection.start.row {
14073 selection.start
14074 } else {
14075 line_start
14076 };
14077 new_selection_ranges.push(selection_start..line_end);
14078 } else {
14079 // Collapse to cursor at end of line
14080 new_selection_ranges.push(line_end..line_end);
14081 }
14082 }
14083
14084 let is_multiline_selection = selection.start.row != selection.end.row;
14085 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14086 // so this action feels more ergonomic when paired with other selection operations
14087 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14088 if !should_skip_last {
14089 if action.keep_selections {
14090 if is_multiline_selection {
14091 let line_start = Point::new(selection.end.row, 0);
14092 new_selection_ranges.push(line_start..selection.end);
14093 } else {
14094 new_selection_ranges.push(selection.start..selection.end);
14095 }
14096 } else {
14097 new_selection_ranges.push(selection.end..selection.end);
14098 }
14099 }
14100 }
14101 }
14102 self.change_selections(Default::default(), window, cx, |s| {
14103 s.select_ranges(new_selection_ranges);
14104 });
14105 }
14106
14107 pub fn add_selection_above(
14108 &mut self,
14109 action: &AddSelectionAbove,
14110 window: &mut Window,
14111 cx: &mut Context<Self>,
14112 ) {
14113 self.add_selection(true, action.skip_soft_wrap, window, cx);
14114 }
14115
14116 pub fn add_selection_below(
14117 &mut self,
14118 action: &AddSelectionBelow,
14119 window: &mut Window,
14120 cx: &mut Context<Self>,
14121 ) {
14122 self.add_selection(false, action.skip_soft_wrap, window, cx);
14123 }
14124
14125 fn add_selection(
14126 &mut self,
14127 above: bool,
14128 skip_soft_wrap: bool,
14129 window: &mut Window,
14130 cx: &mut Context<Self>,
14131 ) {
14132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14133
14134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14135 let all_selections = self.selections.all::<Point>(&display_map);
14136 let text_layout_details = self.text_layout_details(window);
14137
14138 let (mut columnar_selections, new_selections_to_columnarize) = {
14139 if let Some(state) = self.add_selections_state.as_ref() {
14140 let columnar_selection_ids: HashSet<_> = state
14141 .groups
14142 .iter()
14143 .flat_map(|group| group.stack.iter())
14144 .copied()
14145 .collect();
14146
14147 all_selections
14148 .into_iter()
14149 .partition(|s| columnar_selection_ids.contains(&s.id))
14150 } else {
14151 (Vec::new(), all_selections)
14152 }
14153 };
14154
14155 let mut state = self
14156 .add_selections_state
14157 .take()
14158 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14159
14160 for selection in new_selections_to_columnarize {
14161 let range = selection.display_range(&display_map).sorted();
14162 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14163 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14164 let positions = start_x.min(end_x)..start_x.max(end_x);
14165 let mut stack = Vec::new();
14166 for row in range.start.row().0..=range.end.row().0 {
14167 if let Some(selection) = self.selections.build_columnar_selection(
14168 &display_map,
14169 DisplayRow(row),
14170 &positions,
14171 selection.reversed,
14172 &text_layout_details,
14173 ) {
14174 stack.push(selection.id);
14175 columnar_selections.push(selection);
14176 }
14177 }
14178 if !stack.is_empty() {
14179 if above {
14180 stack.reverse();
14181 }
14182 state.groups.push(AddSelectionsGroup { above, stack });
14183 }
14184 }
14185
14186 let mut final_selections = Vec::new();
14187 let end_row = if above {
14188 DisplayRow(0)
14189 } else {
14190 display_map.max_point().row()
14191 };
14192
14193 let mut last_added_item_per_group = HashMap::default();
14194 for group in state.groups.iter_mut() {
14195 if let Some(last_id) = group.stack.last() {
14196 last_added_item_per_group.insert(*last_id, group);
14197 }
14198 }
14199
14200 for selection in columnar_selections {
14201 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14202 if above == group.above {
14203 let range = selection.display_range(&display_map).sorted();
14204 debug_assert_eq!(range.start.row(), range.end.row());
14205 let mut row = range.start.row();
14206 let positions =
14207 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14208 Pixels::from(start)..Pixels::from(end)
14209 } else {
14210 let start_x =
14211 display_map.x_for_display_point(range.start, &text_layout_details);
14212 let end_x =
14213 display_map.x_for_display_point(range.end, &text_layout_details);
14214 start_x.min(end_x)..start_x.max(end_x)
14215 };
14216
14217 let mut maybe_new_selection = None;
14218 let direction = if above { -1 } else { 1 };
14219
14220 while row != end_row {
14221 if skip_soft_wrap {
14222 row = display_map
14223 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14224 .row();
14225 } else if above {
14226 row.0 -= 1;
14227 } else {
14228 row.0 += 1;
14229 }
14230
14231 if let Some(new_selection) = self.selections.build_columnar_selection(
14232 &display_map,
14233 row,
14234 &positions,
14235 selection.reversed,
14236 &text_layout_details,
14237 ) {
14238 maybe_new_selection = Some(new_selection);
14239 break;
14240 }
14241 }
14242
14243 if let Some(new_selection) = maybe_new_selection {
14244 group.stack.push(new_selection.id);
14245 if above {
14246 final_selections.push(new_selection);
14247 final_selections.push(selection);
14248 } else {
14249 final_selections.push(selection);
14250 final_selections.push(new_selection);
14251 }
14252 } else {
14253 final_selections.push(selection);
14254 }
14255 } else {
14256 group.stack.pop();
14257 }
14258 } else {
14259 final_selections.push(selection);
14260 }
14261 }
14262
14263 self.change_selections(Default::default(), window, cx, |s| {
14264 s.select(final_selections);
14265 });
14266
14267 let final_selection_ids: HashSet<_> = self
14268 .selections
14269 .all::<Point>(&display_map)
14270 .iter()
14271 .map(|s| s.id)
14272 .collect();
14273 state.groups.retain_mut(|group| {
14274 // selections might get merged above so we remove invalid items from stacks
14275 group.stack.retain(|id| final_selection_ids.contains(id));
14276
14277 // single selection in stack can be treated as initial state
14278 group.stack.len() > 1
14279 });
14280
14281 if !state.groups.is_empty() {
14282 self.add_selections_state = Some(state);
14283 }
14284 }
14285
14286 fn select_match_ranges(
14287 &mut self,
14288 range: Range<usize>,
14289 reversed: bool,
14290 replace_newest: bool,
14291 auto_scroll: Option<Autoscroll>,
14292 window: &mut Window,
14293 cx: &mut Context<Editor>,
14294 ) {
14295 self.unfold_ranges(
14296 std::slice::from_ref(&range),
14297 false,
14298 auto_scroll.is_some(),
14299 cx,
14300 );
14301 let effects = if let Some(scroll) = auto_scroll {
14302 SelectionEffects::scroll(scroll)
14303 } else {
14304 SelectionEffects::no_scroll()
14305 };
14306 self.change_selections(effects, window, cx, |s| {
14307 if replace_newest {
14308 s.delete(s.newest_anchor().id);
14309 }
14310 if reversed {
14311 s.insert_range(range.end..range.start);
14312 } else {
14313 s.insert_range(range);
14314 }
14315 });
14316 }
14317
14318 pub fn select_next_match_internal(
14319 &mut self,
14320 display_map: &DisplaySnapshot,
14321 replace_newest: bool,
14322 autoscroll: Option<Autoscroll>,
14323 window: &mut Window,
14324 cx: &mut Context<Self>,
14325 ) -> Result<()> {
14326 let buffer = display_map.buffer_snapshot();
14327 let mut selections = self.selections.all::<usize>(&display_map);
14328 if let Some(mut select_next_state) = self.select_next_state.take() {
14329 let query = &select_next_state.query;
14330 if !select_next_state.done {
14331 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14332 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14333 let mut next_selected_range = None;
14334
14335 // Collect and sort selection ranges for efficient overlap checking
14336 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14337 selection_ranges.sort_by_key(|r| r.start);
14338
14339 let bytes_after_last_selection =
14340 buffer.bytes_in_range(last_selection.end..buffer.len());
14341 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14342 let query_matches = query
14343 .stream_find_iter(bytes_after_last_selection)
14344 .map(|result| (last_selection.end, result))
14345 .chain(
14346 query
14347 .stream_find_iter(bytes_before_first_selection)
14348 .map(|result| (0, result)),
14349 );
14350
14351 for (start_offset, query_match) in query_matches {
14352 let query_match = query_match.unwrap(); // can only fail due to I/O
14353 let offset_range =
14354 start_offset + query_match.start()..start_offset + query_match.end();
14355
14356 if !select_next_state.wordwise
14357 || (!buffer.is_inside_word(offset_range.start, None)
14358 && !buffer.is_inside_word(offset_range.end, None))
14359 {
14360 // Use binary search to check for overlap (O(log n))
14361 let overlaps = selection_ranges
14362 .binary_search_by(|range| {
14363 if range.end <= offset_range.start {
14364 std::cmp::Ordering::Less
14365 } else if range.start >= offset_range.end {
14366 std::cmp::Ordering::Greater
14367 } else {
14368 std::cmp::Ordering::Equal
14369 }
14370 })
14371 .is_ok();
14372
14373 if !overlaps {
14374 next_selected_range = Some(offset_range);
14375 break;
14376 }
14377 }
14378 }
14379
14380 if let Some(next_selected_range) = next_selected_range {
14381 self.select_match_ranges(
14382 next_selected_range,
14383 last_selection.reversed,
14384 replace_newest,
14385 autoscroll,
14386 window,
14387 cx,
14388 );
14389 } else {
14390 select_next_state.done = true;
14391 }
14392 }
14393
14394 self.select_next_state = Some(select_next_state);
14395 } else {
14396 let mut only_carets = true;
14397 let mut same_text_selected = true;
14398 let mut selected_text = None;
14399
14400 let mut selections_iter = selections.iter().peekable();
14401 while let Some(selection) = selections_iter.next() {
14402 if selection.start != selection.end {
14403 only_carets = false;
14404 }
14405
14406 if same_text_selected {
14407 if selected_text.is_none() {
14408 selected_text =
14409 Some(buffer.text_for_range(selection.range()).collect::<String>());
14410 }
14411
14412 if let Some(next_selection) = selections_iter.peek() {
14413 if next_selection.range().len() == selection.range().len() {
14414 let next_selected_text = buffer
14415 .text_for_range(next_selection.range())
14416 .collect::<String>();
14417 if Some(next_selected_text) != selected_text {
14418 same_text_selected = false;
14419 selected_text = None;
14420 }
14421 } else {
14422 same_text_selected = false;
14423 selected_text = None;
14424 }
14425 }
14426 }
14427 }
14428
14429 if only_carets {
14430 for selection in &mut selections {
14431 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14432 selection.start = word_range.start;
14433 selection.end = word_range.end;
14434 selection.goal = SelectionGoal::None;
14435 selection.reversed = false;
14436 self.select_match_ranges(
14437 selection.start..selection.end,
14438 selection.reversed,
14439 replace_newest,
14440 autoscroll,
14441 window,
14442 cx,
14443 );
14444 }
14445
14446 if selections.len() == 1 {
14447 let selection = selections
14448 .last()
14449 .expect("ensured that there's only one selection");
14450 let query = buffer
14451 .text_for_range(selection.start..selection.end)
14452 .collect::<String>();
14453 let is_empty = query.is_empty();
14454 let select_state = SelectNextState {
14455 query: AhoCorasick::new(&[query])?,
14456 wordwise: true,
14457 done: is_empty,
14458 };
14459 self.select_next_state = Some(select_state);
14460 } else {
14461 self.select_next_state = None;
14462 }
14463 } else if let Some(selected_text) = selected_text {
14464 self.select_next_state = Some(SelectNextState {
14465 query: AhoCorasick::new(&[selected_text])?,
14466 wordwise: false,
14467 done: false,
14468 });
14469 self.select_next_match_internal(
14470 display_map,
14471 replace_newest,
14472 autoscroll,
14473 window,
14474 cx,
14475 )?;
14476 }
14477 }
14478 Ok(())
14479 }
14480
14481 pub fn select_all_matches(
14482 &mut self,
14483 _action: &SelectAllMatches,
14484 window: &mut Window,
14485 cx: &mut Context<Self>,
14486 ) -> Result<()> {
14487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14488
14489 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14490
14491 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14492 let Some(select_next_state) = self.select_next_state.as_mut() else {
14493 return Ok(());
14494 };
14495 if select_next_state.done {
14496 return Ok(());
14497 }
14498
14499 let mut new_selections = Vec::new();
14500
14501 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14502 let buffer = display_map.buffer_snapshot();
14503 let query_matches = select_next_state
14504 .query
14505 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14506
14507 for query_match in query_matches.into_iter() {
14508 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14509 let offset_range = if reversed {
14510 query_match.end()..query_match.start()
14511 } else {
14512 query_match.start()..query_match.end()
14513 };
14514
14515 if !select_next_state.wordwise
14516 || (!buffer.is_inside_word(offset_range.start, None)
14517 && !buffer.is_inside_word(offset_range.end, None))
14518 {
14519 new_selections.push(offset_range.start..offset_range.end);
14520 }
14521 }
14522
14523 select_next_state.done = true;
14524
14525 if new_selections.is_empty() {
14526 log::error!("bug: new_selections is empty in select_all_matches");
14527 return Ok(());
14528 }
14529
14530 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14531 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14532 selections.select_ranges(new_selections)
14533 });
14534
14535 Ok(())
14536 }
14537
14538 pub fn select_next(
14539 &mut self,
14540 action: &SelectNext,
14541 window: &mut Window,
14542 cx: &mut Context<Self>,
14543 ) -> Result<()> {
14544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14545 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14546 self.select_next_match_internal(
14547 &display_map,
14548 action.replace_newest,
14549 Some(Autoscroll::newest()),
14550 window,
14551 cx,
14552 )?;
14553 Ok(())
14554 }
14555
14556 pub fn select_previous(
14557 &mut self,
14558 action: &SelectPrevious,
14559 window: &mut Window,
14560 cx: &mut Context<Self>,
14561 ) -> Result<()> {
14562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14564 let buffer = display_map.buffer_snapshot();
14565 let mut selections = self.selections.all::<usize>(&display_map);
14566 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14567 let query = &select_prev_state.query;
14568 if !select_prev_state.done {
14569 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14570 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14571 let mut next_selected_range = None;
14572 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14573 let bytes_before_last_selection =
14574 buffer.reversed_bytes_in_range(0..last_selection.start);
14575 let bytes_after_first_selection =
14576 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14577 let query_matches = query
14578 .stream_find_iter(bytes_before_last_selection)
14579 .map(|result| (last_selection.start, result))
14580 .chain(
14581 query
14582 .stream_find_iter(bytes_after_first_selection)
14583 .map(|result| (buffer.len(), result)),
14584 );
14585 for (end_offset, query_match) in query_matches {
14586 let query_match = query_match.unwrap(); // can only fail due to I/O
14587 let offset_range =
14588 end_offset - query_match.end()..end_offset - query_match.start();
14589
14590 if !select_prev_state.wordwise
14591 || (!buffer.is_inside_word(offset_range.start, None)
14592 && !buffer.is_inside_word(offset_range.end, None))
14593 {
14594 next_selected_range = Some(offset_range);
14595 break;
14596 }
14597 }
14598
14599 if let Some(next_selected_range) = next_selected_range {
14600 self.select_match_ranges(
14601 next_selected_range,
14602 last_selection.reversed,
14603 action.replace_newest,
14604 Some(Autoscroll::newest()),
14605 window,
14606 cx,
14607 );
14608 } else {
14609 select_prev_state.done = true;
14610 }
14611 }
14612
14613 self.select_prev_state = Some(select_prev_state);
14614 } else {
14615 let mut only_carets = true;
14616 let mut same_text_selected = true;
14617 let mut selected_text = None;
14618
14619 let mut selections_iter = selections.iter().peekable();
14620 while let Some(selection) = selections_iter.next() {
14621 if selection.start != selection.end {
14622 only_carets = false;
14623 }
14624
14625 if same_text_selected {
14626 if selected_text.is_none() {
14627 selected_text =
14628 Some(buffer.text_for_range(selection.range()).collect::<String>());
14629 }
14630
14631 if let Some(next_selection) = selections_iter.peek() {
14632 if next_selection.range().len() == selection.range().len() {
14633 let next_selected_text = buffer
14634 .text_for_range(next_selection.range())
14635 .collect::<String>();
14636 if Some(next_selected_text) != selected_text {
14637 same_text_selected = false;
14638 selected_text = None;
14639 }
14640 } else {
14641 same_text_selected = false;
14642 selected_text = None;
14643 }
14644 }
14645 }
14646 }
14647
14648 if only_carets {
14649 for selection in &mut selections {
14650 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14651 selection.start = word_range.start;
14652 selection.end = word_range.end;
14653 selection.goal = SelectionGoal::None;
14654 selection.reversed = false;
14655 self.select_match_ranges(
14656 selection.start..selection.end,
14657 selection.reversed,
14658 action.replace_newest,
14659 Some(Autoscroll::newest()),
14660 window,
14661 cx,
14662 );
14663 }
14664 if selections.len() == 1 {
14665 let selection = selections
14666 .last()
14667 .expect("ensured that there's only one selection");
14668 let query = buffer
14669 .text_for_range(selection.start..selection.end)
14670 .collect::<String>();
14671 let is_empty = query.is_empty();
14672 let select_state = SelectNextState {
14673 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14674 wordwise: true,
14675 done: is_empty,
14676 };
14677 self.select_prev_state = Some(select_state);
14678 } else {
14679 self.select_prev_state = None;
14680 }
14681 } else if let Some(selected_text) = selected_text {
14682 self.select_prev_state = Some(SelectNextState {
14683 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14684 wordwise: false,
14685 done: false,
14686 });
14687 self.select_previous(action, window, cx)?;
14688 }
14689 }
14690 Ok(())
14691 }
14692
14693 pub fn find_next_match(
14694 &mut self,
14695 _: &FindNextMatch,
14696 window: &mut Window,
14697 cx: &mut Context<Self>,
14698 ) -> Result<()> {
14699 let selections = self.selections.disjoint_anchors_arc();
14700 match selections.first() {
14701 Some(first) if selections.len() >= 2 => {
14702 self.change_selections(Default::default(), window, cx, |s| {
14703 s.select_ranges([first.range()]);
14704 });
14705 }
14706 _ => self.select_next(
14707 &SelectNext {
14708 replace_newest: true,
14709 },
14710 window,
14711 cx,
14712 )?,
14713 }
14714 Ok(())
14715 }
14716
14717 pub fn find_previous_match(
14718 &mut self,
14719 _: &FindPreviousMatch,
14720 window: &mut Window,
14721 cx: &mut Context<Self>,
14722 ) -> Result<()> {
14723 let selections = self.selections.disjoint_anchors_arc();
14724 match selections.last() {
14725 Some(last) if selections.len() >= 2 => {
14726 self.change_selections(Default::default(), window, cx, |s| {
14727 s.select_ranges([last.range()]);
14728 });
14729 }
14730 _ => self.select_previous(
14731 &SelectPrevious {
14732 replace_newest: true,
14733 },
14734 window,
14735 cx,
14736 )?,
14737 }
14738 Ok(())
14739 }
14740
14741 pub fn toggle_comments(
14742 &mut self,
14743 action: &ToggleComments,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) {
14747 if self.read_only(cx) {
14748 return;
14749 }
14750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14751 let text_layout_details = &self.text_layout_details(window);
14752 self.transact(window, cx, |this, window, cx| {
14753 let mut selections = this
14754 .selections
14755 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14756 let mut edits = Vec::new();
14757 let mut selection_edit_ranges = Vec::new();
14758 let mut last_toggled_row = None;
14759 let snapshot = this.buffer.read(cx).read(cx);
14760 let empty_str: Arc<str> = Arc::default();
14761 let mut suffixes_inserted = Vec::new();
14762 let ignore_indent = action.ignore_indent;
14763
14764 fn comment_prefix_range(
14765 snapshot: &MultiBufferSnapshot,
14766 row: MultiBufferRow,
14767 comment_prefix: &str,
14768 comment_prefix_whitespace: &str,
14769 ignore_indent: bool,
14770 ) -> Range<Point> {
14771 let indent_size = if ignore_indent {
14772 0
14773 } else {
14774 snapshot.indent_size_for_line(row).len
14775 };
14776
14777 let start = Point::new(row.0, indent_size);
14778
14779 let mut line_bytes = snapshot
14780 .bytes_in_range(start..snapshot.max_point())
14781 .flatten()
14782 .copied();
14783
14784 // If this line currently begins with the line comment prefix, then record
14785 // the range containing the prefix.
14786 if line_bytes
14787 .by_ref()
14788 .take(comment_prefix.len())
14789 .eq(comment_prefix.bytes())
14790 {
14791 // Include any whitespace that matches the comment prefix.
14792 let matching_whitespace_len = line_bytes
14793 .zip(comment_prefix_whitespace.bytes())
14794 .take_while(|(a, b)| a == b)
14795 .count() as u32;
14796 let end = Point::new(
14797 start.row,
14798 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14799 );
14800 start..end
14801 } else {
14802 start..start
14803 }
14804 }
14805
14806 fn comment_suffix_range(
14807 snapshot: &MultiBufferSnapshot,
14808 row: MultiBufferRow,
14809 comment_suffix: &str,
14810 comment_suffix_has_leading_space: bool,
14811 ) -> Range<Point> {
14812 let end = Point::new(row.0, snapshot.line_len(row));
14813 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14814
14815 let mut line_end_bytes = snapshot
14816 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14817 .flatten()
14818 .copied();
14819
14820 let leading_space_len = if suffix_start_column > 0
14821 && line_end_bytes.next() == Some(b' ')
14822 && comment_suffix_has_leading_space
14823 {
14824 1
14825 } else {
14826 0
14827 };
14828
14829 // If this line currently begins with the line comment prefix, then record
14830 // the range containing the prefix.
14831 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14832 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14833 start..end
14834 } else {
14835 end..end
14836 }
14837 }
14838
14839 // TODO: Handle selections that cross excerpts
14840 for selection in &mut selections {
14841 let start_column = snapshot
14842 .indent_size_for_line(MultiBufferRow(selection.start.row))
14843 .len;
14844 let language = if let Some(language) =
14845 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14846 {
14847 language
14848 } else {
14849 continue;
14850 };
14851
14852 selection_edit_ranges.clear();
14853
14854 // If multiple selections contain a given row, avoid processing that
14855 // row more than once.
14856 let mut start_row = MultiBufferRow(selection.start.row);
14857 if last_toggled_row == Some(start_row) {
14858 start_row = start_row.next_row();
14859 }
14860 let end_row =
14861 if selection.end.row > selection.start.row && selection.end.column == 0 {
14862 MultiBufferRow(selection.end.row - 1)
14863 } else {
14864 MultiBufferRow(selection.end.row)
14865 };
14866 last_toggled_row = Some(end_row);
14867
14868 if start_row > end_row {
14869 continue;
14870 }
14871
14872 // If the language has line comments, toggle those.
14873 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14874
14875 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14876 if ignore_indent {
14877 full_comment_prefixes = full_comment_prefixes
14878 .into_iter()
14879 .map(|s| Arc::from(s.trim_end()))
14880 .collect();
14881 }
14882
14883 if !full_comment_prefixes.is_empty() {
14884 let first_prefix = full_comment_prefixes
14885 .first()
14886 .expect("prefixes is non-empty");
14887 let prefix_trimmed_lengths = full_comment_prefixes
14888 .iter()
14889 .map(|p| p.trim_end_matches(' ').len())
14890 .collect::<SmallVec<[usize; 4]>>();
14891
14892 let mut all_selection_lines_are_comments = true;
14893
14894 for row in start_row.0..=end_row.0 {
14895 let row = MultiBufferRow(row);
14896 if start_row < end_row && snapshot.is_line_blank(row) {
14897 continue;
14898 }
14899
14900 let prefix_range = full_comment_prefixes
14901 .iter()
14902 .zip(prefix_trimmed_lengths.iter().copied())
14903 .map(|(prefix, trimmed_prefix_len)| {
14904 comment_prefix_range(
14905 snapshot.deref(),
14906 row,
14907 &prefix[..trimmed_prefix_len],
14908 &prefix[trimmed_prefix_len..],
14909 ignore_indent,
14910 )
14911 })
14912 .max_by_key(|range| range.end.column - range.start.column)
14913 .expect("prefixes is non-empty");
14914
14915 if prefix_range.is_empty() {
14916 all_selection_lines_are_comments = false;
14917 }
14918
14919 selection_edit_ranges.push(prefix_range);
14920 }
14921
14922 if all_selection_lines_are_comments {
14923 edits.extend(
14924 selection_edit_ranges
14925 .iter()
14926 .cloned()
14927 .map(|range| (range, empty_str.clone())),
14928 );
14929 } else {
14930 let min_column = selection_edit_ranges
14931 .iter()
14932 .map(|range| range.start.column)
14933 .min()
14934 .unwrap_or(0);
14935 edits.extend(selection_edit_ranges.iter().map(|range| {
14936 let position = Point::new(range.start.row, min_column);
14937 (position..position, first_prefix.clone())
14938 }));
14939 }
14940 } else if let Some(BlockCommentConfig {
14941 start: full_comment_prefix,
14942 end: comment_suffix,
14943 ..
14944 }) = language.block_comment()
14945 {
14946 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14947 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14948 let prefix_range = comment_prefix_range(
14949 snapshot.deref(),
14950 start_row,
14951 comment_prefix,
14952 comment_prefix_whitespace,
14953 ignore_indent,
14954 );
14955 let suffix_range = comment_suffix_range(
14956 snapshot.deref(),
14957 end_row,
14958 comment_suffix.trim_start_matches(' '),
14959 comment_suffix.starts_with(' '),
14960 );
14961
14962 if prefix_range.is_empty() || suffix_range.is_empty() {
14963 edits.push((
14964 prefix_range.start..prefix_range.start,
14965 full_comment_prefix.clone(),
14966 ));
14967 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14968 suffixes_inserted.push((end_row, comment_suffix.len()));
14969 } else {
14970 edits.push((prefix_range, empty_str.clone()));
14971 edits.push((suffix_range, empty_str.clone()));
14972 }
14973 } else {
14974 continue;
14975 }
14976 }
14977
14978 drop(snapshot);
14979 this.buffer.update(cx, |buffer, cx| {
14980 buffer.edit(edits, None, cx);
14981 });
14982
14983 // Adjust selections so that they end before any comment suffixes that
14984 // were inserted.
14985 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14986 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
14987 let snapshot = this.buffer.read(cx).read(cx);
14988 for selection in &mut selections {
14989 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14990 match row.cmp(&MultiBufferRow(selection.end.row)) {
14991 Ordering::Less => {
14992 suffixes_inserted.next();
14993 continue;
14994 }
14995 Ordering::Greater => break,
14996 Ordering::Equal => {
14997 if selection.end.column == snapshot.line_len(row) {
14998 if selection.is_empty() {
14999 selection.start.column -= suffix_len as u32;
15000 }
15001 selection.end.column -= suffix_len as u32;
15002 }
15003 break;
15004 }
15005 }
15006 }
15007 }
15008
15009 drop(snapshot);
15010 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15011
15012 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15013 let selections_on_single_row = selections.windows(2).all(|selections| {
15014 selections[0].start.row == selections[1].start.row
15015 && selections[0].end.row == selections[1].end.row
15016 && selections[0].start.row == selections[0].end.row
15017 });
15018 let selections_selecting = selections
15019 .iter()
15020 .any(|selection| selection.start != selection.end);
15021 let advance_downwards = action.advance_downwards
15022 && selections_on_single_row
15023 && !selections_selecting
15024 && !matches!(this.mode, EditorMode::SingleLine);
15025
15026 if advance_downwards {
15027 let snapshot = this.buffer.read(cx).snapshot(cx);
15028
15029 this.change_selections(Default::default(), window, cx, |s| {
15030 s.move_cursors_with(|display_snapshot, display_point, _| {
15031 let mut point = display_point.to_point(display_snapshot);
15032 point.row += 1;
15033 point = snapshot.clip_point(point, Bias::Left);
15034 let display_point = point.to_display_point(display_snapshot);
15035 let goal = SelectionGoal::HorizontalPosition(
15036 display_snapshot
15037 .x_for_display_point(display_point, text_layout_details)
15038 .into(),
15039 );
15040 (display_point, goal)
15041 })
15042 });
15043 }
15044 });
15045 }
15046
15047 pub fn select_enclosing_symbol(
15048 &mut self,
15049 _: &SelectEnclosingSymbol,
15050 window: &mut Window,
15051 cx: &mut Context<Self>,
15052 ) {
15053 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15054
15055 let buffer = self.buffer.read(cx).snapshot(cx);
15056 let old_selections = self
15057 .selections
15058 .all::<usize>(&self.display_snapshot(cx))
15059 .into_boxed_slice();
15060
15061 fn update_selection(
15062 selection: &Selection<usize>,
15063 buffer_snap: &MultiBufferSnapshot,
15064 ) -> Option<Selection<usize>> {
15065 let cursor = selection.head();
15066 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15067 for symbol in symbols.iter().rev() {
15068 let start = symbol.range.start.to_offset(buffer_snap);
15069 let end = symbol.range.end.to_offset(buffer_snap);
15070 let new_range = start..end;
15071 if start < selection.start || end > selection.end {
15072 return Some(Selection {
15073 id: selection.id,
15074 start: new_range.start,
15075 end: new_range.end,
15076 goal: SelectionGoal::None,
15077 reversed: selection.reversed,
15078 });
15079 }
15080 }
15081 None
15082 }
15083
15084 let mut selected_larger_symbol = false;
15085 let new_selections = old_selections
15086 .iter()
15087 .map(|selection| match update_selection(selection, &buffer) {
15088 Some(new_selection) => {
15089 if new_selection.range() != selection.range() {
15090 selected_larger_symbol = true;
15091 }
15092 new_selection
15093 }
15094 None => selection.clone(),
15095 })
15096 .collect::<Vec<_>>();
15097
15098 if selected_larger_symbol {
15099 self.change_selections(Default::default(), window, cx, |s| {
15100 s.select(new_selections);
15101 });
15102 }
15103 }
15104
15105 pub fn select_larger_syntax_node(
15106 &mut self,
15107 _: &SelectLargerSyntaxNode,
15108 window: &mut Window,
15109 cx: &mut Context<Self>,
15110 ) {
15111 let Some(visible_row_count) = self.visible_row_count() else {
15112 return;
15113 };
15114 let old_selections: Box<[_]> = self
15115 .selections
15116 .all::<usize>(&self.display_snapshot(cx))
15117 .into();
15118 if old_selections.is_empty() {
15119 return;
15120 }
15121
15122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15123
15124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15125 let buffer = self.buffer.read(cx).snapshot(cx);
15126
15127 let mut selected_larger_node = false;
15128 let mut new_selections = old_selections
15129 .iter()
15130 .map(|selection| {
15131 let old_range = selection.start..selection.end;
15132
15133 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15134 // manually select word at selection
15135 if ["string_content", "inline"].contains(&node.kind()) {
15136 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15137 // ignore if word is already selected
15138 if !word_range.is_empty() && old_range != word_range {
15139 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15140 // only select word if start and end point belongs to same word
15141 if word_range == last_word_range {
15142 selected_larger_node = true;
15143 return Selection {
15144 id: selection.id,
15145 start: word_range.start,
15146 end: word_range.end,
15147 goal: SelectionGoal::None,
15148 reversed: selection.reversed,
15149 };
15150 }
15151 }
15152 }
15153 }
15154
15155 let mut new_range = old_range.clone();
15156 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15157 new_range = range;
15158 if !node.is_named() {
15159 continue;
15160 }
15161 if !display_map.intersects_fold(new_range.start)
15162 && !display_map.intersects_fold(new_range.end)
15163 {
15164 break;
15165 }
15166 }
15167
15168 selected_larger_node |= new_range != old_range;
15169 Selection {
15170 id: selection.id,
15171 start: new_range.start,
15172 end: new_range.end,
15173 goal: SelectionGoal::None,
15174 reversed: selection.reversed,
15175 }
15176 })
15177 .collect::<Vec<_>>();
15178
15179 if !selected_larger_node {
15180 return; // don't put this call in the history
15181 }
15182
15183 // scroll based on transformation done to the last selection created by the user
15184 let (last_old, last_new) = old_selections
15185 .last()
15186 .zip(new_selections.last().cloned())
15187 .expect("old_selections isn't empty");
15188
15189 // revert selection
15190 let is_selection_reversed = {
15191 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15192 new_selections.last_mut().expect("checked above").reversed =
15193 should_newest_selection_be_reversed;
15194 should_newest_selection_be_reversed
15195 };
15196
15197 if selected_larger_node {
15198 self.select_syntax_node_history.disable_clearing = true;
15199 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15200 s.select(new_selections.clone());
15201 });
15202 self.select_syntax_node_history.disable_clearing = false;
15203 }
15204
15205 let start_row = last_new.start.to_display_point(&display_map).row().0;
15206 let end_row = last_new.end.to_display_point(&display_map).row().0;
15207 let selection_height = end_row - start_row + 1;
15208 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15209
15210 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15211 let scroll_behavior = if fits_on_the_screen {
15212 self.request_autoscroll(Autoscroll::fit(), cx);
15213 SelectSyntaxNodeScrollBehavior::FitSelection
15214 } else if is_selection_reversed {
15215 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15216 SelectSyntaxNodeScrollBehavior::CursorTop
15217 } else {
15218 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15219 SelectSyntaxNodeScrollBehavior::CursorBottom
15220 };
15221
15222 self.select_syntax_node_history.push((
15223 old_selections,
15224 scroll_behavior,
15225 is_selection_reversed,
15226 ));
15227 }
15228
15229 pub fn select_smaller_syntax_node(
15230 &mut self,
15231 _: &SelectSmallerSyntaxNode,
15232 window: &mut Window,
15233 cx: &mut Context<Self>,
15234 ) {
15235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15236
15237 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15238 self.select_syntax_node_history.pop()
15239 {
15240 if let Some(selection) = selections.last_mut() {
15241 selection.reversed = is_selection_reversed;
15242 }
15243
15244 self.select_syntax_node_history.disable_clearing = true;
15245 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15246 s.select(selections.to_vec());
15247 });
15248 self.select_syntax_node_history.disable_clearing = false;
15249
15250 match scroll_behavior {
15251 SelectSyntaxNodeScrollBehavior::CursorTop => {
15252 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15253 }
15254 SelectSyntaxNodeScrollBehavior::FitSelection => {
15255 self.request_autoscroll(Autoscroll::fit(), cx);
15256 }
15257 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15258 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15259 }
15260 }
15261 }
15262 }
15263
15264 pub fn unwrap_syntax_node(
15265 &mut self,
15266 _: &UnwrapSyntaxNode,
15267 window: &mut Window,
15268 cx: &mut Context<Self>,
15269 ) {
15270 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15271
15272 let buffer = self.buffer.read(cx).snapshot(cx);
15273 let selections = self
15274 .selections
15275 .all::<usize>(&self.display_snapshot(cx))
15276 .into_iter()
15277 // subtracting the offset requires sorting
15278 .sorted_by_key(|i| i.start);
15279
15280 let full_edits = selections
15281 .into_iter()
15282 .filter_map(|selection| {
15283 let child = if selection.is_empty()
15284 && let Some((_, ancestor_range)) =
15285 buffer.syntax_ancestor(selection.start..selection.end)
15286 {
15287 ancestor_range
15288 } else {
15289 selection.range()
15290 };
15291
15292 let mut parent = child.clone();
15293 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15294 parent = ancestor_range;
15295 if parent.start < child.start || parent.end > child.end {
15296 break;
15297 }
15298 }
15299
15300 if parent == child {
15301 return None;
15302 }
15303 let text = buffer.text_for_range(child).collect::<String>();
15304 Some((selection.id, parent, text))
15305 })
15306 .collect::<Vec<_>>();
15307 if full_edits.is_empty() {
15308 return;
15309 }
15310
15311 self.transact(window, cx, |this, window, cx| {
15312 this.buffer.update(cx, |buffer, cx| {
15313 buffer.edit(
15314 full_edits
15315 .iter()
15316 .map(|(_, p, t)| (p.clone(), t.clone()))
15317 .collect::<Vec<_>>(),
15318 None,
15319 cx,
15320 );
15321 });
15322 this.change_selections(Default::default(), window, cx, |s| {
15323 let mut offset = 0;
15324 let mut selections = vec![];
15325 for (id, parent, text) in full_edits {
15326 let start = parent.start - offset;
15327 offset += parent.len() - text.len();
15328 selections.push(Selection {
15329 id,
15330 start,
15331 end: start + text.len(),
15332 reversed: false,
15333 goal: Default::default(),
15334 });
15335 }
15336 s.select(selections);
15337 });
15338 });
15339 }
15340
15341 pub fn select_next_syntax_node(
15342 &mut self,
15343 _: &SelectNextSyntaxNode,
15344 window: &mut Window,
15345 cx: &mut Context<Self>,
15346 ) {
15347 let old_selections: Box<[_]> = self
15348 .selections
15349 .all::<usize>(&self.display_snapshot(cx))
15350 .into();
15351 if old_selections.is_empty() {
15352 return;
15353 }
15354
15355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15356
15357 let buffer = self.buffer.read(cx).snapshot(cx);
15358 let mut selected_sibling = false;
15359
15360 let new_selections = old_selections
15361 .iter()
15362 .map(|selection| {
15363 let old_range = selection.start..selection.end;
15364
15365 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15366 let new_range = node.byte_range();
15367 selected_sibling = true;
15368 Selection {
15369 id: selection.id,
15370 start: new_range.start,
15371 end: new_range.end,
15372 goal: SelectionGoal::None,
15373 reversed: selection.reversed,
15374 }
15375 } else {
15376 selection.clone()
15377 }
15378 })
15379 .collect::<Vec<_>>();
15380
15381 if selected_sibling {
15382 self.change_selections(
15383 SelectionEffects::scroll(Autoscroll::fit()),
15384 window,
15385 cx,
15386 |s| {
15387 s.select(new_selections);
15388 },
15389 );
15390 }
15391 }
15392
15393 pub fn select_prev_syntax_node(
15394 &mut self,
15395 _: &SelectPreviousSyntaxNode,
15396 window: &mut Window,
15397 cx: &mut Context<Self>,
15398 ) {
15399 let old_selections: Box<[_]> = self
15400 .selections
15401 .all::<usize>(&self.display_snapshot(cx))
15402 .into();
15403 if old_selections.is_empty() {
15404 return;
15405 }
15406
15407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15408
15409 let buffer = self.buffer.read(cx).snapshot(cx);
15410 let mut selected_sibling = false;
15411
15412 let new_selections = old_selections
15413 .iter()
15414 .map(|selection| {
15415 let old_range = selection.start..selection.end;
15416
15417 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15418 let new_range = node.byte_range();
15419 selected_sibling = true;
15420 Selection {
15421 id: selection.id,
15422 start: new_range.start,
15423 end: new_range.end,
15424 goal: SelectionGoal::None,
15425 reversed: selection.reversed,
15426 }
15427 } else {
15428 selection.clone()
15429 }
15430 })
15431 .collect::<Vec<_>>();
15432
15433 if selected_sibling {
15434 self.change_selections(
15435 SelectionEffects::scroll(Autoscroll::fit()),
15436 window,
15437 cx,
15438 |s| {
15439 s.select(new_selections);
15440 },
15441 );
15442 }
15443 }
15444
15445 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15446 if !EditorSettings::get_global(cx).gutter.runnables {
15447 self.clear_tasks();
15448 return Task::ready(());
15449 }
15450 let project = self.project().map(Entity::downgrade);
15451 let task_sources = self.lsp_task_sources(cx);
15452 let multi_buffer = self.buffer.downgrade();
15453 cx.spawn_in(window, async move |editor, cx| {
15454 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15455 let Some(project) = project.and_then(|p| p.upgrade()) else {
15456 return;
15457 };
15458 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15459 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15460 }) else {
15461 return;
15462 };
15463
15464 let hide_runnables = project
15465 .update(cx, |project, _| project.is_via_collab())
15466 .unwrap_or(true);
15467 if hide_runnables {
15468 return;
15469 }
15470 let new_rows =
15471 cx.background_spawn({
15472 let snapshot = display_snapshot.clone();
15473 async move {
15474 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15475 }
15476 })
15477 .await;
15478 let Ok(lsp_tasks) =
15479 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15480 else {
15481 return;
15482 };
15483 let lsp_tasks = lsp_tasks.await;
15484
15485 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15486 lsp_tasks
15487 .into_iter()
15488 .flat_map(|(kind, tasks)| {
15489 tasks.into_iter().filter_map(move |(location, task)| {
15490 Some((kind.clone(), location?, task))
15491 })
15492 })
15493 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15494 let buffer = location.target.buffer;
15495 let buffer_snapshot = buffer.read(cx).snapshot();
15496 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15497 |(excerpt_id, snapshot, _)| {
15498 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15499 display_snapshot
15500 .buffer_snapshot()
15501 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15502 } else {
15503 None
15504 }
15505 },
15506 );
15507 if let Some(offset) = offset {
15508 let task_buffer_range =
15509 location.target.range.to_point(&buffer_snapshot);
15510 let context_buffer_range =
15511 task_buffer_range.to_offset(&buffer_snapshot);
15512 let context_range = BufferOffset(context_buffer_range.start)
15513 ..BufferOffset(context_buffer_range.end);
15514
15515 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15516 .or_insert_with(|| RunnableTasks {
15517 templates: Vec::new(),
15518 offset,
15519 column: task_buffer_range.start.column,
15520 extra_variables: HashMap::default(),
15521 context_range,
15522 })
15523 .templates
15524 .push((kind, task.original_task().clone()));
15525 }
15526
15527 acc
15528 })
15529 }) else {
15530 return;
15531 };
15532
15533 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15534 buffer.language_settings(cx).tasks.prefer_lsp
15535 }) else {
15536 return;
15537 };
15538
15539 let rows = Self::runnable_rows(
15540 project,
15541 display_snapshot,
15542 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15543 new_rows,
15544 cx.clone(),
15545 )
15546 .await;
15547 editor
15548 .update(cx, |editor, _| {
15549 editor.clear_tasks();
15550 for (key, mut value) in rows {
15551 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15552 value.templates.extend(lsp_tasks.templates);
15553 }
15554
15555 editor.insert_tasks(key, value);
15556 }
15557 for (key, value) in lsp_tasks_by_rows {
15558 editor.insert_tasks(key, value);
15559 }
15560 })
15561 .ok();
15562 })
15563 }
15564 fn fetch_runnable_ranges(
15565 snapshot: &DisplaySnapshot,
15566 range: Range<Anchor>,
15567 ) -> Vec<language::RunnableRange> {
15568 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15569 }
15570
15571 fn runnable_rows(
15572 project: Entity<Project>,
15573 snapshot: DisplaySnapshot,
15574 prefer_lsp: bool,
15575 runnable_ranges: Vec<RunnableRange>,
15576 cx: AsyncWindowContext,
15577 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15578 cx.spawn(async move |cx| {
15579 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15580 for mut runnable in runnable_ranges {
15581 let Some(tasks) = cx
15582 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15583 .ok()
15584 else {
15585 continue;
15586 };
15587 let mut tasks = tasks.await;
15588
15589 if prefer_lsp {
15590 tasks.retain(|(task_kind, _)| {
15591 !matches!(task_kind, TaskSourceKind::Language { .. })
15592 });
15593 }
15594 if tasks.is_empty() {
15595 continue;
15596 }
15597
15598 let point = runnable
15599 .run_range
15600 .start
15601 .to_point(&snapshot.buffer_snapshot());
15602 let Some(row) = snapshot
15603 .buffer_snapshot()
15604 .buffer_line_for_row(MultiBufferRow(point.row))
15605 .map(|(_, range)| range.start.row)
15606 else {
15607 continue;
15608 };
15609
15610 let context_range =
15611 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15612 runnable_rows.push((
15613 (runnable.buffer_id, row),
15614 RunnableTasks {
15615 templates: tasks,
15616 offset: snapshot
15617 .buffer_snapshot()
15618 .anchor_before(runnable.run_range.start),
15619 context_range,
15620 column: point.column,
15621 extra_variables: runnable.extra_captures,
15622 },
15623 ));
15624 }
15625 runnable_rows
15626 })
15627 }
15628
15629 fn templates_with_tags(
15630 project: &Entity<Project>,
15631 runnable: &mut Runnable,
15632 cx: &mut App,
15633 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15634 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15635 let (worktree_id, file) = project
15636 .buffer_for_id(runnable.buffer, cx)
15637 .and_then(|buffer| buffer.read(cx).file())
15638 .map(|file| (file.worktree_id(cx), file.clone()))
15639 .unzip();
15640
15641 (
15642 project.task_store().read(cx).task_inventory().cloned(),
15643 worktree_id,
15644 file,
15645 )
15646 });
15647
15648 let tags = mem::take(&mut runnable.tags);
15649 let language = runnable.language.clone();
15650 cx.spawn(async move |cx| {
15651 let mut templates_with_tags = Vec::new();
15652 if let Some(inventory) = inventory {
15653 for RunnableTag(tag) in tags {
15654 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15655 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15656 }) else {
15657 return templates_with_tags;
15658 };
15659 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15660 move |(_, template)| {
15661 template.tags.iter().any(|source_tag| source_tag == &tag)
15662 },
15663 ));
15664 }
15665 }
15666 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15667
15668 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15669 // Strongest source wins; if we have worktree tag binding, prefer that to
15670 // global and language bindings;
15671 // if we have a global binding, prefer that to language binding.
15672 let first_mismatch = templates_with_tags
15673 .iter()
15674 .position(|(tag_source, _)| tag_source != leading_tag_source);
15675 if let Some(index) = first_mismatch {
15676 templates_with_tags.truncate(index);
15677 }
15678 }
15679
15680 templates_with_tags
15681 })
15682 }
15683
15684 pub fn move_to_enclosing_bracket(
15685 &mut self,
15686 _: &MoveToEnclosingBracket,
15687 window: &mut Window,
15688 cx: &mut Context<Self>,
15689 ) {
15690 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15691 self.change_selections(Default::default(), window, cx, |s| {
15692 s.move_offsets_with(|snapshot, selection| {
15693 let Some(enclosing_bracket_ranges) =
15694 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15695 else {
15696 return;
15697 };
15698
15699 let mut best_length = usize::MAX;
15700 let mut best_inside = false;
15701 let mut best_in_bracket_range = false;
15702 let mut best_destination = None;
15703 for (open, close) in enclosing_bracket_ranges {
15704 let close = close.to_inclusive();
15705 let length = close.end() - open.start;
15706 let inside = selection.start >= open.end && selection.end <= *close.start();
15707 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15708 || close.contains(&selection.head());
15709
15710 // If best is next to a bracket and current isn't, skip
15711 if !in_bracket_range && best_in_bracket_range {
15712 continue;
15713 }
15714
15715 // Prefer smaller lengths unless best is inside and current isn't
15716 if length > best_length && (best_inside || !inside) {
15717 continue;
15718 }
15719
15720 best_length = length;
15721 best_inside = inside;
15722 best_in_bracket_range = in_bracket_range;
15723 best_destination = Some(
15724 if close.contains(&selection.start) && close.contains(&selection.end) {
15725 if inside { open.end } else { open.start }
15726 } else if inside {
15727 *close.start()
15728 } else {
15729 *close.end()
15730 },
15731 );
15732 }
15733
15734 if let Some(destination) = best_destination {
15735 selection.collapse_to(destination, SelectionGoal::None);
15736 }
15737 })
15738 });
15739 }
15740
15741 pub fn undo_selection(
15742 &mut self,
15743 _: &UndoSelection,
15744 window: &mut Window,
15745 cx: &mut Context<Self>,
15746 ) {
15747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15748 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15749 self.selection_history.mode = SelectionHistoryMode::Undoing;
15750 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15751 this.end_selection(window, cx);
15752 this.change_selections(
15753 SelectionEffects::scroll(Autoscroll::newest()),
15754 window,
15755 cx,
15756 |s| s.select_anchors(entry.selections.to_vec()),
15757 );
15758 });
15759 self.selection_history.mode = SelectionHistoryMode::Normal;
15760
15761 self.select_next_state = entry.select_next_state;
15762 self.select_prev_state = entry.select_prev_state;
15763 self.add_selections_state = entry.add_selections_state;
15764 }
15765 }
15766
15767 pub fn redo_selection(
15768 &mut self,
15769 _: &RedoSelection,
15770 window: &mut Window,
15771 cx: &mut Context<Self>,
15772 ) {
15773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15774 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15775 self.selection_history.mode = SelectionHistoryMode::Redoing;
15776 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15777 this.end_selection(window, cx);
15778 this.change_selections(
15779 SelectionEffects::scroll(Autoscroll::newest()),
15780 window,
15781 cx,
15782 |s| s.select_anchors(entry.selections.to_vec()),
15783 );
15784 });
15785 self.selection_history.mode = SelectionHistoryMode::Normal;
15786
15787 self.select_next_state = entry.select_next_state;
15788 self.select_prev_state = entry.select_prev_state;
15789 self.add_selections_state = entry.add_selections_state;
15790 }
15791 }
15792
15793 pub fn expand_excerpts(
15794 &mut self,
15795 action: &ExpandExcerpts,
15796 _: &mut Window,
15797 cx: &mut Context<Self>,
15798 ) {
15799 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15800 }
15801
15802 pub fn expand_excerpts_down(
15803 &mut self,
15804 action: &ExpandExcerptsDown,
15805 _: &mut Window,
15806 cx: &mut Context<Self>,
15807 ) {
15808 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15809 }
15810
15811 pub fn expand_excerpts_up(
15812 &mut self,
15813 action: &ExpandExcerptsUp,
15814 _: &mut Window,
15815 cx: &mut Context<Self>,
15816 ) {
15817 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15818 }
15819
15820 pub fn expand_excerpts_for_direction(
15821 &mut self,
15822 lines: u32,
15823 direction: ExpandExcerptDirection,
15824
15825 cx: &mut Context<Self>,
15826 ) {
15827 let selections = self.selections.disjoint_anchors_arc();
15828
15829 let lines = if lines == 0 {
15830 EditorSettings::get_global(cx).expand_excerpt_lines
15831 } else {
15832 lines
15833 };
15834
15835 self.buffer.update(cx, |buffer, cx| {
15836 let snapshot = buffer.snapshot(cx);
15837 let mut excerpt_ids = selections
15838 .iter()
15839 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15840 .collect::<Vec<_>>();
15841 excerpt_ids.sort();
15842 excerpt_ids.dedup();
15843 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15844 })
15845 }
15846
15847 pub fn expand_excerpt(
15848 &mut self,
15849 excerpt: ExcerptId,
15850 direction: ExpandExcerptDirection,
15851 window: &mut Window,
15852 cx: &mut Context<Self>,
15853 ) {
15854 let current_scroll_position = self.scroll_position(cx);
15855 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15856 let mut should_scroll_up = false;
15857
15858 if direction == ExpandExcerptDirection::Down {
15859 let multi_buffer = self.buffer.read(cx);
15860 let snapshot = multi_buffer.snapshot(cx);
15861 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15862 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15863 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15864 {
15865 let buffer_snapshot = buffer.read(cx).snapshot();
15866 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15867 let last_row = buffer_snapshot.max_point().row;
15868 let lines_below = last_row.saturating_sub(excerpt_end_row);
15869 should_scroll_up = lines_below >= lines_to_expand;
15870 }
15871 }
15872
15873 self.buffer.update(cx, |buffer, cx| {
15874 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15875 });
15876
15877 if should_scroll_up {
15878 let new_scroll_position =
15879 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15880 self.set_scroll_position(new_scroll_position, window, cx);
15881 }
15882 }
15883
15884 pub fn go_to_singleton_buffer_point(
15885 &mut self,
15886 point: Point,
15887 window: &mut Window,
15888 cx: &mut Context<Self>,
15889 ) {
15890 self.go_to_singleton_buffer_range(point..point, window, cx);
15891 }
15892
15893 pub fn go_to_singleton_buffer_range(
15894 &mut self,
15895 range: Range<Point>,
15896 window: &mut Window,
15897 cx: &mut Context<Self>,
15898 ) {
15899 let multibuffer = self.buffer().read(cx);
15900 let Some(buffer) = multibuffer.as_singleton() else {
15901 return;
15902 };
15903 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15904 return;
15905 };
15906 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15907 return;
15908 };
15909 self.change_selections(
15910 SelectionEffects::default().nav_history(true),
15911 window,
15912 cx,
15913 |s| s.select_anchor_ranges([start..end]),
15914 );
15915 }
15916
15917 pub fn go_to_diagnostic(
15918 &mut self,
15919 action: &GoToDiagnostic,
15920 window: &mut Window,
15921 cx: &mut Context<Self>,
15922 ) {
15923 if !self.diagnostics_enabled() {
15924 return;
15925 }
15926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15927 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15928 }
15929
15930 pub fn go_to_prev_diagnostic(
15931 &mut self,
15932 action: &GoToPreviousDiagnostic,
15933 window: &mut Window,
15934 cx: &mut Context<Self>,
15935 ) {
15936 if !self.diagnostics_enabled() {
15937 return;
15938 }
15939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15940 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15941 }
15942
15943 pub fn go_to_diagnostic_impl(
15944 &mut self,
15945 direction: Direction,
15946 severity: GoToDiagnosticSeverityFilter,
15947 window: &mut Window,
15948 cx: &mut Context<Self>,
15949 ) {
15950 let buffer = self.buffer.read(cx).snapshot(cx);
15951 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
15952
15953 let mut active_group_id = None;
15954 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15955 && active_group.active_range.start.to_offset(&buffer) == selection.start
15956 {
15957 active_group_id = Some(active_group.group_id);
15958 }
15959
15960 fn filtered<'a>(
15961 snapshot: EditorSnapshot,
15962 severity: GoToDiagnosticSeverityFilter,
15963 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15964 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15965 diagnostics
15966 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15967 .filter(|entry| entry.range.start != entry.range.end)
15968 .filter(|entry| !entry.diagnostic.is_unnecessary)
15969 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15970 }
15971
15972 let snapshot = self.snapshot(window, cx);
15973 let before = filtered(
15974 snapshot.clone(),
15975 severity,
15976 buffer
15977 .diagnostics_in_range(0..selection.start)
15978 .filter(|entry| entry.range.start <= selection.start),
15979 );
15980 let after = filtered(
15981 snapshot,
15982 severity,
15983 buffer
15984 .diagnostics_in_range(selection.start..buffer.len())
15985 .filter(|entry| entry.range.start >= selection.start),
15986 );
15987
15988 let mut found: Option<DiagnosticEntryRef<usize>> = None;
15989 if direction == Direction::Prev {
15990 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15991 {
15992 for diagnostic in prev_diagnostics.into_iter().rev() {
15993 if diagnostic.range.start != selection.start
15994 || active_group_id
15995 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15996 {
15997 found = Some(diagnostic);
15998 break 'outer;
15999 }
16000 }
16001 }
16002 } else {
16003 for diagnostic in after.chain(before) {
16004 if diagnostic.range.start != selection.start
16005 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16006 {
16007 found = Some(diagnostic);
16008 break;
16009 }
16010 }
16011 }
16012 let Some(next_diagnostic) = found else {
16013 return;
16014 };
16015
16016 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16017 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16018 return;
16019 };
16020 self.change_selections(Default::default(), window, cx, |s| {
16021 s.select_ranges(vec![
16022 next_diagnostic.range.start..next_diagnostic.range.start,
16023 ])
16024 });
16025 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16026 self.refresh_edit_prediction(false, true, window, cx);
16027 }
16028
16029 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16031 let snapshot = self.snapshot(window, cx);
16032 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16033 self.go_to_hunk_before_or_after_position(
16034 &snapshot,
16035 selection.head(),
16036 Direction::Next,
16037 window,
16038 cx,
16039 );
16040 }
16041
16042 pub fn go_to_hunk_before_or_after_position(
16043 &mut self,
16044 snapshot: &EditorSnapshot,
16045 position: Point,
16046 direction: Direction,
16047 window: &mut Window,
16048 cx: &mut Context<Editor>,
16049 ) {
16050 let row = if direction == Direction::Next {
16051 self.hunk_after_position(snapshot, position)
16052 .map(|hunk| hunk.row_range.start)
16053 } else {
16054 self.hunk_before_position(snapshot, position)
16055 };
16056
16057 if let Some(row) = row {
16058 let destination = Point::new(row.0, 0);
16059 let autoscroll = Autoscroll::center();
16060
16061 self.unfold_ranges(&[destination..destination], false, false, cx);
16062 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16063 s.select_ranges([destination..destination]);
16064 });
16065 }
16066 }
16067
16068 fn hunk_after_position(
16069 &mut self,
16070 snapshot: &EditorSnapshot,
16071 position: Point,
16072 ) -> Option<MultiBufferDiffHunk> {
16073 snapshot
16074 .buffer_snapshot()
16075 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16076 .find(|hunk| hunk.row_range.start.0 > position.row)
16077 .or_else(|| {
16078 snapshot
16079 .buffer_snapshot()
16080 .diff_hunks_in_range(Point::zero()..position)
16081 .find(|hunk| hunk.row_range.end.0 < position.row)
16082 })
16083 }
16084
16085 fn go_to_prev_hunk(
16086 &mut self,
16087 _: &GoToPreviousHunk,
16088 window: &mut Window,
16089 cx: &mut Context<Self>,
16090 ) {
16091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16092 let snapshot = self.snapshot(window, cx);
16093 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16094 self.go_to_hunk_before_or_after_position(
16095 &snapshot,
16096 selection.head(),
16097 Direction::Prev,
16098 window,
16099 cx,
16100 );
16101 }
16102
16103 fn hunk_before_position(
16104 &mut self,
16105 snapshot: &EditorSnapshot,
16106 position: Point,
16107 ) -> Option<MultiBufferRow> {
16108 snapshot
16109 .buffer_snapshot()
16110 .diff_hunk_before(position)
16111 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16112 }
16113
16114 fn go_to_next_change(
16115 &mut self,
16116 _: &GoToNextChange,
16117 window: &mut Window,
16118 cx: &mut Context<Self>,
16119 ) {
16120 if let Some(selections) = self
16121 .change_list
16122 .next_change(1, Direction::Next)
16123 .map(|s| s.to_vec())
16124 {
16125 self.change_selections(Default::default(), window, cx, |s| {
16126 let map = s.display_map();
16127 s.select_display_ranges(selections.iter().map(|a| {
16128 let point = a.to_display_point(&map);
16129 point..point
16130 }))
16131 })
16132 }
16133 }
16134
16135 fn go_to_previous_change(
16136 &mut self,
16137 _: &GoToPreviousChange,
16138 window: &mut Window,
16139 cx: &mut Context<Self>,
16140 ) {
16141 if let Some(selections) = self
16142 .change_list
16143 .next_change(1, Direction::Prev)
16144 .map(|s| s.to_vec())
16145 {
16146 self.change_selections(Default::default(), window, cx, |s| {
16147 let map = s.display_map();
16148 s.select_display_ranges(selections.iter().map(|a| {
16149 let point = a.to_display_point(&map);
16150 point..point
16151 }))
16152 })
16153 }
16154 }
16155
16156 pub fn go_to_next_document_highlight(
16157 &mut self,
16158 _: &GoToNextDocumentHighlight,
16159 window: &mut Window,
16160 cx: &mut Context<Self>,
16161 ) {
16162 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16163 }
16164
16165 pub fn go_to_prev_document_highlight(
16166 &mut self,
16167 _: &GoToPreviousDocumentHighlight,
16168 window: &mut Window,
16169 cx: &mut Context<Self>,
16170 ) {
16171 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16172 }
16173
16174 pub fn go_to_document_highlight_before_or_after_position(
16175 &mut self,
16176 direction: Direction,
16177 window: &mut Window,
16178 cx: &mut Context<Editor>,
16179 ) {
16180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16181 let snapshot = self.snapshot(window, cx);
16182 let buffer = &snapshot.buffer_snapshot();
16183 let position = self
16184 .selections
16185 .newest::<Point>(&snapshot.display_snapshot)
16186 .head();
16187 let anchor_position = buffer.anchor_after(position);
16188
16189 // Get all document highlights (both read and write)
16190 let mut all_highlights = Vec::new();
16191
16192 if let Some((_, read_highlights)) = self
16193 .background_highlights
16194 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16195 {
16196 all_highlights.extend(read_highlights.iter());
16197 }
16198
16199 if let Some((_, write_highlights)) = self
16200 .background_highlights
16201 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16202 {
16203 all_highlights.extend(write_highlights.iter());
16204 }
16205
16206 if all_highlights.is_empty() {
16207 return;
16208 }
16209
16210 // Sort highlights by position
16211 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16212
16213 let target_highlight = match direction {
16214 Direction::Next => {
16215 // Find the first highlight after the current position
16216 all_highlights
16217 .iter()
16218 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16219 }
16220 Direction::Prev => {
16221 // Find the last highlight before the current position
16222 all_highlights
16223 .iter()
16224 .rev()
16225 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16226 }
16227 };
16228
16229 if let Some(highlight) = target_highlight {
16230 let destination = highlight.start.to_point(buffer);
16231 let autoscroll = Autoscroll::center();
16232
16233 self.unfold_ranges(&[destination..destination], false, false, cx);
16234 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16235 s.select_ranges([destination..destination]);
16236 });
16237 }
16238 }
16239
16240 fn go_to_line<T: 'static>(
16241 &mut self,
16242 position: Anchor,
16243 highlight_color: Option<Hsla>,
16244 window: &mut Window,
16245 cx: &mut Context<Self>,
16246 ) {
16247 let snapshot = self.snapshot(window, cx).display_snapshot;
16248 let position = position.to_point(&snapshot.buffer_snapshot());
16249 let start = snapshot
16250 .buffer_snapshot()
16251 .clip_point(Point::new(position.row, 0), Bias::Left);
16252 let end = start + Point::new(1, 0);
16253 let start = snapshot.buffer_snapshot().anchor_before(start);
16254 let end = snapshot.buffer_snapshot().anchor_before(end);
16255
16256 self.highlight_rows::<T>(
16257 start..end,
16258 highlight_color
16259 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16260 Default::default(),
16261 cx,
16262 );
16263
16264 if self.buffer.read(cx).is_singleton() {
16265 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16266 }
16267 }
16268
16269 pub fn go_to_definition(
16270 &mut self,
16271 _: &GoToDefinition,
16272 window: &mut Window,
16273 cx: &mut Context<Self>,
16274 ) -> Task<Result<Navigated>> {
16275 let definition =
16276 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16277 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16278 cx.spawn_in(window, async move |editor, cx| {
16279 if definition.await? == Navigated::Yes {
16280 return Ok(Navigated::Yes);
16281 }
16282 match fallback_strategy {
16283 GoToDefinitionFallback::None => Ok(Navigated::No),
16284 GoToDefinitionFallback::FindAllReferences => {
16285 match editor.update_in(cx, |editor, window, cx| {
16286 editor.find_all_references(&FindAllReferences, window, cx)
16287 })? {
16288 Some(references) => references.await,
16289 None => Ok(Navigated::No),
16290 }
16291 }
16292 }
16293 })
16294 }
16295
16296 pub fn go_to_declaration(
16297 &mut self,
16298 _: &GoToDeclaration,
16299 window: &mut Window,
16300 cx: &mut Context<Self>,
16301 ) -> Task<Result<Navigated>> {
16302 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16303 }
16304
16305 pub fn go_to_declaration_split(
16306 &mut self,
16307 _: &GoToDeclaration,
16308 window: &mut Window,
16309 cx: &mut Context<Self>,
16310 ) -> Task<Result<Navigated>> {
16311 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16312 }
16313
16314 pub fn go_to_implementation(
16315 &mut self,
16316 _: &GoToImplementation,
16317 window: &mut Window,
16318 cx: &mut Context<Self>,
16319 ) -> Task<Result<Navigated>> {
16320 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16321 }
16322
16323 pub fn go_to_implementation_split(
16324 &mut self,
16325 _: &GoToImplementationSplit,
16326 window: &mut Window,
16327 cx: &mut Context<Self>,
16328 ) -> Task<Result<Navigated>> {
16329 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16330 }
16331
16332 pub fn go_to_type_definition(
16333 &mut self,
16334 _: &GoToTypeDefinition,
16335 window: &mut Window,
16336 cx: &mut Context<Self>,
16337 ) -> Task<Result<Navigated>> {
16338 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16339 }
16340
16341 pub fn go_to_definition_split(
16342 &mut self,
16343 _: &GoToDefinitionSplit,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) -> Task<Result<Navigated>> {
16347 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16348 }
16349
16350 pub fn go_to_type_definition_split(
16351 &mut self,
16352 _: &GoToTypeDefinitionSplit,
16353 window: &mut Window,
16354 cx: &mut Context<Self>,
16355 ) -> Task<Result<Navigated>> {
16356 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16357 }
16358
16359 fn go_to_definition_of_kind(
16360 &mut self,
16361 kind: GotoDefinitionKind,
16362 split: bool,
16363 window: &mut Window,
16364 cx: &mut Context<Self>,
16365 ) -> Task<Result<Navigated>> {
16366 let Some(provider) = self.semantics_provider.clone() else {
16367 return Task::ready(Ok(Navigated::No));
16368 };
16369 let head = self
16370 .selections
16371 .newest::<usize>(&self.display_snapshot(cx))
16372 .head();
16373 let buffer = self.buffer.read(cx);
16374 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16375 return Task::ready(Ok(Navigated::No));
16376 };
16377 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16378 return Task::ready(Ok(Navigated::No));
16379 };
16380
16381 cx.spawn_in(window, async move |editor, cx| {
16382 let Some(definitions) = definitions.await? else {
16383 return Ok(Navigated::No);
16384 };
16385 let navigated = editor
16386 .update_in(cx, |editor, window, cx| {
16387 editor.navigate_to_hover_links(
16388 Some(kind),
16389 definitions
16390 .into_iter()
16391 .filter(|location| {
16392 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16393 })
16394 .map(HoverLink::Text)
16395 .collect::<Vec<_>>(),
16396 split,
16397 window,
16398 cx,
16399 )
16400 })?
16401 .await?;
16402 anyhow::Ok(navigated)
16403 })
16404 }
16405
16406 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16407 let selection = self.selections.newest_anchor();
16408 let head = selection.head();
16409 let tail = selection.tail();
16410
16411 let Some((buffer, start_position)) =
16412 self.buffer.read(cx).text_anchor_for_position(head, cx)
16413 else {
16414 return;
16415 };
16416
16417 let end_position = if head != tail {
16418 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16419 return;
16420 };
16421 Some(pos)
16422 } else {
16423 None
16424 };
16425
16426 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16427 let url = if let Some(end_pos) = end_position {
16428 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16429 } else {
16430 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16431 };
16432
16433 if let Some(url) = url {
16434 cx.update(|window, cx| {
16435 if parse_zed_link(&url, cx).is_some() {
16436 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16437 } else {
16438 cx.open_url(&url);
16439 }
16440 })?;
16441 }
16442
16443 anyhow::Ok(())
16444 });
16445
16446 url_finder.detach();
16447 }
16448
16449 pub fn open_selected_filename(
16450 &mut self,
16451 _: &OpenSelectedFilename,
16452 window: &mut Window,
16453 cx: &mut Context<Self>,
16454 ) {
16455 let Some(workspace) = self.workspace() else {
16456 return;
16457 };
16458
16459 let position = self.selections.newest_anchor().head();
16460
16461 let Some((buffer, buffer_position)) =
16462 self.buffer.read(cx).text_anchor_for_position(position, cx)
16463 else {
16464 return;
16465 };
16466
16467 let project = self.project.clone();
16468
16469 cx.spawn_in(window, async move |_, cx| {
16470 let result = find_file(&buffer, project, buffer_position, cx).await;
16471
16472 if let Some((_, path)) = result {
16473 workspace
16474 .update_in(cx, |workspace, window, cx| {
16475 workspace.open_resolved_path(path, window, cx)
16476 })?
16477 .await?;
16478 }
16479 anyhow::Ok(())
16480 })
16481 .detach();
16482 }
16483
16484 pub(crate) fn navigate_to_hover_links(
16485 &mut self,
16486 kind: Option<GotoDefinitionKind>,
16487 definitions: Vec<HoverLink>,
16488 split: bool,
16489 window: &mut Window,
16490 cx: &mut Context<Editor>,
16491 ) -> Task<Result<Navigated>> {
16492 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16493 let mut first_url_or_file = None;
16494 let definitions: Vec<_> = definitions
16495 .into_iter()
16496 .filter_map(|def| match def {
16497 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16498 HoverLink::InlayHint(lsp_location, server_id) => {
16499 let computation =
16500 self.compute_target_location(lsp_location, server_id, window, cx);
16501 Some(cx.background_spawn(computation))
16502 }
16503 HoverLink::Url(url) => {
16504 first_url_or_file = Some(Either::Left(url));
16505 None
16506 }
16507 HoverLink::File(path) => {
16508 first_url_or_file = Some(Either::Right(path));
16509 None
16510 }
16511 })
16512 .collect();
16513
16514 let workspace = self.workspace();
16515
16516 cx.spawn_in(window, async move |editor, cx| {
16517 let locations: Vec<Location> = future::join_all(definitions)
16518 .await
16519 .into_iter()
16520 .filter_map(|location| location.transpose())
16521 .collect::<Result<_>>()
16522 .context("location tasks")?;
16523 let mut locations = cx.update(|_, cx| {
16524 locations
16525 .into_iter()
16526 .map(|location| {
16527 let buffer = location.buffer.read(cx);
16528 (location.buffer, location.range.to_point(buffer))
16529 })
16530 .into_group_map()
16531 })?;
16532 let mut num_locations = 0;
16533 for ranges in locations.values_mut() {
16534 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16535 ranges.dedup();
16536 num_locations += ranges.len();
16537 }
16538
16539 if num_locations > 1 {
16540 let Some(workspace) = workspace else {
16541 return Ok(Navigated::No);
16542 };
16543
16544 let tab_kind = match kind {
16545 Some(GotoDefinitionKind::Implementation) => "Implementations",
16546 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16547 Some(GotoDefinitionKind::Declaration) => "Declarations",
16548 Some(GotoDefinitionKind::Type) => "Types",
16549 };
16550 let title = editor
16551 .update_in(cx, |_, _, cx| {
16552 let target = locations
16553 .iter()
16554 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16555 .map(|(buffer, location)| {
16556 buffer
16557 .read(cx)
16558 .text_for_range(location.clone())
16559 .collect::<String>()
16560 })
16561 .filter(|text| !text.contains('\n'))
16562 .unique()
16563 .take(3)
16564 .join(", ");
16565 if target.is_empty() {
16566 tab_kind.to_owned()
16567 } else {
16568 format!("{tab_kind} for {target}")
16569 }
16570 })
16571 .context("buffer title")?;
16572
16573 let opened = workspace
16574 .update_in(cx, |workspace, window, cx| {
16575 Self::open_locations_in_multibuffer(
16576 workspace,
16577 locations,
16578 title,
16579 split,
16580 MultibufferSelectionMode::First,
16581 window,
16582 cx,
16583 )
16584 })
16585 .is_ok();
16586
16587 anyhow::Ok(Navigated::from_bool(opened))
16588 } else if num_locations == 0 {
16589 // If there is one url or file, open it directly
16590 match first_url_or_file {
16591 Some(Either::Left(url)) => {
16592 cx.update(|_, cx| cx.open_url(&url))?;
16593 Ok(Navigated::Yes)
16594 }
16595 Some(Either::Right(path)) => {
16596 let Some(workspace) = workspace else {
16597 return Ok(Navigated::No);
16598 };
16599
16600 workspace
16601 .update_in(cx, |workspace, window, cx| {
16602 workspace.open_resolved_path(path, window, cx)
16603 })?
16604 .await?;
16605 Ok(Navigated::Yes)
16606 }
16607 None => Ok(Navigated::No),
16608 }
16609 } else {
16610 let Some(workspace) = workspace else {
16611 return Ok(Navigated::No);
16612 };
16613
16614 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16615 let target_range = target_ranges.first().unwrap().clone();
16616
16617 editor.update_in(cx, |editor, window, cx| {
16618 let range = target_range.to_point(target_buffer.read(cx));
16619 let range = editor.range_for_match(&range);
16620 let range = collapse_multiline_range(range);
16621
16622 if !split
16623 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16624 {
16625 editor.go_to_singleton_buffer_range(range, window, cx);
16626 } else {
16627 let pane = workspace.read(cx).active_pane().clone();
16628 window.defer(cx, move |window, cx| {
16629 let target_editor: Entity<Self> =
16630 workspace.update(cx, |workspace, cx| {
16631 let pane = if split {
16632 workspace.adjacent_pane(window, cx)
16633 } else {
16634 workspace.active_pane().clone()
16635 };
16636
16637 workspace.open_project_item(
16638 pane,
16639 target_buffer.clone(),
16640 true,
16641 true,
16642 window,
16643 cx,
16644 )
16645 });
16646 target_editor.update(cx, |target_editor, cx| {
16647 // When selecting a definition in a different buffer, disable the nav history
16648 // to avoid creating a history entry at the previous cursor location.
16649 pane.update(cx, |pane, _| pane.disable_history());
16650 target_editor.go_to_singleton_buffer_range(range, window, cx);
16651 pane.update(cx, |pane, _| pane.enable_history());
16652 });
16653 });
16654 }
16655 Navigated::Yes
16656 })
16657 }
16658 })
16659 }
16660
16661 fn compute_target_location(
16662 &self,
16663 lsp_location: lsp::Location,
16664 server_id: LanguageServerId,
16665 window: &mut Window,
16666 cx: &mut Context<Self>,
16667 ) -> Task<anyhow::Result<Option<Location>>> {
16668 let Some(project) = self.project.clone() else {
16669 return Task::ready(Ok(None));
16670 };
16671
16672 cx.spawn_in(window, async move |editor, cx| {
16673 let location_task = editor.update(cx, |_, cx| {
16674 project.update(cx, |project, cx| {
16675 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16676 })
16677 })?;
16678 let location = Some({
16679 let target_buffer_handle = location_task.await.context("open local buffer")?;
16680 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16681 let target_start = target_buffer
16682 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16683 let target_end = target_buffer
16684 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16685 target_buffer.anchor_after(target_start)
16686 ..target_buffer.anchor_before(target_end)
16687 })?;
16688 Location {
16689 buffer: target_buffer_handle,
16690 range,
16691 }
16692 });
16693 Ok(location)
16694 })
16695 }
16696
16697 pub fn find_all_references(
16698 &mut self,
16699 _: &FindAllReferences,
16700 window: &mut Window,
16701 cx: &mut Context<Self>,
16702 ) -> Option<Task<Result<Navigated>>> {
16703 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16704 let multi_buffer = self.buffer.read(cx);
16705 let head = selection.head();
16706
16707 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16708 let head_anchor = multi_buffer_snapshot.anchor_at(
16709 head,
16710 if head < selection.tail() {
16711 Bias::Right
16712 } else {
16713 Bias::Left
16714 },
16715 );
16716
16717 match self
16718 .find_all_references_task_sources
16719 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16720 {
16721 Ok(_) => {
16722 log::info!(
16723 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16724 );
16725 return None;
16726 }
16727 Err(i) => {
16728 self.find_all_references_task_sources.insert(i, head_anchor);
16729 }
16730 }
16731
16732 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16733 let workspace = self.workspace()?;
16734 let project = workspace.read(cx).project().clone();
16735 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16736 Some(cx.spawn_in(window, async move |editor, cx| {
16737 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16738 if let Ok(i) = editor
16739 .find_all_references_task_sources
16740 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16741 {
16742 editor.find_all_references_task_sources.remove(i);
16743 }
16744 });
16745
16746 let Some(locations) = references.await? else {
16747 return anyhow::Ok(Navigated::No);
16748 };
16749 let mut locations = cx.update(|_, cx| {
16750 locations
16751 .into_iter()
16752 .map(|location| {
16753 let buffer = location.buffer.read(cx);
16754 (location.buffer, location.range.to_point(buffer))
16755 })
16756 .into_group_map()
16757 })?;
16758 if locations.is_empty() {
16759 return anyhow::Ok(Navigated::No);
16760 }
16761 for ranges in locations.values_mut() {
16762 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16763 ranges.dedup();
16764 }
16765
16766 workspace.update_in(cx, |workspace, window, cx| {
16767 let target = locations
16768 .iter()
16769 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16770 .map(|(buffer, location)| {
16771 buffer
16772 .read(cx)
16773 .text_for_range(location.clone())
16774 .collect::<String>()
16775 })
16776 .filter(|text| !text.contains('\n'))
16777 .unique()
16778 .take(3)
16779 .join(", ");
16780 let title = if target.is_empty() {
16781 "References".to_owned()
16782 } else {
16783 format!("References to {target}")
16784 };
16785 Self::open_locations_in_multibuffer(
16786 workspace,
16787 locations,
16788 title,
16789 false,
16790 MultibufferSelectionMode::First,
16791 window,
16792 cx,
16793 );
16794 Navigated::Yes
16795 })
16796 }))
16797 }
16798
16799 /// Opens a multibuffer with the given project locations in it
16800 pub fn open_locations_in_multibuffer(
16801 workspace: &mut Workspace,
16802 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16803 title: String,
16804 split: bool,
16805 multibuffer_selection_mode: MultibufferSelectionMode,
16806 window: &mut Window,
16807 cx: &mut Context<Workspace>,
16808 ) {
16809 if locations.is_empty() {
16810 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16811 return;
16812 }
16813
16814 let capability = workspace.project().read(cx).capability();
16815 let mut ranges = <Vec<Range<Anchor>>>::new();
16816
16817 // a key to find existing multibuffer editors with the same set of locations
16818 // to prevent us from opening more and more multibuffer tabs for searches and the like
16819 let mut key = (title.clone(), vec![]);
16820 let excerpt_buffer = cx.new(|cx| {
16821 let key = &mut key.1;
16822 let mut multibuffer = MultiBuffer::new(capability);
16823 for (buffer, mut ranges_for_buffer) in locations {
16824 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16825 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16826 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16827 PathKey::for_buffer(&buffer, cx),
16828 buffer.clone(),
16829 ranges_for_buffer,
16830 multibuffer_context_lines(cx),
16831 cx,
16832 );
16833 ranges.extend(new_ranges)
16834 }
16835
16836 multibuffer.with_title(title)
16837 });
16838 let existing = workspace.active_pane().update(cx, |pane, cx| {
16839 pane.items()
16840 .filter_map(|item| item.downcast::<Editor>())
16841 .find(|editor| {
16842 editor
16843 .read(cx)
16844 .lookup_key
16845 .as_ref()
16846 .and_then(|it| {
16847 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16848 })
16849 .is_some_and(|it| *it == key)
16850 })
16851 });
16852 let editor = existing.unwrap_or_else(|| {
16853 cx.new(|cx| {
16854 let mut editor = Editor::for_multibuffer(
16855 excerpt_buffer,
16856 Some(workspace.project().clone()),
16857 window,
16858 cx,
16859 );
16860 editor.lookup_key = Some(Box::new(key));
16861 editor
16862 })
16863 });
16864 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
16865 MultibufferSelectionMode::First => {
16866 if let Some(first_range) = ranges.first() {
16867 editor.change_selections(
16868 SelectionEffects::no_scroll(),
16869 window,
16870 cx,
16871 |selections| {
16872 selections.clear_disjoint();
16873 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
16874 },
16875 );
16876 }
16877 editor.highlight_background::<Self>(
16878 &ranges,
16879 |theme| theme.colors().editor_highlighted_line_background,
16880 cx,
16881 );
16882 }
16883 MultibufferSelectionMode::All => {
16884 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16885 selections.clear_disjoint();
16886 selections.select_anchor_ranges(ranges);
16887 });
16888 }
16889 });
16890
16891 let item = Box::new(editor);
16892 let item_id = item.item_id();
16893
16894 if split {
16895 let pane = workspace.adjacent_pane(window, cx);
16896 workspace.add_item(pane, item, None, true, true, window, cx);
16897 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16898 let (preview_item_id, preview_item_idx) =
16899 workspace.active_pane().read_with(cx, |pane, _| {
16900 (pane.preview_item_id(), pane.preview_item_idx())
16901 });
16902
16903 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16904
16905 if let Some(preview_item_id) = preview_item_id {
16906 workspace.active_pane().update(cx, |pane, cx| {
16907 pane.remove_item(preview_item_id, false, false, window, cx);
16908 });
16909 }
16910 } else {
16911 workspace.add_item_to_active_pane(item, None, true, window, cx);
16912 }
16913 workspace.active_pane().update(cx, |pane, cx| {
16914 pane.set_preview_item_id(Some(item_id), cx);
16915 });
16916 }
16917
16918 pub fn rename(
16919 &mut self,
16920 _: &Rename,
16921 window: &mut Window,
16922 cx: &mut Context<Self>,
16923 ) -> Option<Task<Result<()>>> {
16924 use language::ToOffset as _;
16925
16926 let provider = self.semantics_provider.clone()?;
16927 let selection = self.selections.newest_anchor().clone();
16928 let (cursor_buffer, cursor_buffer_position) = self
16929 .buffer
16930 .read(cx)
16931 .text_anchor_for_position(selection.head(), cx)?;
16932 let (tail_buffer, cursor_buffer_position_end) = self
16933 .buffer
16934 .read(cx)
16935 .text_anchor_for_position(selection.tail(), cx)?;
16936 if tail_buffer != cursor_buffer {
16937 return None;
16938 }
16939
16940 let snapshot = cursor_buffer.read(cx).snapshot();
16941 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16942 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16943 let prepare_rename = provider
16944 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16945 .unwrap_or_else(|| Task::ready(Ok(None)));
16946 drop(snapshot);
16947
16948 Some(cx.spawn_in(window, async move |this, cx| {
16949 let rename_range = if let Some(range) = prepare_rename.await? {
16950 Some(range)
16951 } else {
16952 this.update(cx, |this, cx| {
16953 let buffer = this.buffer.read(cx).snapshot(cx);
16954 let mut buffer_highlights = this
16955 .document_highlights_for_position(selection.head(), &buffer)
16956 .filter(|highlight| {
16957 highlight.start.excerpt_id == selection.head().excerpt_id
16958 && highlight.end.excerpt_id == selection.head().excerpt_id
16959 });
16960 buffer_highlights
16961 .next()
16962 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16963 })?
16964 };
16965 if let Some(rename_range) = rename_range {
16966 this.update_in(cx, |this, window, cx| {
16967 let snapshot = cursor_buffer.read(cx).snapshot();
16968 let rename_buffer_range = rename_range.to_offset(&snapshot);
16969 let cursor_offset_in_rename_range =
16970 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16971 let cursor_offset_in_rename_range_end =
16972 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16973
16974 this.take_rename(false, window, cx);
16975 let buffer = this.buffer.read(cx).read(cx);
16976 let cursor_offset = selection.head().to_offset(&buffer);
16977 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16978 let rename_end = rename_start + rename_buffer_range.len();
16979 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16980 let mut old_highlight_id = None;
16981 let old_name: Arc<str> = buffer
16982 .chunks(rename_start..rename_end, true)
16983 .map(|chunk| {
16984 if old_highlight_id.is_none() {
16985 old_highlight_id = chunk.syntax_highlight_id;
16986 }
16987 chunk.text
16988 })
16989 .collect::<String>()
16990 .into();
16991
16992 drop(buffer);
16993
16994 // Position the selection in the rename editor so that it matches the current selection.
16995 this.show_local_selections = false;
16996 let rename_editor = cx.new(|cx| {
16997 let mut editor = Editor::single_line(window, cx);
16998 editor.buffer.update(cx, |buffer, cx| {
16999 buffer.edit([(0..0, old_name.clone())], None, cx)
17000 });
17001 let rename_selection_range = match cursor_offset_in_rename_range
17002 .cmp(&cursor_offset_in_rename_range_end)
17003 {
17004 Ordering::Equal => {
17005 editor.select_all(&SelectAll, window, cx);
17006 return editor;
17007 }
17008 Ordering::Less => {
17009 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17010 }
17011 Ordering::Greater => {
17012 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17013 }
17014 };
17015 if rename_selection_range.end > old_name.len() {
17016 editor.select_all(&SelectAll, window, cx);
17017 } else {
17018 editor.change_selections(Default::default(), window, cx, |s| {
17019 s.select_ranges([rename_selection_range]);
17020 });
17021 }
17022 editor
17023 });
17024 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17025 if e == &EditorEvent::Focused {
17026 cx.emit(EditorEvent::FocusedIn)
17027 }
17028 })
17029 .detach();
17030
17031 let write_highlights =
17032 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17033 let read_highlights =
17034 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17035 let ranges = write_highlights
17036 .iter()
17037 .flat_map(|(_, ranges)| ranges.iter())
17038 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17039 .cloned()
17040 .collect();
17041
17042 this.highlight_text::<Rename>(
17043 ranges,
17044 HighlightStyle {
17045 fade_out: Some(0.6),
17046 ..Default::default()
17047 },
17048 cx,
17049 );
17050 let rename_focus_handle = rename_editor.focus_handle(cx);
17051 window.focus(&rename_focus_handle);
17052 let block_id = this.insert_blocks(
17053 [BlockProperties {
17054 style: BlockStyle::Flex,
17055 placement: BlockPlacement::Below(range.start),
17056 height: Some(1),
17057 render: Arc::new({
17058 let rename_editor = rename_editor.clone();
17059 move |cx: &mut BlockContext| {
17060 let mut text_style = cx.editor_style.text.clone();
17061 if let Some(highlight_style) = old_highlight_id
17062 .and_then(|h| h.style(&cx.editor_style.syntax))
17063 {
17064 text_style = text_style.highlight(highlight_style);
17065 }
17066 div()
17067 .block_mouse_except_scroll()
17068 .pl(cx.anchor_x)
17069 .child(EditorElement::new(
17070 &rename_editor,
17071 EditorStyle {
17072 background: cx.theme().system().transparent,
17073 local_player: cx.editor_style.local_player,
17074 text: text_style,
17075 scrollbar_width: cx.editor_style.scrollbar_width,
17076 syntax: cx.editor_style.syntax.clone(),
17077 status: cx.editor_style.status.clone(),
17078 inlay_hints_style: HighlightStyle {
17079 font_weight: Some(FontWeight::BOLD),
17080 ..make_inlay_hints_style(cx.app)
17081 },
17082 edit_prediction_styles: make_suggestion_styles(
17083 cx.app,
17084 ),
17085 ..EditorStyle::default()
17086 },
17087 ))
17088 .into_any_element()
17089 }
17090 }),
17091 priority: 0,
17092 }],
17093 Some(Autoscroll::fit()),
17094 cx,
17095 )[0];
17096 this.pending_rename = Some(RenameState {
17097 range,
17098 old_name,
17099 editor: rename_editor,
17100 block_id,
17101 });
17102 })?;
17103 }
17104
17105 Ok(())
17106 }))
17107 }
17108
17109 pub fn confirm_rename(
17110 &mut self,
17111 _: &ConfirmRename,
17112 window: &mut Window,
17113 cx: &mut Context<Self>,
17114 ) -> Option<Task<Result<()>>> {
17115 let rename = self.take_rename(false, window, cx)?;
17116 let workspace = self.workspace()?.downgrade();
17117 let (buffer, start) = self
17118 .buffer
17119 .read(cx)
17120 .text_anchor_for_position(rename.range.start, cx)?;
17121 let (end_buffer, _) = self
17122 .buffer
17123 .read(cx)
17124 .text_anchor_for_position(rename.range.end, cx)?;
17125 if buffer != end_buffer {
17126 return None;
17127 }
17128
17129 let old_name = rename.old_name;
17130 let new_name = rename.editor.read(cx).text(cx);
17131
17132 let rename = self.semantics_provider.as_ref()?.perform_rename(
17133 &buffer,
17134 start,
17135 new_name.clone(),
17136 cx,
17137 )?;
17138
17139 Some(cx.spawn_in(window, async move |editor, cx| {
17140 let project_transaction = rename.await?;
17141 Self::open_project_transaction(
17142 &editor,
17143 workspace,
17144 project_transaction,
17145 format!("Rename: {} → {}", old_name, new_name),
17146 cx,
17147 )
17148 .await?;
17149
17150 editor.update(cx, |editor, cx| {
17151 editor.refresh_document_highlights(cx);
17152 })?;
17153 Ok(())
17154 }))
17155 }
17156
17157 fn take_rename(
17158 &mut self,
17159 moving_cursor: bool,
17160 window: &mut Window,
17161 cx: &mut Context<Self>,
17162 ) -> Option<RenameState> {
17163 let rename = self.pending_rename.take()?;
17164 if rename.editor.focus_handle(cx).is_focused(window) {
17165 window.focus(&self.focus_handle);
17166 }
17167
17168 self.remove_blocks(
17169 [rename.block_id].into_iter().collect(),
17170 Some(Autoscroll::fit()),
17171 cx,
17172 );
17173 self.clear_highlights::<Rename>(cx);
17174 self.show_local_selections = true;
17175
17176 if moving_cursor {
17177 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17178 editor
17179 .selections
17180 .newest::<usize>(&editor.display_snapshot(cx))
17181 .head()
17182 });
17183
17184 // Update the selection to match the position of the selection inside
17185 // the rename editor.
17186 let snapshot = self.buffer.read(cx).read(cx);
17187 let rename_range = rename.range.to_offset(&snapshot);
17188 let cursor_in_editor = snapshot
17189 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17190 .min(rename_range.end);
17191 drop(snapshot);
17192
17193 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17194 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17195 });
17196 } else {
17197 self.refresh_document_highlights(cx);
17198 }
17199
17200 Some(rename)
17201 }
17202
17203 pub fn pending_rename(&self) -> Option<&RenameState> {
17204 self.pending_rename.as_ref()
17205 }
17206
17207 fn format(
17208 &mut self,
17209 _: &Format,
17210 window: &mut Window,
17211 cx: &mut Context<Self>,
17212 ) -> Option<Task<Result<()>>> {
17213 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17214
17215 let project = match &self.project {
17216 Some(project) => project.clone(),
17217 None => return None,
17218 };
17219
17220 Some(self.perform_format(
17221 project,
17222 FormatTrigger::Manual,
17223 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17224 window,
17225 cx,
17226 ))
17227 }
17228
17229 fn format_selections(
17230 &mut self,
17231 _: &FormatSelections,
17232 window: &mut Window,
17233 cx: &mut Context<Self>,
17234 ) -> Option<Task<Result<()>>> {
17235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17236
17237 let project = match &self.project {
17238 Some(project) => project.clone(),
17239 None => return None,
17240 };
17241
17242 let ranges = self
17243 .selections
17244 .all_adjusted(&self.display_snapshot(cx))
17245 .into_iter()
17246 .map(|selection| selection.range())
17247 .collect_vec();
17248
17249 Some(self.perform_format(
17250 project,
17251 FormatTrigger::Manual,
17252 FormatTarget::Ranges(ranges),
17253 window,
17254 cx,
17255 ))
17256 }
17257
17258 fn perform_format(
17259 &mut self,
17260 project: Entity<Project>,
17261 trigger: FormatTrigger,
17262 target: FormatTarget,
17263 window: &mut Window,
17264 cx: &mut Context<Self>,
17265 ) -> Task<Result<()>> {
17266 let buffer = self.buffer.clone();
17267 let (buffers, target) = match target {
17268 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17269 FormatTarget::Ranges(selection_ranges) => {
17270 let multi_buffer = buffer.read(cx);
17271 let snapshot = multi_buffer.read(cx);
17272 let mut buffers = HashSet::default();
17273 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17274 BTreeMap::new();
17275 for selection_range in selection_ranges {
17276 for (buffer, buffer_range, _) in
17277 snapshot.range_to_buffer_ranges(selection_range)
17278 {
17279 let buffer_id = buffer.remote_id();
17280 let start = buffer.anchor_before(buffer_range.start);
17281 let end = buffer.anchor_after(buffer_range.end);
17282 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17283 buffer_id_to_ranges
17284 .entry(buffer_id)
17285 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17286 .or_insert_with(|| vec![start..end]);
17287 }
17288 }
17289 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17290 }
17291 };
17292
17293 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17294 let selections_prev = transaction_id_prev
17295 .and_then(|transaction_id_prev| {
17296 // default to selections as they were after the last edit, if we have them,
17297 // instead of how they are now.
17298 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17299 // will take you back to where you made the last edit, instead of staying where you scrolled
17300 self.selection_history
17301 .transaction(transaction_id_prev)
17302 .map(|t| t.0.clone())
17303 })
17304 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17305
17306 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17307 let format = project.update(cx, |project, cx| {
17308 project.format(buffers, target, true, trigger, cx)
17309 });
17310
17311 cx.spawn_in(window, async move |editor, cx| {
17312 let transaction = futures::select_biased! {
17313 transaction = format.log_err().fuse() => transaction,
17314 () = timeout => {
17315 log::warn!("timed out waiting for formatting");
17316 None
17317 }
17318 };
17319
17320 buffer
17321 .update(cx, |buffer, cx| {
17322 if let Some(transaction) = transaction
17323 && !buffer.is_singleton()
17324 {
17325 buffer.push_transaction(&transaction.0, cx);
17326 }
17327 cx.notify();
17328 })
17329 .ok();
17330
17331 if let Some(transaction_id_now) =
17332 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17333 {
17334 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17335 if has_new_transaction {
17336 _ = editor.update(cx, |editor, _| {
17337 editor
17338 .selection_history
17339 .insert_transaction(transaction_id_now, selections_prev);
17340 });
17341 }
17342 }
17343
17344 Ok(())
17345 })
17346 }
17347
17348 fn organize_imports(
17349 &mut self,
17350 _: &OrganizeImports,
17351 window: &mut Window,
17352 cx: &mut Context<Self>,
17353 ) -> Option<Task<Result<()>>> {
17354 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17355 let project = match &self.project {
17356 Some(project) => project.clone(),
17357 None => return None,
17358 };
17359 Some(self.perform_code_action_kind(
17360 project,
17361 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17362 window,
17363 cx,
17364 ))
17365 }
17366
17367 fn perform_code_action_kind(
17368 &mut self,
17369 project: Entity<Project>,
17370 kind: CodeActionKind,
17371 window: &mut Window,
17372 cx: &mut Context<Self>,
17373 ) -> Task<Result<()>> {
17374 let buffer = self.buffer.clone();
17375 let buffers = buffer.read(cx).all_buffers();
17376 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17377 let apply_action = project.update(cx, |project, cx| {
17378 project.apply_code_action_kind(buffers, kind, true, cx)
17379 });
17380 cx.spawn_in(window, async move |_, cx| {
17381 let transaction = futures::select_biased! {
17382 () = timeout => {
17383 log::warn!("timed out waiting for executing code action");
17384 None
17385 }
17386 transaction = apply_action.log_err().fuse() => transaction,
17387 };
17388 buffer
17389 .update(cx, |buffer, cx| {
17390 // check if we need this
17391 if let Some(transaction) = transaction
17392 && !buffer.is_singleton()
17393 {
17394 buffer.push_transaction(&transaction.0, cx);
17395 }
17396 cx.notify();
17397 })
17398 .ok();
17399 Ok(())
17400 })
17401 }
17402
17403 pub fn restart_language_server(
17404 &mut self,
17405 _: &RestartLanguageServer,
17406 _: &mut Window,
17407 cx: &mut Context<Self>,
17408 ) {
17409 if let Some(project) = self.project.clone() {
17410 self.buffer.update(cx, |multi_buffer, cx| {
17411 project.update(cx, |project, cx| {
17412 project.restart_language_servers_for_buffers(
17413 multi_buffer.all_buffers().into_iter().collect(),
17414 HashSet::default(),
17415 cx,
17416 );
17417 });
17418 })
17419 }
17420 }
17421
17422 pub fn stop_language_server(
17423 &mut self,
17424 _: &StopLanguageServer,
17425 _: &mut Window,
17426 cx: &mut Context<Self>,
17427 ) {
17428 if let Some(project) = self.project.clone() {
17429 self.buffer.update(cx, |multi_buffer, cx| {
17430 project.update(cx, |project, cx| {
17431 project.stop_language_servers_for_buffers(
17432 multi_buffer.all_buffers().into_iter().collect(),
17433 HashSet::default(),
17434 cx,
17435 );
17436 });
17437 });
17438 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17439 }
17440 }
17441
17442 fn cancel_language_server_work(
17443 workspace: &mut Workspace,
17444 _: &actions::CancelLanguageServerWork,
17445 _: &mut Window,
17446 cx: &mut Context<Workspace>,
17447 ) {
17448 let project = workspace.project();
17449 let buffers = workspace
17450 .active_item(cx)
17451 .and_then(|item| item.act_as::<Editor>(cx))
17452 .map_or(HashSet::default(), |editor| {
17453 editor.read(cx).buffer.read(cx).all_buffers()
17454 });
17455 project.update(cx, |project, cx| {
17456 project.cancel_language_server_work_for_buffers(buffers, cx);
17457 });
17458 }
17459
17460 fn show_character_palette(
17461 &mut self,
17462 _: &ShowCharacterPalette,
17463 window: &mut Window,
17464 _: &mut Context<Self>,
17465 ) {
17466 window.show_character_palette();
17467 }
17468
17469 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17470 if !self.diagnostics_enabled() {
17471 return;
17472 }
17473
17474 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17475 let buffer = self.buffer.read(cx).snapshot(cx);
17476 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17477 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17478 let is_valid = buffer
17479 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17480 .any(|entry| {
17481 entry.diagnostic.is_primary
17482 && !entry.range.is_empty()
17483 && entry.range.start == primary_range_start
17484 && entry.diagnostic.message == active_diagnostics.active_message
17485 });
17486
17487 if !is_valid {
17488 self.dismiss_diagnostics(cx);
17489 }
17490 }
17491 }
17492
17493 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17494 match &self.active_diagnostics {
17495 ActiveDiagnostic::Group(group) => Some(group),
17496 _ => None,
17497 }
17498 }
17499
17500 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17501 if !self.diagnostics_enabled() {
17502 return;
17503 }
17504 self.dismiss_diagnostics(cx);
17505 self.active_diagnostics = ActiveDiagnostic::All;
17506 }
17507
17508 fn activate_diagnostics(
17509 &mut self,
17510 buffer_id: BufferId,
17511 diagnostic: DiagnosticEntryRef<'_, usize>,
17512 window: &mut Window,
17513 cx: &mut Context<Self>,
17514 ) {
17515 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17516 return;
17517 }
17518 self.dismiss_diagnostics(cx);
17519 let snapshot = self.snapshot(window, cx);
17520 let buffer = self.buffer.read(cx).snapshot(cx);
17521 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17522 return;
17523 };
17524
17525 let diagnostic_group = buffer
17526 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17527 .collect::<Vec<_>>();
17528
17529 let blocks =
17530 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17531
17532 let blocks = self.display_map.update(cx, |display_map, cx| {
17533 display_map.insert_blocks(blocks, cx).into_iter().collect()
17534 });
17535 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17536 active_range: buffer.anchor_before(diagnostic.range.start)
17537 ..buffer.anchor_after(diagnostic.range.end),
17538 active_message: diagnostic.diagnostic.message.clone(),
17539 group_id: diagnostic.diagnostic.group_id,
17540 blocks,
17541 });
17542 cx.notify();
17543 }
17544
17545 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17546 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17547 return;
17548 };
17549
17550 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17551 if let ActiveDiagnostic::Group(group) = prev {
17552 self.display_map.update(cx, |display_map, cx| {
17553 display_map.remove_blocks(group.blocks, cx);
17554 });
17555 cx.notify();
17556 }
17557 }
17558
17559 /// Disable inline diagnostics rendering for this editor.
17560 pub fn disable_inline_diagnostics(&mut self) {
17561 self.inline_diagnostics_enabled = false;
17562 self.inline_diagnostics_update = Task::ready(());
17563 self.inline_diagnostics.clear();
17564 }
17565
17566 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17567 self.diagnostics_enabled = false;
17568 self.dismiss_diagnostics(cx);
17569 self.inline_diagnostics_update = Task::ready(());
17570 self.inline_diagnostics.clear();
17571 }
17572
17573 pub fn disable_word_completions(&mut self) {
17574 self.word_completions_enabled = false;
17575 }
17576
17577 pub fn diagnostics_enabled(&self) -> bool {
17578 self.diagnostics_enabled && self.mode.is_full()
17579 }
17580
17581 pub fn inline_diagnostics_enabled(&self) -> bool {
17582 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17583 }
17584
17585 pub fn show_inline_diagnostics(&self) -> bool {
17586 self.show_inline_diagnostics
17587 }
17588
17589 pub fn toggle_inline_diagnostics(
17590 &mut self,
17591 _: &ToggleInlineDiagnostics,
17592 window: &mut Window,
17593 cx: &mut Context<Editor>,
17594 ) {
17595 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17596 self.refresh_inline_diagnostics(false, window, cx);
17597 }
17598
17599 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17600 self.diagnostics_max_severity = severity;
17601 self.display_map.update(cx, |display_map, _| {
17602 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17603 });
17604 }
17605
17606 pub fn toggle_diagnostics(
17607 &mut self,
17608 _: &ToggleDiagnostics,
17609 window: &mut Window,
17610 cx: &mut Context<Editor>,
17611 ) {
17612 if !self.diagnostics_enabled() {
17613 return;
17614 }
17615
17616 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17617 EditorSettings::get_global(cx)
17618 .diagnostics_max_severity
17619 .filter(|severity| severity != &DiagnosticSeverity::Off)
17620 .unwrap_or(DiagnosticSeverity::Hint)
17621 } else {
17622 DiagnosticSeverity::Off
17623 };
17624 self.set_max_diagnostics_severity(new_severity, cx);
17625 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17626 self.active_diagnostics = ActiveDiagnostic::None;
17627 self.inline_diagnostics_update = Task::ready(());
17628 self.inline_diagnostics.clear();
17629 } else {
17630 self.refresh_inline_diagnostics(false, window, cx);
17631 }
17632
17633 cx.notify();
17634 }
17635
17636 pub fn toggle_minimap(
17637 &mut self,
17638 _: &ToggleMinimap,
17639 window: &mut Window,
17640 cx: &mut Context<Editor>,
17641 ) {
17642 if self.supports_minimap(cx) {
17643 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17644 }
17645 }
17646
17647 fn refresh_inline_diagnostics(
17648 &mut self,
17649 debounce: bool,
17650 window: &mut Window,
17651 cx: &mut Context<Self>,
17652 ) {
17653 let max_severity = ProjectSettings::get_global(cx)
17654 .diagnostics
17655 .inline
17656 .max_severity
17657 .unwrap_or(self.diagnostics_max_severity);
17658
17659 if !self.inline_diagnostics_enabled()
17660 || !self.show_inline_diagnostics
17661 || max_severity == DiagnosticSeverity::Off
17662 {
17663 self.inline_diagnostics_update = Task::ready(());
17664 self.inline_diagnostics.clear();
17665 return;
17666 }
17667
17668 let debounce_ms = ProjectSettings::get_global(cx)
17669 .diagnostics
17670 .inline
17671 .update_debounce_ms;
17672 let debounce = if debounce && debounce_ms > 0 {
17673 Some(Duration::from_millis(debounce_ms))
17674 } else {
17675 None
17676 };
17677 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17678 if let Some(debounce) = debounce {
17679 cx.background_executor().timer(debounce).await;
17680 }
17681 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17682 editor
17683 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17684 .ok()
17685 }) else {
17686 return;
17687 };
17688
17689 let new_inline_diagnostics = cx
17690 .background_spawn(async move {
17691 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17692 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17693 let message = diagnostic_entry
17694 .diagnostic
17695 .message
17696 .split_once('\n')
17697 .map(|(line, _)| line)
17698 .map(SharedString::new)
17699 .unwrap_or_else(|| {
17700 SharedString::new(&*diagnostic_entry.diagnostic.message)
17701 });
17702 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17703 let (Ok(i) | Err(i)) = inline_diagnostics
17704 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17705 inline_diagnostics.insert(
17706 i,
17707 (
17708 start_anchor,
17709 InlineDiagnostic {
17710 message,
17711 group_id: diagnostic_entry.diagnostic.group_id,
17712 start: diagnostic_entry.range.start.to_point(&snapshot),
17713 is_primary: diagnostic_entry.diagnostic.is_primary,
17714 severity: diagnostic_entry.diagnostic.severity,
17715 },
17716 ),
17717 );
17718 }
17719 inline_diagnostics
17720 })
17721 .await;
17722
17723 editor
17724 .update(cx, |editor, cx| {
17725 editor.inline_diagnostics = new_inline_diagnostics;
17726 cx.notify();
17727 })
17728 .ok();
17729 });
17730 }
17731
17732 fn pull_diagnostics(
17733 &mut self,
17734 buffer_id: Option<BufferId>,
17735 window: &Window,
17736 cx: &mut Context<Self>,
17737 ) -> Option<()> {
17738 if self.ignore_lsp_data() {
17739 return None;
17740 }
17741 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17742 .diagnostics
17743 .lsp_pull_diagnostics;
17744 if !pull_diagnostics_settings.enabled {
17745 return None;
17746 }
17747 let project = self.project()?.downgrade();
17748 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17749 let mut buffers = self.buffer.read(cx).all_buffers();
17750 buffers.retain(|buffer| {
17751 let buffer_id_to_retain = buffer.read(cx).remote_id();
17752 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17753 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17754 });
17755 if buffers.is_empty() {
17756 self.pull_diagnostics_task = Task::ready(());
17757 return None;
17758 }
17759
17760 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17761 cx.background_executor().timer(debounce).await;
17762
17763 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17764 buffers
17765 .into_iter()
17766 .filter_map(|buffer| {
17767 project
17768 .update(cx, |project, cx| {
17769 project.lsp_store().update(cx, |lsp_store, cx| {
17770 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17771 })
17772 })
17773 .ok()
17774 })
17775 .collect::<FuturesUnordered<_>>()
17776 }) else {
17777 return;
17778 };
17779
17780 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17781 match pull_task {
17782 Ok(()) => {
17783 if editor
17784 .update_in(cx, |editor, window, cx| {
17785 editor.update_diagnostics_state(window, cx);
17786 })
17787 .is_err()
17788 {
17789 return;
17790 }
17791 }
17792 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17793 }
17794 }
17795 });
17796
17797 Some(())
17798 }
17799
17800 pub fn set_selections_from_remote(
17801 &mut self,
17802 selections: Vec<Selection<Anchor>>,
17803 pending_selection: Option<Selection<Anchor>>,
17804 window: &mut Window,
17805 cx: &mut Context<Self>,
17806 ) {
17807 let old_cursor_position = self.selections.newest_anchor().head();
17808 self.selections.change_with(cx, |s| {
17809 s.select_anchors(selections);
17810 if let Some(pending_selection) = pending_selection {
17811 s.set_pending(pending_selection, SelectMode::Character);
17812 } else {
17813 s.clear_pending();
17814 }
17815 });
17816 self.selections_did_change(
17817 false,
17818 &old_cursor_position,
17819 SelectionEffects::default(),
17820 window,
17821 cx,
17822 );
17823 }
17824
17825 pub fn transact(
17826 &mut self,
17827 window: &mut Window,
17828 cx: &mut Context<Self>,
17829 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17830 ) -> Option<TransactionId> {
17831 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17832 this.start_transaction_at(Instant::now(), window, cx);
17833 update(this, window, cx);
17834 this.end_transaction_at(Instant::now(), cx)
17835 })
17836 }
17837
17838 pub fn start_transaction_at(
17839 &mut self,
17840 now: Instant,
17841 window: &mut Window,
17842 cx: &mut Context<Self>,
17843 ) -> Option<TransactionId> {
17844 self.end_selection(window, cx);
17845 if let Some(tx_id) = self
17846 .buffer
17847 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17848 {
17849 self.selection_history
17850 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17851 cx.emit(EditorEvent::TransactionBegun {
17852 transaction_id: tx_id,
17853 });
17854 Some(tx_id)
17855 } else {
17856 None
17857 }
17858 }
17859
17860 pub fn end_transaction_at(
17861 &mut self,
17862 now: Instant,
17863 cx: &mut Context<Self>,
17864 ) -> Option<TransactionId> {
17865 if let Some(transaction_id) = self
17866 .buffer
17867 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17868 {
17869 if let Some((_, end_selections)) =
17870 self.selection_history.transaction_mut(transaction_id)
17871 {
17872 *end_selections = Some(self.selections.disjoint_anchors_arc());
17873 } else {
17874 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17875 }
17876
17877 cx.emit(EditorEvent::Edited { transaction_id });
17878 Some(transaction_id)
17879 } else {
17880 None
17881 }
17882 }
17883
17884 pub fn modify_transaction_selection_history(
17885 &mut self,
17886 transaction_id: TransactionId,
17887 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17888 ) -> bool {
17889 self.selection_history
17890 .transaction_mut(transaction_id)
17891 .map(modify)
17892 .is_some()
17893 }
17894
17895 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17896 if self.selection_mark_mode {
17897 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17898 s.move_with(|_, sel| {
17899 sel.collapse_to(sel.head(), SelectionGoal::None);
17900 });
17901 })
17902 }
17903 self.selection_mark_mode = true;
17904 cx.notify();
17905 }
17906
17907 pub fn swap_selection_ends(
17908 &mut self,
17909 _: &actions::SwapSelectionEnds,
17910 window: &mut Window,
17911 cx: &mut Context<Self>,
17912 ) {
17913 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17914 s.move_with(|_, sel| {
17915 if sel.start != sel.end {
17916 sel.reversed = !sel.reversed
17917 }
17918 });
17919 });
17920 self.request_autoscroll(Autoscroll::newest(), cx);
17921 cx.notify();
17922 }
17923
17924 pub fn toggle_focus(
17925 workspace: &mut Workspace,
17926 _: &actions::ToggleFocus,
17927 window: &mut Window,
17928 cx: &mut Context<Workspace>,
17929 ) {
17930 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17931 return;
17932 };
17933 workspace.activate_item(&item, true, true, window, cx);
17934 }
17935
17936 pub fn toggle_fold(
17937 &mut self,
17938 _: &actions::ToggleFold,
17939 window: &mut Window,
17940 cx: &mut Context<Self>,
17941 ) {
17942 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
17943 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17944 let selection = self.selections.newest::<Point>(&display_map);
17945
17946 let range = if selection.is_empty() {
17947 let point = selection.head().to_display_point(&display_map);
17948 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17949 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17950 .to_point(&display_map);
17951 start..end
17952 } else {
17953 selection.range()
17954 };
17955 if display_map.folds_in_range(range).next().is_some() {
17956 self.unfold_lines(&Default::default(), window, cx)
17957 } else {
17958 self.fold(&Default::default(), window, cx)
17959 }
17960 } else {
17961 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17962 let buffer_ids: HashSet<_> = self
17963 .selections
17964 .disjoint_anchor_ranges()
17965 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17966 .collect();
17967
17968 let should_unfold = buffer_ids
17969 .iter()
17970 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17971
17972 for buffer_id in buffer_ids {
17973 if should_unfold {
17974 self.unfold_buffer(buffer_id, cx);
17975 } else {
17976 self.fold_buffer(buffer_id, cx);
17977 }
17978 }
17979 }
17980 }
17981
17982 pub fn toggle_fold_recursive(
17983 &mut self,
17984 _: &actions::ToggleFoldRecursive,
17985 window: &mut Window,
17986 cx: &mut Context<Self>,
17987 ) {
17988 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17989
17990 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17991 let range = if selection.is_empty() {
17992 let point = selection.head().to_display_point(&display_map);
17993 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17994 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17995 .to_point(&display_map);
17996 start..end
17997 } else {
17998 selection.range()
17999 };
18000 if display_map.folds_in_range(range).next().is_some() {
18001 self.unfold_recursive(&Default::default(), window, cx)
18002 } else {
18003 self.fold_recursive(&Default::default(), window, cx)
18004 }
18005 }
18006
18007 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18008 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18009 let mut to_fold = Vec::new();
18010 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18011 let selections = self.selections.all_adjusted(&display_map);
18012
18013 for selection in selections {
18014 let range = selection.range().sorted();
18015 let buffer_start_row = range.start.row;
18016
18017 if range.start.row != range.end.row {
18018 let mut found = false;
18019 let mut row = range.start.row;
18020 while row <= range.end.row {
18021 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18022 {
18023 found = true;
18024 row = crease.range().end.row + 1;
18025 to_fold.push(crease);
18026 } else {
18027 row += 1
18028 }
18029 }
18030 if found {
18031 continue;
18032 }
18033 }
18034
18035 for row in (0..=range.start.row).rev() {
18036 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18037 && crease.range().end.row >= buffer_start_row
18038 {
18039 to_fold.push(crease);
18040 if row <= range.start.row {
18041 break;
18042 }
18043 }
18044 }
18045 }
18046
18047 self.fold_creases(to_fold, true, window, cx);
18048 } else {
18049 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18050 let buffer_ids = self
18051 .selections
18052 .disjoint_anchor_ranges()
18053 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18054 .collect::<HashSet<_>>();
18055 for buffer_id in buffer_ids {
18056 self.fold_buffer(buffer_id, cx);
18057 }
18058 }
18059 }
18060
18061 pub fn toggle_fold_all(
18062 &mut self,
18063 _: &actions::ToggleFoldAll,
18064 window: &mut Window,
18065 cx: &mut Context<Self>,
18066 ) {
18067 if self.buffer.read(cx).is_singleton() {
18068 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18069 let has_folds = display_map
18070 .folds_in_range(0..display_map.buffer_snapshot().len())
18071 .next()
18072 .is_some();
18073
18074 if has_folds {
18075 self.unfold_all(&actions::UnfoldAll, window, cx);
18076 } else {
18077 self.fold_all(&actions::FoldAll, window, cx);
18078 }
18079 } else {
18080 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18081 let should_unfold = buffer_ids
18082 .iter()
18083 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18084
18085 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18086 editor
18087 .update_in(cx, |editor, _, cx| {
18088 for buffer_id in buffer_ids {
18089 if should_unfold {
18090 editor.unfold_buffer(buffer_id, cx);
18091 } else {
18092 editor.fold_buffer(buffer_id, cx);
18093 }
18094 }
18095 })
18096 .ok();
18097 });
18098 }
18099 }
18100
18101 fn fold_at_level(
18102 &mut self,
18103 fold_at: &FoldAtLevel,
18104 window: &mut Window,
18105 cx: &mut Context<Self>,
18106 ) {
18107 if !self.buffer.read(cx).is_singleton() {
18108 return;
18109 }
18110
18111 let fold_at_level = fold_at.0;
18112 let snapshot = self.buffer.read(cx).snapshot(cx);
18113 let mut to_fold = Vec::new();
18114 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18115
18116 let row_ranges_to_keep: Vec<Range<u32>> = self
18117 .selections
18118 .all::<Point>(&self.display_snapshot(cx))
18119 .into_iter()
18120 .map(|sel| sel.start.row..sel.end.row)
18121 .collect();
18122
18123 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18124 while start_row < end_row {
18125 match self
18126 .snapshot(window, cx)
18127 .crease_for_buffer_row(MultiBufferRow(start_row))
18128 {
18129 Some(crease) => {
18130 let nested_start_row = crease.range().start.row + 1;
18131 let nested_end_row = crease.range().end.row;
18132
18133 if current_level < fold_at_level {
18134 stack.push((nested_start_row, nested_end_row, current_level + 1));
18135 } else if current_level == fold_at_level {
18136 // Fold iff there is no selection completely contained within the fold region
18137 if !row_ranges_to_keep.iter().any(|selection| {
18138 selection.end >= nested_start_row
18139 && selection.start <= nested_end_row
18140 }) {
18141 to_fold.push(crease);
18142 }
18143 }
18144
18145 start_row = nested_end_row + 1;
18146 }
18147 None => start_row += 1,
18148 }
18149 }
18150 }
18151
18152 self.fold_creases(to_fold, true, window, cx);
18153 }
18154
18155 pub fn fold_at_level_1(
18156 &mut self,
18157 _: &actions::FoldAtLevel1,
18158 window: &mut Window,
18159 cx: &mut Context<Self>,
18160 ) {
18161 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18162 }
18163
18164 pub fn fold_at_level_2(
18165 &mut self,
18166 _: &actions::FoldAtLevel2,
18167 window: &mut Window,
18168 cx: &mut Context<Self>,
18169 ) {
18170 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18171 }
18172
18173 pub fn fold_at_level_3(
18174 &mut self,
18175 _: &actions::FoldAtLevel3,
18176 window: &mut Window,
18177 cx: &mut Context<Self>,
18178 ) {
18179 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18180 }
18181
18182 pub fn fold_at_level_4(
18183 &mut self,
18184 _: &actions::FoldAtLevel4,
18185 window: &mut Window,
18186 cx: &mut Context<Self>,
18187 ) {
18188 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18189 }
18190
18191 pub fn fold_at_level_5(
18192 &mut self,
18193 _: &actions::FoldAtLevel5,
18194 window: &mut Window,
18195 cx: &mut Context<Self>,
18196 ) {
18197 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18198 }
18199
18200 pub fn fold_at_level_6(
18201 &mut self,
18202 _: &actions::FoldAtLevel6,
18203 window: &mut Window,
18204 cx: &mut Context<Self>,
18205 ) {
18206 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18207 }
18208
18209 pub fn fold_at_level_7(
18210 &mut self,
18211 _: &actions::FoldAtLevel7,
18212 window: &mut Window,
18213 cx: &mut Context<Self>,
18214 ) {
18215 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18216 }
18217
18218 pub fn fold_at_level_8(
18219 &mut self,
18220 _: &actions::FoldAtLevel8,
18221 window: &mut Window,
18222 cx: &mut Context<Self>,
18223 ) {
18224 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18225 }
18226
18227 pub fn fold_at_level_9(
18228 &mut self,
18229 _: &actions::FoldAtLevel9,
18230 window: &mut Window,
18231 cx: &mut Context<Self>,
18232 ) {
18233 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18234 }
18235
18236 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18237 if self.buffer.read(cx).is_singleton() {
18238 let mut fold_ranges = Vec::new();
18239 let snapshot = self.buffer.read(cx).snapshot(cx);
18240
18241 for row in 0..snapshot.max_row().0 {
18242 if let Some(foldable_range) = self
18243 .snapshot(window, cx)
18244 .crease_for_buffer_row(MultiBufferRow(row))
18245 {
18246 fold_ranges.push(foldable_range);
18247 }
18248 }
18249
18250 self.fold_creases(fold_ranges, true, window, cx);
18251 } else {
18252 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18253 editor
18254 .update_in(cx, |editor, _, cx| {
18255 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18256 editor.fold_buffer(buffer_id, cx);
18257 }
18258 })
18259 .ok();
18260 });
18261 }
18262 }
18263
18264 pub fn fold_function_bodies(
18265 &mut self,
18266 _: &actions::FoldFunctionBodies,
18267 window: &mut Window,
18268 cx: &mut Context<Self>,
18269 ) {
18270 let snapshot = self.buffer.read(cx).snapshot(cx);
18271
18272 let ranges = snapshot
18273 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18274 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18275 .collect::<Vec<_>>();
18276
18277 let creases = ranges
18278 .into_iter()
18279 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18280 .collect();
18281
18282 self.fold_creases(creases, true, window, cx);
18283 }
18284
18285 pub fn fold_recursive(
18286 &mut self,
18287 _: &actions::FoldRecursive,
18288 window: &mut Window,
18289 cx: &mut Context<Self>,
18290 ) {
18291 let mut to_fold = Vec::new();
18292 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18293 let selections = self.selections.all_adjusted(&display_map);
18294
18295 for selection in selections {
18296 let range = selection.range().sorted();
18297 let buffer_start_row = range.start.row;
18298
18299 if range.start.row != range.end.row {
18300 let mut found = false;
18301 for row in range.start.row..=range.end.row {
18302 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18303 found = true;
18304 to_fold.push(crease);
18305 }
18306 }
18307 if found {
18308 continue;
18309 }
18310 }
18311
18312 for row in (0..=range.start.row).rev() {
18313 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18314 if crease.range().end.row >= buffer_start_row {
18315 to_fold.push(crease);
18316 } else {
18317 break;
18318 }
18319 }
18320 }
18321 }
18322
18323 self.fold_creases(to_fold, true, window, cx);
18324 }
18325
18326 pub fn fold_at(
18327 &mut self,
18328 buffer_row: MultiBufferRow,
18329 window: &mut Window,
18330 cx: &mut Context<Self>,
18331 ) {
18332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18333
18334 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18335 let autoscroll = self
18336 .selections
18337 .all::<Point>(&display_map)
18338 .iter()
18339 .any(|selection| crease.range().overlaps(&selection.range()));
18340
18341 self.fold_creases(vec![crease], autoscroll, window, cx);
18342 }
18343 }
18344
18345 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18346 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18348 let buffer = display_map.buffer_snapshot();
18349 let selections = self.selections.all::<Point>(&display_map);
18350 let ranges = selections
18351 .iter()
18352 .map(|s| {
18353 let range = s.display_range(&display_map).sorted();
18354 let mut start = range.start.to_point(&display_map);
18355 let mut end = range.end.to_point(&display_map);
18356 start.column = 0;
18357 end.column = buffer.line_len(MultiBufferRow(end.row));
18358 start..end
18359 })
18360 .collect::<Vec<_>>();
18361
18362 self.unfold_ranges(&ranges, true, true, cx);
18363 } else {
18364 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18365 let buffer_ids = self
18366 .selections
18367 .disjoint_anchor_ranges()
18368 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18369 .collect::<HashSet<_>>();
18370 for buffer_id in buffer_ids {
18371 self.unfold_buffer(buffer_id, cx);
18372 }
18373 }
18374 }
18375
18376 pub fn unfold_recursive(
18377 &mut self,
18378 _: &UnfoldRecursive,
18379 _window: &mut Window,
18380 cx: &mut Context<Self>,
18381 ) {
18382 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18383 let selections = self.selections.all::<Point>(&display_map);
18384 let ranges = selections
18385 .iter()
18386 .map(|s| {
18387 let mut range = s.display_range(&display_map).sorted();
18388 *range.start.column_mut() = 0;
18389 *range.end.column_mut() = display_map.line_len(range.end.row());
18390 let start = range.start.to_point(&display_map);
18391 let end = range.end.to_point(&display_map);
18392 start..end
18393 })
18394 .collect::<Vec<_>>();
18395
18396 self.unfold_ranges(&ranges, true, true, cx);
18397 }
18398
18399 pub fn unfold_at(
18400 &mut self,
18401 buffer_row: MultiBufferRow,
18402 _window: &mut Window,
18403 cx: &mut Context<Self>,
18404 ) {
18405 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18406
18407 let intersection_range = Point::new(buffer_row.0, 0)
18408 ..Point::new(
18409 buffer_row.0,
18410 display_map.buffer_snapshot().line_len(buffer_row),
18411 );
18412
18413 let autoscroll = self
18414 .selections
18415 .all::<Point>(&display_map)
18416 .iter()
18417 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18418
18419 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18420 }
18421
18422 pub fn unfold_all(
18423 &mut self,
18424 _: &actions::UnfoldAll,
18425 _window: &mut Window,
18426 cx: &mut Context<Self>,
18427 ) {
18428 if self.buffer.read(cx).is_singleton() {
18429 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18430 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18431 } else {
18432 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18433 editor
18434 .update(cx, |editor, cx| {
18435 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18436 editor.unfold_buffer(buffer_id, cx);
18437 }
18438 })
18439 .ok();
18440 });
18441 }
18442 }
18443
18444 pub fn fold_selected_ranges(
18445 &mut self,
18446 _: &FoldSelectedRanges,
18447 window: &mut Window,
18448 cx: &mut Context<Self>,
18449 ) {
18450 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18451 let selections = self.selections.all_adjusted(&display_map);
18452 let ranges = selections
18453 .into_iter()
18454 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18455 .collect::<Vec<_>>();
18456 self.fold_creases(ranges, true, window, cx);
18457 }
18458
18459 pub fn fold_ranges<T: ToOffset + Clone>(
18460 &mut self,
18461 ranges: Vec<Range<T>>,
18462 auto_scroll: bool,
18463 window: &mut Window,
18464 cx: &mut Context<Self>,
18465 ) {
18466 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18467 let ranges = ranges
18468 .into_iter()
18469 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18470 .collect::<Vec<_>>();
18471 self.fold_creases(ranges, auto_scroll, window, cx);
18472 }
18473
18474 pub fn fold_creases<T: ToOffset + Clone>(
18475 &mut self,
18476 creases: Vec<Crease<T>>,
18477 auto_scroll: bool,
18478 _window: &mut Window,
18479 cx: &mut Context<Self>,
18480 ) {
18481 if creases.is_empty() {
18482 return;
18483 }
18484
18485 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18486
18487 if auto_scroll {
18488 self.request_autoscroll(Autoscroll::fit(), cx);
18489 }
18490
18491 cx.notify();
18492
18493 self.scrollbar_marker_state.dirty = true;
18494 self.folds_did_change(cx);
18495 }
18496
18497 /// Removes any folds whose ranges intersect any of the given ranges.
18498 pub fn unfold_ranges<T: ToOffset + Clone>(
18499 &mut self,
18500 ranges: &[Range<T>],
18501 inclusive: bool,
18502 auto_scroll: bool,
18503 cx: &mut Context<Self>,
18504 ) {
18505 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18506 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18507 });
18508 self.folds_did_change(cx);
18509 }
18510
18511 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18512 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18513 return;
18514 }
18515 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18516 self.display_map.update(cx, |display_map, cx| {
18517 display_map.fold_buffers([buffer_id], cx)
18518 });
18519 cx.emit(EditorEvent::BufferFoldToggled {
18520 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18521 folded: true,
18522 });
18523 cx.notify();
18524 }
18525
18526 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18527 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18528 return;
18529 }
18530 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18531 self.display_map.update(cx, |display_map, cx| {
18532 display_map.unfold_buffers([buffer_id], cx);
18533 });
18534 cx.emit(EditorEvent::BufferFoldToggled {
18535 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18536 folded: false,
18537 });
18538 cx.notify();
18539 }
18540
18541 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18542 self.display_map.read(cx).is_buffer_folded(buffer)
18543 }
18544
18545 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18546 self.display_map.read(cx).folded_buffers()
18547 }
18548
18549 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18550 self.display_map.update(cx, |display_map, cx| {
18551 display_map.disable_header_for_buffer(buffer_id, cx);
18552 });
18553 cx.notify();
18554 }
18555
18556 /// Removes any folds with the given ranges.
18557 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18558 &mut self,
18559 ranges: &[Range<T>],
18560 type_id: TypeId,
18561 auto_scroll: bool,
18562 cx: &mut Context<Self>,
18563 ) {
18564 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18565 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18566 });
18567 self.folds_did_change(cx);
18568 }
18569
18570 fn remove_folds_with<T: ToOffset + Clone>(
18571 &mut self,
18572 ranges: &[Range<T>],
18573 auto_scroll: bool,
18574 cx: &mut Context<Self>,
18575 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18576 ) {
18577 if ranges.is_empty() {
18578 return;
18579 }
18580
18581 let mut buffers_affected = HashSet::default();
18582 let multi_buffer = self.buffer().read(cx);
18583 for range in ranges {
18584 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18585 buffers_affected.insert(buffer.read(cx).remote_id());
18586 };
18587 }
18588
18589 self.display_map.update(cx, update);
18590
18591 if auto_scroll {
18592 self.request_autoscroll(Autoscroll::fit(), cx);
18593 }
18594
18595 cx.notify();
18596 self.scrollbar_marker_state.dirty = true;
18597 self.active_indent_guides_state.dirty = true;
18598 }
18599
18600 pub fn update_renderer_widths(
18601 &mut self,
18602 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18603 cx: &mut Context<Self>,
18604 ) -> bool {
18605 self.display_map
18606 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18607 }
18608
18609 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18610 self.display_map.read(cx).fold_placeholder.clone()
18611 }
18612
18613 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18614 self.buffer.update(cx, |buffer, cx| {
18615 buffer.set_all_diff_hunks_expanded(cx);
18616 });
18617 }
18618
18619 pub fn expand_all_diff_hunks(
18620 &mut self,
18621 _: &ExpandAllDiffHunks,
18622 _window: &mut Window,
18623 cx: &mut Context<Self>,
18624 ) {
18625 self.buffer.update(cx, |buffer, cx| {
18626 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18627 });
18628 }
18629
18630 pub fn collapse_all_diff_hunks(
18631 &mut self,
18632 _: &CollapseAllDiffHunks,
18633 _window: &mut Window,
18634 cx: &mut Context<Self>,
18635 ) {
18636 self.buffer.update(cx, |buffer, cx| {
18637 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18638 });
18639 }
18640
18641 pub fn toggle_selected_diff_hunks(
18642 &mut self,
18643 _: &ToggleSelectedDiffHunks,
18644 _window: &mut Window,
18645 cx: &mut Context<Self>,
18646 ) {
18647 let ranges: Vec<_> = self
18648 .selections
18649 .disjoint_anchors()
18650 .iter()
18651 .map(|s| s.range())
18652 .collect();
18653 self.toggle_diff_hunks_in_ranges(ranges, cx);
18654 }
18655
18656 pub fn diff_hunks_in_ranges<'a>(
18657 &'a self,
18658 ranges: &'a [Range<Anchor>],
18659 buffer: &'a MultiBufferSnapshot,
18660 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18661 ranges.iter().flat_map(move |range| {
18662 let end_excerpt_id = range.end.excerpt_id;
18663 let range = range.to_point(buffer);
18664 let mut peek_end = range.end;
18665 if range.end.row < buffer.max_row().0 {
18666 peek_end = Point::new(range.end.row + 1, 0);
18667 }
18668 buffer
18669 .diff_hunks_in_range(range.start..peek_end)
18670 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18671 })
18672 }
18673
18674 pub fn has_stageable_diff_hunks_in_ranges(
18675 &self,
18676 ranges: &[Range<Anchor>],
18677 snapshot: &MultiBufferSnapshot,
18678 ) -> bool {
18679 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18680 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18681 }
18682
18683 pub fn toggle_staged_selected_diff_hunks(
18684 &mut self,
18685 _: &::git::ToggleStaged,
18686 _: &mut Window,
18687 cx: &mut Context<Self>,
18688 ) {
18689 let snapshot = self.buffer.read(cx).snapshot(cx);
18690 let ranges: Vec<_> = self
18691 .selections
18692 .disjoint_anchors()
18693 .iter()
18694 .map(|s| s.range())
18695 .collect();
18696 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18697 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18698 }
18699
18700 pub fn set_render_diff_hunk_controls(
18701 &mut self,
18702 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18703 cx: &mut Context<Self>,
18704 ) {
18705 self.render_diff_hunk_controls = render_diff_hunk_controls;
18706 cx.notify();
18707 }
18708
18709 pub fn stage_and_next(
18710 &mut self,
18711 _: &::git::StageAndNext,
18712 window: &mut Window,
18713 cx: &mut Context<Self>,
18714 ) {
18715 self.do_stage_or_unstage_and_next(true, window, cx);
18716 }
18717
18718 pub fn unstage_and_next(
18719 &mut self,
18720 _: &::git::UnstageAndNext,
18721 window: &mut Window,
18722 cx: &mut Context<Self>,
18723 ) {
18724 self.do_stage_or_unstage_and_next(false, window, cx);
18725 }
18726
18727 pub fn stage_or_unstage_diff_hunks(
18728 &mut self,
18729 stage: bool,
18730 ranges: Vec<Range<Anchor>>,
18731 cx: &mut Context<Self>,
18732 ) {
18733 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18734 cx.spawn(async move |this, cx| {
18735 task.await?;
18736 this.update(cx, |this, cx| {
18737 let snapshot = this.buffer.read(cx).snapshot(cx);
18738 let chunk_by = this
18739 .diff_hunks_in_ranges(&ranges, &snapshot)
18740 .chunk_by(|hunk| hunk.buffer_id);
18741 for (buffer_id, hunks) in &chunk_by {
18742 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18743 }
18744 })
18745 })
18746 .detach_and_log_err(cx);
18747 }
18748
18749 fn save_buffers_for_ranges_if_needed(
18750 &mut self,
18751 ranges: &[Range<Anchor>],
18752 cx: &mut Context<Editor>,
18753 ) -> Task<Result<()>> {
18754 let multibuffer = self.buffer.read(cx);
18755 let snapshot = multibuffer.read(cx);
18756 let buffer_ids: HashSet<_> = ranges
18757 .iter()
18758 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18759 .collect();
18760 drop(snapshot);
18761
18762 let mut buffers = HashSet::default();
18763 for buffer_id in buffer_ids {
18764 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18765 let buffer = buffer_entity.read(cx);
18766 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18767 {
18768 buffers.insert(buffer_entity);
18769 }
18770 }
18771 }
18772
18773 if let Some(project) = &self.project {
18774 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18775 } else {
18776 Task::ready(Ok(()))
18777 }
18778 }
18779
18780 fn do_stage_or_unstage_and_next(
18781 &mut self,
18782 stage: bool,
18783 window: &mut Window,
18784 cx: &mut Context<Self>,
18785 ) {
18786 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18787
18788 if ranges.iter().any(|range| range.start != range.end) {
18789 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18790 return;
18791 }
18792
18793 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18794 let snapshot = self.snapshot(window, cx);
18795 let position = self
18796 .selections
18797 .newest::<Point>(&snapshot.display_snapshot)
18798 .head();
18799 let mut row = snapshot
18800 .buffer_snapshot()
18801 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18802 .find(|hunk| hunk.row_range.start.0 > position.row)
18803 .map(|hunk| hunk.row_range.start);
18804
18805 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18806 // Outside of the project diff editor, wrap around to the beginning.
18807 if !all_diff_hunks_expanded {
18808 row = row.or_else(|| {
18809 snapshot
18810 .buffer_snapshot()
18811 .diff_hunks_in_range(Point::zero()..position)
18812 .find(|hunk| hunk.row_range.end.0 < position.row)
18813 .map(|hunk| hunk.row_range.start)
18814 });
18815 }
18816
18817 if let Some(row) = row {
18818 let destination = Point::new(row.0, 0);
18819 let autoscroll = Autoscroll::center();
18820
18821 self.unfold_ranges(&[destination..destination], false, false, cx);
18822 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18823 s.select_ranges([destination..destination]);
18824 });
18825 }
18826 }
18827
18828 fn do_stage_or_unstage(
18829 &self,
18830 stage: bool,
18831 buffer_id: BufferId,
18832 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18833 cx: &mut App,
18834 ) -> Option<()> {
18835 let project = self.project()?;
18836 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18837 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18838 let buffer_snapshot = buffer.read(cx).snapshot();
18839 let file_exists = buffer_snapshot
18840 .file()
18841 .is_some_and(|file| file.disk_state().exists());
18842 diff.update(cx, |diff, cx| {
18843 diff.stage_or_unstage_hunks(
18844 stage,
18845 &hunks
18846 .map(|hunk| buffer_diff::DiffHunk {
18847 buffer_range: hunk.buffer_range,
18848 diff_base_byte_range: hunk.diff_base_byte_range,
18849 secondary_status: hunk.secondary_status,
18850 range: Point::zero()..Point::zero(), // unused
18851 })
18852 .collect::<Vec<_>>(),
18853 &buffer_snapshot,
18854 file_exists,
18855 cx,
18856 )
18857 });
18858 None
18859 }
18860
18861 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18862 let ranges: Vec<_> = self
18863 .selections
18864 .disjoint_anchors()
18865 .iter()
18866 .map(|s| s.range())
18867 .collect();
18868 self.buffer
18869 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18870 }
18871
18872 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18873 self.buffer.update(cx, |buffer, cx| {
18874 let ranges = vec![Anchor::min()..Anchor::max()];
18875 if !buffer.all_diff_hunks_expanded()
18876 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18877 {
18878 buffer.collapse_diff_hunks(ranges, cx);
18879 true
18880 } else {
18881 false
18882 }
18883 })
18884 }
18885
18886 fn toggle_diff_hunks_in_ranges(
18887 &mut self,
18888 ranges: Vec<Range<Anchor>>,
18889 cx: &mut Context<Editor>,
18890 ) {
18891 self.buffer.update(cx, |buffer, cx| {
18892 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18893 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18894 })
18895 }
18896
18897 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18898 self.buffer.update(cx, |buffer, cx| {
18899 let snapshot = buffer.snapshot(cx);
18900 let excerpt_id = range.end.excerpt_id;
18901 let point_range = range.to_point(&snapshot);
18902 let expand = !buffer.single_hunk_is_expanded(range, cx);
18903 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18904 })
18905 }
18906
18907 pub(crate) fn apply_all_diff_hunks(
18908 &mut self,
18909 _: &ApplyAllDiffHunks,
18910 window: &mut Window,
18911 cx: &mut Context<Self>,
18912 ) {
18913 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18914
18915 let buffers = self.buffer.read(cx).all_buffers();
18916 for branch_buffer in buffers {
18917 branch_buffer.update(cx, |branch_buffer, cx| {
18918 branch_buffer.merge_into_base(Vec::new(), cx);
18919 });
18920 }
18921
18922 if let Some(project) = self.project.clone() {
18923 self.save(
18924 SaveOptions {
18925 format: true,
18926 autosave: false,
18927 },
18928 project,
18929 window,
18930 cx,
18931 )
18932 .detach_and_log_err(cx);
18933 }
18934 }
18935
18936 pub(crate) fn apply_selected_diff_hunks(
18937 &mut self,
18938 _: &ApplyDiffHunk,
18939 window: &mut Window,
18940 cx: &mut Context<Self>,
18941 ) {
18942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18943 let snapshot = self.snapshot(window, cx);
18944 let hunks = snapshot.hunks_for_ranges(
18945 self.selections
18946 .all(&snapshot.display_snapshot)
18947 .into_iter()
18948 .map(|selection| selection.range()),
18949 );
18950 let mut ranges_by_buffer = HashMap::default();
18951 self.transact(window, cx, |editor, _window, cx| {
18952 for hunk in hunks {
18953 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18954 ranges_by_buffer
18955 .entry(buffer.clone())
18956 .or_insert_with(Vec::new)
18957 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18958 }
18959 }
18960
18961 for (buffer, ranges) in ranges_by_buffer {
18962 buffer.update(cx, |buffer, cx| {
18963 buffer.merge_into_base(ranges, cx);
18964 });
18965 }
18966 });
18967
18968 if let Some(project) = self.project.clone() {
18969 self.save(
18970 SaveOptions {
18971 format: true,
18972 autosave: false,
18973 },
18974 project,
18975 window,
18976 cx,
18977 )
18978 .detach_and_log_err(cx);
18979 }
18980 }
18981
18982 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18983 if hovered != self.gutter_hovered {
18984 self.gutter_hovered = hovered;
18985 cx.notify();
18986 }
18987 }
18988
18989 pub fn insert_blocks(
18990 &mut self,
18991 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18992 autoscroll: Option<Autoscroll>,
18993 cx: &mut Context<Self>,
18994 ) -> Vec<CustomBlockId> {
18995 let blocks = self
18996 .display_map
18997 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18998 if let Some(autoscroll) = autoscroll {
18999 self.request_autoscroll(autoscroll, cx);
19000 }
19001 cx.notify();
19002 blocks
19003 }
19004
19005 pub fn resize_blocks(
19006 &mut self,
19007 heights: HashMap<CustomBlockId, u32>,
19008 autoscroll: Option<Autoscroll>,
19009 cx: &mut Context<Self>,
19010 ) {
19011 self.display_map
19012 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19013 if let Some(autoscroll) = autoscroll {
19014 self.request_autoscroll(autoscroll, cx);
19015 }
19016 cx.notify();
19017 }
19018
19019 pub fn replace_blocks(
19020 &mut self,
19021 renderers: HashMap<CustomBlockId, RenderBlock>,
19022 autoscroll: Option<Autoscroll>,
19023 cx: &mut Context<Self>,
19024 ) {
19025 self.display_map
19026 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19027 if let Some(autoscroll) = autoscroll {
19028 self.request_autoscroll(autoscroll, cx);
19029 }
19030 cx.notify();
19031 }
19032
19033 pub fn remove_blocks(
19034 &mut self,
19035 block_ids: HashSet<CustomBlockId>,
19036 autoscroll: Option<Autoscroll>,
19037 cx: &mut Context<Self>,
19038 ) {
19039 self.display_map.update(cx, |display_map, cx| {
19040 display_map.remove_blocks(block_ids, cx)
19041 });
19042 if let Some(autoscroll) = autoscroll {
19043 self.request_autoscroll(autoscroll, cx);
19044 }
19045 cx.notify();
19046 }
19047
19048 pub fn row_for_block(
19049 &self,
19050 block_id: CustomBlockId,
19051 cx: &mut Context<Self>,
19052 ) -> Option<DisplayRow> {
19053 self.display_map
19054 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19055 }
19056
19057 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19058 self.focused_block = Some(focused_block);
19059 }
19060
19061 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19062 self.focused_block.take()
19063 }
19064
19065 pub fn insert_creases(
19066 &mut self,
19067 creases: impl IntoIterator<Item = Crease<Anchor>>,
19068 cx: &mut Context<Self>,
19069 ) -> Vec<CreaseId> {
19070 self.display_map
19071 .update(cx, |map, cx| map.insert_creases(creases, cx))
19072 }
19073
19074 pub fn remove_creases(
19075 &mut self,
19076 ids: impl IntoIterator<Item = CreaseId>,
19077 cx: &mut Context<Self>,
19078 ) -> Vec<(CreaseId, Range<Anchor>)> {
19079 self.display_map
19080 .update(cx, |map, cx| map.remove_creases(ids, cx))
19081 }
19082
19083 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19084 self.display_map
19085 .update(cx, |map, cx| map.snapshot(cx))
19086 .longest_row()
19087 }
19088
19089 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19090 self.display_map
19091 .update(cx, |map, cx| map.snapshot(cx))
19092 .max_point()
19093 }
19094
19095 pub fn text(&self, cx: &App) -> String {
19096 self.buffer.read(cx).read(cx).text()
19097 }
19098
19099 pub fn is_empty(&self, cx: &App) -> bool {
19100 self.buffer.read(cx).read(cx).is_empty()
19101 }
19102
19103 pub fn text_option(&self, cx: &App) -> Option<String> {
19104 let text = self.text(cx);
19105 let text = text.trim();
19106
19107 if text.is_empty() {
19108 return None;
19109 }
19110
19111 Some(text.to_string())
19112 }
19113
19114 pub fn set_text(
19115 &mut self,
19116 text: impl Into<Arc<str>>,
19117 window: &mut Window,
19118 cx: &mut Context<Self>,
19119 ) {
19120 self.transact(window, cx, |this, _, cx| {
19121 this.buffer
19122 .read(cx)
19123 .as_singleton()
19124 .expect("you can only call set_text on editors for singleton buffers")
19125 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19126 });
19127 }
19128
19129 pub fn display_text(&self, cx: &mut App) -> String {
19130 self.display_map
19131 .update(cx, |map, cx| map.snapshot(cx))
19132 .text()
19133 }
19134
19135 fn create_minimap(
19136 &self,
19137 minimap_settings: MinimapSettings,
19138 window: &mut Window,
19139 cx: &mut Context<Self>,
19140 ) -> Option<Entity<Self>> {
19141 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19142 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19143 }
19144
19145 fn initialize_new_minimap(
19146 &self,
19147 minimap_settings: MinimapSettings,
19148 window: &mut Window,
19149 cx: &mut Context<Self>,
19150 ) -> Entity<Self> {
19151 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19152
19153 let mut minimap = Editor::new_internal(
19154 EditorMode::Minimap {
19155 parent: cx.weak_entity(),
19156 },
19157 self.buffer.clone(),
19158 None,
19159 Some(self.display_map.clone()),
19160 window,
19161 cx,
19162 );
19163 minimap.scroll_manager.clone_state(&self.scroll_manager);
19164 minimap.set_text_style_refinement(TextStyleRefinement {
19165 font_size: Some(MINIMAP_FONT_SIZE),
19166 font_weight: Some(MINIMAP_FONT_WEIGHT),
19167 ..Default::default()
19168 });
19169 minimap.update_minimap_configuration(minimap_settings, cx);
19170 cx.new(|_| minimap)
19171 }
19172
19173 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19174 let current_line_highlight = minimap_settings
19175 .current_line_highlight
19176 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19177 self.set_current_line_highlight(Some(current_line_highlight));
19178 }
19179
19180 pub fn minimap(&self) -> Option<&Entity<Self>> {
19181 self.minimap
19182 .as_ref()
19183 .filter(|_| self.minimap_visibility.visible())
19184 }
19185
19186 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19187 let mut wrap_guides = smallvec![];
19188
19189 if self.show_wrap_guides == Some(false) {
19190 return wrap_guides;
19191 }
19192
19193 let settings = self.buffer.read(cx).language_settings(cx);
19194 if settings.show_wrap_guides {
19195 match self.soft_wrap_mode(cx) {
19196 SoftWrap::Column(soft_wrap) => {
19197 wrap_guides.push((soft_wrap as usize, true));
19198 }
19199 SoftWrap::Bounded(soft_wrap) => {
19200 wrap_guides.push((soft_wrap as usize, true));
19201 }
19202 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19203 }
19204 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19205 }
19206
19207 wrap_guides
19208 }
19209
19210 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19211 let settings = self.buffer.read(cx).language_settings(cx);
19212 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19213 match mode {
19214 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19215 SoftWrap::None
19216 }
19217 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19218 language_settings::SoftWrap::PreferredLineLength => {
19219 SoftWrap::Column(settings.preferred_line_length)
19220 }
19221 language_settings::SoftWrap::Bounded => {
19222 SoftWrap::Bounded(settings.preferred_line_length)
19223 }
19224 }
19225 }
19226
19227 pub fn set_soft_wrap_mode(
19228 &mut self,
19229 mode: language_settings::SoftWrap,
19230
19231 cx: &mut Context<Self>,
19232 ) {
19233 self.soft_wrap_mode_override = Some(mode);
19234 cx.notify();
19235 }
19236
19237 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19238 self.hard_wrap = hard_wrap;
19239 cx.notify();
19240 }
19241
19242 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19243 self.text_style_refinement = Some(style);
19244 }
19245
19246 /// called by the Element so we know what style we were most recently rendered with.
19247 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19248 // We intentionally do not inform the display map about the minimap style
19249 // so that wrapping is not recalculated and stays consistent for the editor
19250 // and its linked minimap.
19251 if !self.mode.is_minimap() {
19252 let font = style.text.font();
19253 let font_size = style.text.font_size.to_pixels(window.rem_size());
19254 let display_map = self
19255 .placeholder_display_map
19256 .as_ref()
19257 .filter(|_| self.is_empty(cx))
19258 .unwrap_or(&self.display_map);
19259
19260 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19261 }
19262 self.style = Some(style);
19263 }
19264
19265 pub fn style(&self) -> Option<&EditorStyle> {
19266 self.style.as_ref()
19267 }
19268
19269 // Called by the element. This method is not designed to be called outside of the editor
19270 // element's layout code because it does not notify when rewrapping is computed synchronously.
19271 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19272 if self.is_empty(cx) {
19273 self.placeholder_display_map
19274 .as_ref()
19275 .map_or(false, |display_map| {
19276 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19277 })
19278 } else {
19279 self.display_map
19280 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19281 }
19282 }
19283
19284 pub fn set_soft_wrap(&mut self) {
19285 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19286 }
19287
19288 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19289 if self.soft_wrap_mode_override.is_some() {
19290 self.soft_wrap_mode_override.take();
19291 } else {
19292 let soft_wrap = match self.soft_wrap_mode(cx) {
19293 SoftWrap::GitDiff => return,
19294 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19295 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19296 language_settings::SoftWrap::None
19297 }
19298 };
19299 self.soft_wrap_mode_override = Some(soft_wrap);
19300 }
19301 cx.notify();
19302 }
19303
19304 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19305 let Some(workspace) = self.workspace() else {
19306 return;
19307 };
19308 let fs = workspace.read(cx).app_state().fs.clone();
19309 let current_show = TabBarSettings::get_global(cx).show;
19310 update_settings_file(fs, cx, move |setting, _| {
19311 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19312 });
19313 }
19314
19315 pub fn toggle_indent_guides(
19316 &mut self,
19317 _: &ToggleIndentGuides,
19318 _: &mut Window,
19319 cx: &mut Context<Self>,
19320 ) {
19321 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19322 self.buffer
19323 .read(cx)
19324 .language_settings(cx)
19325 .indent_guides
19326 .enabled
19327 });
19328 self.show_indent_guides = Some(!currently_enabled);
19329 cx.notify();
19330 }
19331
19332 fn should_show_indent_guides(&self) -> Option<bool> {
19333 self.show_indent_guides
19334 }
19335
19336 pub fn toggle_line_numbers(
19337 &mut self,
19338 _: &ToggleLineNumbers,
19339 _: &mut Window,
19340 cx: &mut Context<Self>,
19341 ) {
19342 let mut editor_settings = EditorSettings::get_global(cx).clone();
19343 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19344 EditorSettings::override_global(editor_settings, cx);
19345 }
19346
19347 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19348 if let Some(show_line_numbers) = self.show_line_numbers {
19349 return show_line_numbers;
19350 }
19351 EditorSettings::get_global(cx).gutter.line_numbers
19352 }
19353
19354 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19355 self.use_relative_line_numbers
19356 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19357 }
19358
19359 pub fn toggle_relative_line_numbers(
19360 &mut self,
19361 _: &ToggleRelativeLineNumbers,
19362 _: &mut Window,
19363 cx: &mut Context<Self>,
19364 ) {
19365 let is_relative = self.should_use_relative_line_numbers(cx);
19366 self.set_relative_line_number(Some(!is_relative), cx)
19367 }
19368
19369 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19370 self.use_relative_line_numbers = is_relative;
19371 cx.notify();
19372 }
19373
19374 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19375 self.show_gutter = show_gutter;
19376 cx.notify();
19377 }
19378
19379 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19380 self.show_scrollbars = ScrollbarAxes {
19381 horizontal: show,
19382 vertical: show,
19383 };
19384 cx.notify();
19385 }
19386
19387 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19388 self.show_scrollbars.vertical = show;
19389 cx.notify();
19390 }
19391
19392 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19393 self.show_scrollbars.horizontal = show;
19394 cx.notify();
19395 }
19396
19397 pub fn set_minimap_visibility(
19398 &mut self,
19399 minimap_visibility: MinimapVisibility,
19400 window: &mut Window,
19401 cx: &mut Context<Self>,
19402 ) {
19403 if self.minimap_visibility != minimap_visibility {
19404 if minimap_visibility.visible() && self.minimap.is_none() {
19405 let minimap_settings = EditorSettings::get_global(cx).minimap;
19406 self.minimap =
19407 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19408 }
19409 self.minimap_visibility = minimap_visibility;
19410 cx.notify();
19411 }
19412 }
19413
19414 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19415 self.set_show_scrollbars(false, cx);
19416 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19417 }
19418
19419 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19420 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19421 }
19422
19423 /// Normally the text in full mode and auto height editors is padded on the
19424 /// left side by roughly half a character width for improved hit testing.
19425 ///
19426 /// Use this method to disable this for cases where this is not wanted (e.g.
19427 /// if you want to align the editor text with some other text above or below)
19428 /// or if you want to add this padding to single-line editors.
19429 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19430 self.offset_content = offset_content;
19431 cx.notify();
19432 }
19433
19434 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19435 self.show_line_numbers = Some(show_line_numbers);
19436 cx.notify();
19437 }
19438
19439 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19440 self.disable_expand_excerpt_buttons = true;
19441 cx.notify();
19442 }
19443
19444 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19445 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19446 cx.notify();
19447 }
19448
19449 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19450 self.show_code_actions = Some(show_code_actions);
19451 cx.notify();
19452 }
19453
19454 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19455 self.show_runnables = Some(show_runnables);
19456 cx.notify();
19457 }
19458
19459 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19460 self.show_breakpoints = Some(show_breakpoints);
19461 cx.notify();
19462 }
19463
19464 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19465 if self.display_map.read(cx).masked != masked {
19466 self.display_map.update(cx, |map, _| map.masked = masked);
19467 }
19468 cx.notify()
19469 }
19470
19471 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19472 self.show_wrap_guides = Some(show_wrap_guides);
19473 cx.notify();
19474 }
19475
19476 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19477 self.show_indent_guides = Some(show_indent_guides);
19478 cx.notify();
19479 }
19480
19481 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19482 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19483 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19484 && let Some(dir) = file.abs_path(cx).parent()
19485 {
19486 return Some(dir.to_owned());
19487 }
19488 }
19489
19490 None
19491 }
19492
19493 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19494 self.active_excerpt(cx)?
19495 .1
19496 .read(cx)
19497 .file()
19498 .and_then(|f| f.as_local())
19499 }
19500
19501 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19502 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19503 let buffer = buffer.read(cx);
19504 if let Some(project_path) = buffer.project_path(cx) {
19505 let project = self.project()?.read(cx);
19506 project.absolute_path(&project_path, cx)
19507 } else {
19508 buffer
19509 .file()
19510 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19511 }
19512 })
19513 }
19514
19515 pub fn reveal_in_finder(
19516 &mut self,
19517 _: &RevealInFileManager,
19518 _window: &mut Window,
19519 cx: &mut Context<Self>,
19520 ) {
19521 if let Some(target) = self.target_file(cx) {
19522 cx.reveal_path(&target.abs_path(cx));
19523 }
19524 }
19525
19526 pub fn copy_path(
19527 &mut self,
19528 _: &zed_actions::workspace::CopyPath,
19529 _window: &mut Window,
19530 cx: &mut Context<Self>,
19531 ) {
19532 if let Some(path) = self.target_file_abs_path(cx)
19533 && let Some(path) = path.to_str()
19534 {
19535 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19536 } else {
19537 cx.propagate();
19538 }
19539 }
19540
19541 pub fn copy_relative_path(
19542 &mut self,
19543 _: &zed_actions::workspace::CopyRelativePath,
19544 _window: &mut Window,
19545 cx: &mut Context<Self>,
19546 ) {
19547 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19548 let project = self.project()?.read(cx);
19549 let path = buffer.read(cx).file()?.path();
19550 let path = path.display(project.path_style(cx));
19551 Some(path)
19552 }) {
19553 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19554 } else {
19555 cx.propagate();
19556 }
19557 }
19558
19559 /// Returns the project path for the editor's buffer, if any buffer is
19560 /// opened in the editor.
19561 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19562 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19563 buffer.read(cx).project_path(cx)
19564 } else {
19565 None
19566 }
19567 }
19568
19569 // Returns true if the editor handled a go-to-line request
19570 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19571 maybe!({
19572 let breakpoint_store = self.breakpoint_store.as_ref()?;
19573
19574 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19575 else {
19576 self.clear_row_highlights::<ActiveDebugLine>();
19577 return None;
19578 };
19579
19580 let position = active_stack_frame.position;
19581 let buffer_id = position.buffer_id?;
19582 let snapshot = self
19583 .project
19584 .as_ref()?
19585 .read(cx)
19586 .buffer_for_id(buffer_id, cx)?
19587 .read(cx)
19588 .snapshot();
19589
19590 let mut handled = false;
19591 for (id, ExcerptRange { context, .. }) in
19592 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19593 {
19594 if context.start.cmp(&position, &snapshot).is_ge()
19595 || context.end.cmp(&position, &snapshot).is_lt()
19596 {
19597 continue;
19598 }
19599 let snapshot = self.buffer.read(cx).snapshot(cx);
19600 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19601
19602 handled = true;
19603 self.clear_row_highlights::<ActiveDebugLine>();
19604
19605 self.go_to_line::<ActiveDebugLine>(
19606 multibuffer_anchor,
19607 Some(cx.theme().colors().editor_debugger_active_line_background),
19608 window,
19609 cx,
19610 );
19611
19612 cx.notify();
19613 }
19614
19615 handled.then_some(())
19616 })
19617 .is_some()
19618 }
19619
19620 pub fn copy_file_name_without_extension(
19621 &mut self,
19622 _: &CopyFileNameWithoutExtension,
19623 _: &mut Window,
19624 cx: &mut Context<Self>,
19625 ) {
19626 if let Some(file) = self.target_file(cx)
19627 && let Some(file_stem) = file.path().file_stem()
19628 {
19629 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19630 }
19631 }
19632
19633 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19634 if let Some(file) = self.target_file(cx)
19635 && let Some(name) = file.path().file_name()
19636 {
19637 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19638 }
19639 }
19640
19641 pub fn toggle_git_blame(
19642 &mut self,
19643 _: &::git::Blame,
19644 window: &mut Window,
19645 cx: &mut Context<Self>,
19646 ) {
19647 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19648
19649 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19650 self.start_git_blame(true, window, cx);
19651 }
19652
19653 cx.notify();
19654 }
19655
19656 pub fn toggle_git_blame_inline(
19657 &mut self,
19658 _: &ToggleGitBlameInline,
19659 window: &mut Window,
19660 cx: &mut Context<Self>,
19661 ) {
19662 self.toggle_git_blame_inline_internal(true, window, cx);
19663 cx.notify();
19664 }
19665
19666 pub fn open_git_blame_commit(
19667 &mut self,
19668 _: &OpenGitBlameCommit,
19669 window: &mut Window,
19670 cx: &mut Context<Self>,
19671 ) {
19672 self.open_git_blame_commit_internal(window, cx);
19673 }
19674
19675 fn open_git_blame_commit_internal(
19676 &mut self,
19677 window: &mut Window,
19678 cx: &mut Context<Self>,
19679 ) -> Option<()> {
19680 let blame = self.blame.as_ref()?;
19681 let snapshot = self.snapshot(window, cx);
19682 let cursor = self
19683 .selections
19684 .newest::<Point>(&snapshot.display_snapshot)
19685 .head();
19686 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19687 let (_, blame_entry) = blame
19688 .update(cx, |blame, cx| {
19689 blame
19690 .blame_for_rows(
19691 &[RowInfo {
19692 buffer_id: Some(buffer.remote_id()),
19693 buffer_row: Some(point.row),
19694 ..Default::default()
19695 }],
19696 cx,
19697 )
19698 .next()
19699 })
19700 .flatten()?;
19701 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19702 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19703 let workspace = self.workspace()?.downgrade();
19704 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19705 None
19706 }
19707
19708 pub fn git_blame_inline_enabled(&self) -> bool {
19709 self.git_blame_inline_enabled
19710 }
19711
19712 pub fn toggle_selection_menu(
19713 &mut self,
19714 _: &ToggleSelectionMenu,
19715 _: &mut Window,
19716 cx: &mut Context<Self>,
19717 ) {
19718 self.show_selection_menu = self
19719 .show_selection_menu
19720 .map(|show_selections_menu| !show_selections_menu)
19721 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19722
19723 cx.notify();
19724 }
19725
19726 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19727 self.show_selection_menu
19728 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19729 }
19730
19731 fn start_git_blame(
19732 &mut self,
19733 user_triggered: bool,
19734 window: &mut Window,
19735 cx: &mut Context<Self>,
19736 ) {
19737 if let Some(project) = self.project() {
19738 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19739 && buffer.read(cx).file().is_none()
19740 {
19741 return;
19742 }
19743
19744 let focused = self.focus_handle(cx).contains_focused(window, cx);
19745
19746 let project = project.clone();
19747 let blame = cx
19748 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19749 self.blame_subscription =
19750 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19751 self.blame = Some(blame);
19752 }
19753 }
19754
19755 fn toggle_git_blame_inline_internal(
19756 &mut self,
19757 user_triggered: bool,
19758 window: &mut Window,
19759 cx: &mut Context<Self>,
19760 ) {
19761 if self.git_blame_inline_enabled {
19762 self.git_blame_inline_enabled = false;
19763 self.show_git_blame_inline = false;
19764 self.show_git_blame_inline_delay_task.take();
19765 } else {
19766 self.git_blame_inline_enabled = true;
19767 self.start_git_blame_inline(user_triggered, window, cx);
19768 }
19769
19770 cx.notify();
19771 }
19772
19773 fn start_git_blame_inline(
19774 &mut self,
19775 user_triggered: bool,
19776 window: &mut Window,
19777 cx: &mut Context<Self>,
19778 ) {
19779 self.start_git_blame(user_triggered, window, cx);
19780
19781 if ProjectSettings::get_global(cx)
19782 .git
19783 .inline_blame_delay()
19784 .is_some()
19785 {
19786 self.start_inline_blame_timer(window, cx);
19787 } else {
19788 self.show_git_blame_inline = true
19789 }
19790 }
19791
19792 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19793 self.blame.as_ref()
19794 }
19795
19796 pub fn show_git_blame_gutter(&self) -> bool {
19797 self.show_git_blame_gutter
19798 }
19799
19800 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19801 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19802 }
19803
19804 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19805 self.show_git_blame_inline
19806 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19807 && !self.newest_selection_head_on_empty_line(cx)
19808 && self.has_blame_entries(cx)
19809 }
19810
19811 fn has_blame_entries(&self, cx: &App) -> bool {
19812 self.blame()
19813 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19814 }
19815
19816 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19817 let cursor_anchor = self.selections.newest_anchor().head();
19818
19819 let snapshot = self.buffer.read(cx).snapshot(cx);
19820 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19821
19822 snapshot.line_len(buffer_row) == 0
19823 }
19824
19825 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19826 let buffer_and_selection = maybe!({
19827 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19828 let selection_range = selection.range();
19829
19830 let multi_buffer = self.buffer().read(cx);
19831 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19832 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19833
19834 let (buffer, range, _) = if selection.reversed {
19835 buffer_ranges.first()
19836 } else {
19837 buffer_ranges.last()
19838 }?;
19839
19840 let selection = text::ToPoint::to_point(&range.start, buffer).row
19841 ..text::ToPoint::to_point(&range.end, buffer).row;
19842 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19843 });
19844
19845 let Some((buffer, selection)) = buffer_and_selection else {
19846 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19847 };
19848
19849 let Some(project) = self.project() else {
19850 return Task::ready(Err(anyhow!("editor does not have project")));
19851 };
19852
19853 project.update(cx, |project, cx| {
19854 project.get_permalink_to_line(&buffer, selection, cx)
19855 })
19856 }
19857
19858 pub fn copy_permalink_to_line(
19859 &mut self,
19860 _: &CopyPermalinkToLine,
19861 window: &mut Window,
19862 cx: &mut Context<Self>,
19863 ) {
19864 let permalink_task = self.get_permalink_to_line(cx);
19865 let workspace = self.workspace();
19866
19867 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19868 Ok(permalink) => {
19869 cx.update(|_, cx| {
19870 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19871 })
19872 .ok();
19873 }
19874 Err(err) => {
19875 let message = format!("Failed to copy permalink: {err}");
19876
19877 anyhow::Result::<()>::Err(err).log_err();
19878
19879 if let Some(workspace) = workspace {
19880 workspace
19881 .update_in(cx, |workspace, _, cx| {
19882 struct CopyPermalinkToLine;
19883
19884 workspace.show_toast(
19885 Toast::new(
19886 NotificationId::unique::<CopyPermalinkToLine>(),
19887 message,
19888 ),
19889 cx,
19890 )
19891 })
19892 .ok();
19893 }
19894 }
19895 })
19896 .detach();
19897 }
19898
19899 pub fn copy_file_location(
19900 &mut self,
19901 _: &CopyFileLocation,
19902 _: &mut Window,
19903 cx: &mut Context<Self>,
19904 ) {
19905 let selection = self
19906 .selections
19907 .newest::<Point>(&self.display_snapshot(cx))
19908 .start
19909 .row
19910 + 1;
19911 if let Some(file) = self.target_file(cx) {
19912 let path = file.path().display(file.path_style(cx));
19913 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19914 }
19915 }
19916
19917 pub fn open_permalink_to_line(
19918 &mut self,
19919 _: &OpenPermalinkToLine,
19920 window: &mut Window,
19921 cx: &mut Context<Self>,
19922 ) {
19923 let permalink_task = self.get_permalink_to_line(cx);
19924 let workspace = self.workspace();
19925
19926 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19927 Ok(permalink) => {
19928 cx.update(|_, cx| {
19929 cx.open_url(permalink.as_ref());
19930 })
19931 .ok();
19932 }
19933 Err(err) => {
19934 let message = format!("Failed to open permalink: {err}");
19935
19936 anyhow::Result::<()>::Err(err).log_err();
19937
19938 if let Some(workspace) = workspace {
19939 workspace
19940 .update(cx, |workspace, cx| {
19941 struct OpenPermalinkToLine;
19942
19943 workspace.show_toast(
19944 Toast::new(
19945 NotificationId::unique::<OpenPermalinkToLine>(),
19946 message,
19947 ),
19948 cx,
19949 )
19950 })
19951 .ok();
19952 }
19953 }
19954 })
19955 .detach();
19956 }
19957
19958 pub fn insert_uuid_v4(
19959 &mut self,
19960 _: &InsertUuidV4,
19961 window: &mut Window,
19962 cx: &mut Context<Self>,
19963 ) {
19964 self.insert_uuid(UuidVersion::V4, window, cx);
19965 }
19966
19967 pub fn insert_uuid_v7(
19968 &mut self,
19969 _: &InsertUuidV7,
19970 window: &mut Window,
19971 cx: &mut Context<Self>,
19972 ) {
19973 self.insert_uuid(UuidVersion::V7, window, cx);
19974 }
19975
19976 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19978 self.transact(window, cx, |this, window, cx| {
19979 let edits = this
19980 .selections
19981 .all::<Point>(&this.display_snapshot(cx))
19982 .into_iter()
19983 .map(|selection| {
19984 let uuid = match version {
19985 UuidVersion::V4 => uuid::Uuid::new_v4(),
19986 UuidVersion::V7 => uuid::Uuid::now_v7(),
19987 };
19988
19989 (selection.range(), uuid.to_string())
19990 });
19991 this.edit(edits, cx);
19992 this.refresh_edit_prediction(true, false, window, cx);
19993 });
19994 }
19995
19996 pub fn open_selections_in_multibuffer(
19997 &mut self,
19998 _: &OpenSelectionsInMultibuffer,
19999 window: &mut Window,
20000 cx: &mut Context<Self>,
20001 ) {
20002 let multibuffer = self.buffer.read(cx);
20003
20004 let Some(buffer) = multibuffer.as_singleton() else {
20005 return;
20006 };
20007
20008 let Some(workspace) = self.workspace() else {
20009 return;
20010 };
20011
20012 let title = multibuffer.title(cx).to_string();
20013
20014 let locations = self
20015 .selections
20016 .all_anchors(cx)
20017 .iter()
20018 .map(|selection| {
20019 (
20020 buffer.clone(),
20021 (selection.start.text_anchor..selection.end.text_anchor)
20022 .to_point(buffer.read(cx)),
20023 )
20024 })
20025 .into_group_map();
20026
20027 cx.spawn_in(window, async move |_, cx| {
20028 workspace.update_in(cx, |workspace, window, cx| {
20029 Self::open_locations_in_multibuffer(
20030 workspace,
20031 locations,
20032 format!("Selections for '{title}'"),
20033 false,
20034 MultibufferSelectionMode::All,
20035 window,
20036 cx,
20037 );
20038 })
20039 })
20040 .detach();
20041 }
20042
20043 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20044 /// last highlight added will be used.
20045 ///
20046 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20047 pub fn highlight_rows<T: 'static>(
20048 &mut self,
20049 range: Range<Anchor>,
20050 color: Hsla,
20051 options: RowHighlightOptions,
20052 cx: &mut Context<Self>,
20053 ) {
20054 let snapshot = self.buffer().read(cx).snapshot(cx);
20055 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20056 let ix = row_highlights.binary_search_by(|highlight| {
20057 Ordering::Equal
20058 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20059 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20060 });
20061
20062 if let Err(mut ix) = ix {
20063 let index = post_inc(&mut self.highlight_order);
20064
20065 // If this range intersects with the preceding highlight, then merge it with
20066 // the preceding highlight. Otherwise insert a new highlight.
20067 let mut merged = false;
20068 if ix > 0 {
20069 let prev_highlight = &mut row_highlights[ix - 1];
20070 if prev_highlight
20071 .range
20072 .end
20073 .cmp(&range.start, &snapshot)
20074 .is_ge()
20075 {
20076 ix -= 1;
20077 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20078 prev_highlight.range.end = range.end;
20079 }
20080 merged = true;
20081 prev_highlight.index = index;
20082 prev_highlight.color = color;
20083 prev_highlight.options = options;
20084 }
20085 }
20086
20087 if !merged {
20088 row_highlights.insert(
20089 ix,
20090 RowHighlight {
20091 range,
20092 index,
20093 color,
20094 options,
20095 type_id: TypeId::of::<T>(),
20096 },
20097 );
20098 }
20099
20100 // If any of the following highlights intersect with this one, merge them.
20101 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20102 let highlight = &row_highlights[ix];
20103 if next_highlight
20104 .range
20105 .start
20106 .cmp(&highlight.range.end, &snapshot)
20107 .is_le()
20108 {
20109 if next_highlight
20110 .range
20111 .end
20112 .cmp(&highlight.range.end, &snapshot)
20113 .is_gt()
20114 {
20115 row_highlights[ix].range.end = next_highlight.range.end;
20116 }
20117 row_highlights.remove(ix + 1);
20118 } else {
20119 break;
20120 }
20121 }
20122 }
20123 }
20124
20125 /// Remove any highlighted row ranges of the given type that intersect the
20126 /// given ranges.
20127 pub fn remove_highlighted_rows<T: 'static>(
20128 &mut self,
20129 ranges_to_remove: Vec<Range<Anchor>>,
20130 cx: &mut Context<Self>,
20131 ) {
20132 let snapshot = self.buffer().read(cx).snapshot(cx);
20133 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20134 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20135 row_highlights.retain(|highlight| {
20136 while let Some(range_to_remove) = ranges_to_remove.peek() {
20137 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20138 Ordering::Less | Ordering::Equal => {
20139 ranges_to_remove.next();
20140 }
20141 Ordering::Greater => {
20142 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20143 Ordering::Less | Ordering::Equal => {
20144 return false;
20145 }
20146 Ordering::Greater => break,
20147 }
20148 }
20149 }
20150 }
20151
20152 true
20153 })
20154 }
20155
20156 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20157 pub fn clear_row_highlights<T: 'static>(&mut self) {
20158 self.highlighted_rows.remove(&TypeId::of::<T>());
20159 }
20160
20161 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20162 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20163 self.highlighted_rows
20164 .get(&TypeId::of::<T>())
20165 .map_or(&[] as &[_], |vec| vec.as_slice())
20166 .iter()
20167 .map(|highlight| (highlight.range.clone(), highlight.color))
20168 }
20169
20170 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20171 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20172 /// Allows to ignore certain kinds of highlights.
20173 pub fn highlighted_display_rows(
20174 &self,
20175 window: &mut Window,
20176 cx: &mut App,
20177 ) -> BTreeMap<DisplayRow, LineHighlight> {
20178 let snapshot = self.snapshot(window, cx);
20179 let mut used_highlight_orders = HashMap::default();
20180 self.highlighted_rows
20181 .iter()
20182 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20183 .fold(
20184 BTreeMap::<DisplayRow, LineHighlight>::new(),
20185 |mut unique_rows, highlight| {
20186 let start = highlight.range.start.to_display_point(&snapshot);
20187 let end = highlight.range.end.to_display_point(&snapshot);
20188 let start_row = start.row().0;
20189 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20190 && end.column() == 0
20191 {
20192 end.row().0.saturating_sub(1)
20193 } else {
20194 end.row().0
20195 };
20196 for row in start_row..=end_row {
20197 let used_index =
20198 used_highlight_orders.entry(row).or_insert(highlight.index);
20199 if highlight.index >= *used_index {
20200 *used_index = highlight.index;
20201 unique_rows.insert(
20202 DisplayRow(row),
20203 LineHighlight {
20204 include_gutter: highlight.options.include_gutter,
20205 border: None,
20206 background: highlight.color.into(),
20207 type_id: Some(highlight.type_id),
20208 },
20209 );
20210 }
20211 }
20212 unique_rows
20213 },
20214 )
20215 }
20216
20217 pub fn highlighted_display_row_for_autoscroll(
20218 &self,
20219 snapshot: &DisplaySnapshot,
20220 ) -> Option<DisplayRow> {
20221 self.highlighted_rows
20222 .values()
20223 .flat_map(|highlighted_rows| highlighted_rows.iter())
20224 .filter_map(|highlight| {
20225 if highlight.options.autoscroll {
20226 Some(highlight.range.start.to_display_point(snapshot).row())
20227 } else {
20228 None
20229 }
20230 })
20231 .min()
20232 }
20233
20234 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20235 self.highlight_background::<SearchWithinRange>(
20236 ranges,
20237 |colors| colors.colors().editor_document_highlight_read_background,
20238 cx,
20239 )
20240 }
20241
20242 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20243 self.breadcrumb_header = Some(new_header);
20244 }
20245
20246 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20247 self.clear_background_highlights::<SearchWithinRange>(cx);
20248 }
20249
20250 pub fn highlight_background<T: 'static>(
20251 &mut self,
20252 ranges: &[Range<Anchor>],
20253 color_fetcher: fn(&Theme) -> Hsla,
20254 cx: &mut Context<Self>,
20255 ) {
20256 self.background_highlights.insert(
20257 HighlightKey::Type(TypeId::of::<T>()),
20258 (color_fetcher, Arc::from(ranges)),
20259 );
20260 self.scrollbar_marker_state.dirty = true;
20261 cx.notify();
20262 }
20263
20264 pub fn highlight_background_key<T: 'static>(
20265 &mut self,
20266 key: usize,
20267 ranges: &[Range<Anchor>],
20268 color_fetcher: fn(&Theme) -> Hsla,
20269 cx: &mut Context<Self>,
20270 ) {
20271 self.background_highlights.insert(
20272 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20273 (color_fetcher, Arc::from(ranges)),
20274 );
20275 self.scrollbar_marker_state.dirty = true;
20276 cx.notify();
20277 }
20278
20279 pub fn clear_background_highlights<T: 'static>(
20280 &mut self,
20281 cx: &mut Context<Self>,
20282 ) -> Option<BackgroundHighlight> {
20283 let text_highlights = self
20284 .background_highlights
20285 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20286 if !text_highlights.1.is_empty() {
20287 self.scrollbar_marker_state.dirty = true;
20288 cx.notify();
20289 }
20290 Some(text_highlights)
20291 }
20292
20293 pub fn highlight_gutter<T: 'static>(
20294 &mut self,
20295 ranges: impl Into<Vec<Range<Anchor>>>,
20296 color_fetcher: fn(&App) -> Hsla,
20297 cx: &mut Context<Self>,
20298 ) {
20299 self.gutter_highlights
20300 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20301 cx.notify();
20302 }
20303
20304 pub fn clear_gutter_highlights<T: 'static>(
20305 &mut self,
20306 cx: &mut Context<Self>,
20307 ) -> Option<GutterHighlight> {
20308 cx.notify();
20309 self.gutter_highlights.remove(&TypeId::of::<T>())
20310 }
20311
20312 pub fn insert_gutter_highlight<T: 'static>(
20313 &mut self,
20314 range: Range<Anchor>,
20315 color_fetcher: fn(&App) -> Hsla,
20316 cx: &mut Context<Self>,
20317 ) {
20318 let snapshot = self.buffer().read(cx).snapshot(cx);
20319 let mut highlights = self
20320 .gutter_highlights
20321 .remove(&TypeId::of::<T>())
20322 .map(|(_, highlights)| highlights)
20323 .unwrap_or_default();
20324 let ix = highlights.binary_search_by(|highlight| {
20325 Ordering::Equal
20326 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20327 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20328 });
20329 if let Err(ix) = ix {
20330 highlights.insert(ix, range);
20331 }
20332 self.gutter_highlights
20333 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20334 }
20335
20336 pub fn remove_gutter_highlights<T: 'static>(
20337 &mut self,
20338 ranges_to_remove: Vec<Range<Anchor>>,
20339 cx: &mut Context<Self>,
20340 ) {
20341 let snapshot = self.buffer().read(cx).snapshot(cx);
20342 let Some((color_fetcher, mut gutter_highlights)) =
20343 self.gutter_highlights.remove(&TypeId::of::<T>())
20344 else {
20345 return;
20346 };
20347 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20348 gutter_highlights.retain(|highlight| {
20349 while let Some(range_to_remove) = ranges_to_remove.peek() {
20350 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20351 Ordering::Less | Ordering::Equal => {
20352 ranges_to_remove.next();
20353 }
20354 Ordering::Greater => {
20355 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20356 Ordering::Less | Ordering::Equal => {
20357 return false;
20358 }
20359 Ordering::Greater => break,
20360 }
20361 }
20362 }
20363 }
20364
20365 true
20366 });
20367 self.gutter_highlights
20368 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20369 }
20370
20371 #[cfg(feature = "test-support")]
20372 pub fn all_text_highlights(
20373 &self,
20374 window: &mut Window,
20375 cx: &mut Context<Self>,
20376 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20377 let snapshot = self.snapshot(window, cx);
20378 self.display_map.update(cx, |display_map, _| {
20379 display_map
20380 .all_text_highlights()
20381 .map(|highlight| {
20382 let (style, ranges) = highlight.as_ref();
20383 (
20384 *style,
20385 ranges
20386 .iter()
20387 .map(|range| range.clone().to_display_points(&snapshot))
20388 .collect(),
20389 )
20390 })
20391 .collect()
20392 })
20393 }
20394
20395 #[cfg(feature = "test-support")]
20396 pub fn all_text_background_highlights(
20397 &self,
20398 window: &mut Window,
20399 cx: &mut Context<Self>,
20400 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20401 let snapshot = self.snapshot(window, cx);
20402 let buffer = &snapshot.buffer_snapshot();
20403 let start = buffer.anchor_before(0);
20404 let end = buffer.anchor_after(buffer.len());
20405 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20406 }
20407
20408 #[cfg(any(test, feature = "test-support"))]
20409 pub fn sorted_background_highlights_in_range(
20410 &self,
20411 search_range: Range<Anchor>,
20412 display_snapshot: &DisplaySnapshot,
20413 theme: &Theme,
20414 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20415 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20416 res.sort_by(|a, b| {
20417 a.0.start
20418 .cmp(&b.0.start)
20419 .then_with(|| a.0.end.cmp(&b.0.end))
20420 .then_with(|| a.1.cmp(&b.1))
20421 });
20422 res
20423 }
20424
20425 #[cfg(feature = "test-support")]
20426 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20427 let snapshot = self.buffer().read(cx).snapshot(cx);
20428
20429 let highlights = self
20430 .background_highlights
20431 .get(&HighlightKey::Type(TypeId::of::<
20432 items::BufferSearchHighlights,
20433 >()));
20434
20435 if let Some((_color, ranges)) = highlights {
20436 ranges
20437 .iter()
20438 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20439 .collect_vec()
20440 } else {
20441 vec![]
20442 }
20443 }
20444
20445 fn document_highlights_for_position<'a>(
20446 &'a self,
20447 position: Anchor,
20448 buffer: &'a MultiBufferSnapshot,
20449 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20450 let read_highlights = self
20451 .background_highlights
20452 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20453 .map(|h| &h.1);
20454 let write_highlights = self
20455 .background_highlights
20456 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20457 .map(|h| &h.1);
20458 let left_position = position.bias_left(buffer);
20459 let right_position = position.bias_right(buffer);
20460 read_highlights
20461 .into_iter()
20462 .chain(write_highlights)
20463 .flat_map(move |ranges| {
20464 let start_ix = match ranges.binary_search_by(|probe| {
20465 let cmp = probe.end.cmp(&left_position, buffer);
20466 if cmp.is_ge() {
20467 Ordering::Greater
20468 } else {
20469 Ordering::Less
20470 }
20471 }) {
20472 Ok(i) | Err(i) => i,
20473 };
20474
20475 ranges[start_ix..]
20476 .iter()
20477 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20478 })
20479 }
20480
20481 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20482 self.background_highlights
20483 .get(&HighlightKey::Type(TypeId::of::<T>()))
20484 .is_some_and(|(_, highlights)| !highlights.is_empty())
20485 }
20486
20487 /// Returns all background highlights for a given range.
20488 ///
20489 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20490 pub fn background_highlights_in_range(
20491 &self,
20492 search_range: Range<Anchor>,
20493 display_snapshot: &DisplaySnapshot,
20494 theme: &Theme,
20495 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20496 let mut results = Vec::new();
20497 for (color_fetcher, ranges) in self.background_highlights.values() {
20498 let color = color_fetcher(theme);
20499 let start_ix = match ranges.binary_search_by(|probe| {
20500 let cmp = probe
20501 .end
20502 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20503 if cmp.is_gt() {
20504 Ordering::Greater
20505 } else {
20506 Ordering::Less
20507 }
20508 }) {
20509 Ok(i) | Err(i) => i,
20510 };
20511 for range in &ranges[start_ix..] {
20512 if range
20513 .start
20514 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20515 .is_ge()
20516 {
20517 break;
20518 }
20519
20520 let start = range.start.to_display_point(display_snapshot);
20521 let end = range.end.to_display_point(display_snapshot);
20522 results.push((start..end, color))
20523 }
20524 }
20525 results
20526 }
20527
20528 pub fn gutter_highlights_in_range(
20529 &self,
20530 search_range: Range<Anchor>,
20531 display_snapshot: &DisplaySnapshot,
20532 cx: &App,
20533 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20534 let mut results = Vec::new();
20535 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20536 let color = color_fetcher(cx);
20537 let start_ix = match ranges.binary_search_by(|probe| {
20538 let cmp = probe
20539 .end
20540 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20541 if cmp.is_gt() {
20542 Ordering::Greater
20543 } else {
20544 Ordering::Less
20545 }
20546 }) {
20547 Ok(i) | Err(i) => i,
20548 };
20549 for range in &ranges[start_ix..] {
20550 if range
20551 .start
20552 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20553 .is_ge()
20554 {
20555 break;
20556 }
20557
20558 let start = range.start.to_display_point(display_snapshot);
20559 let end = range.end.to_display_point(display_snapshot);
20560 results.push((start..end, color))
20561 }
20562 }
20563 results
20564 }
20565
20566 /// Get the text ranges corresponding to the redaction query
20567 pub fn redacted_ranges(
20568 &self,
20569 search_range: Range<Anchor>,
20570 display_snapshot: &DisplaySnapshot,
20571 cx: &App,
20572 ) -> Vec<Range<DisplayPoint>> {
20573 display_snapshot
20574 .buffer_snapshot()
20575 .redacted_ranges(search_range, |file| {
20576 if let Some(file) = file {
20577 file.is_private()
20578 && EditorSettings::get(
20579 Some(SettingsLocation {
20580 worktree_id: file.worktree_id(cx),
20581 path: file.path().as_ref(),
20582 }),
20583 cx,
20584 )
20585 .redact_private_values
20586 } else {
20587 false
20588 }
20589 })
20590 .map(|range| {
20591 range.start.to_display_point(display_snapshot)
20592 ..range.end.to_display_point(display_snapshot)
20593 })
20594 .collect()
20595 }
20596
20597 pub fn highlight_text_key<T: 'static>(
20598 &mut self,
20599 key: usize,
20600 ranges: Vec<Range<Anchor>>,
20601 style: HighlightStyle,
20602 cx: &mut Context<Self>,
20603 ) {
20604 self.display_map.update(cx, |map, _| {
20605 map.highlight_text(
20606 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20607 ranges,
20608 style,
20609 );
20610 });
20611 cx.notify();
20612 }
20613
20614 pub fn highlight_text<T: 'static>(
20615 &mut self,
20616 ranges: Vec<Range<Anchor>>,
20617 style: HighlightStyle,
20618 cx: &mut Context<Self>,
20619 ) {
20620 self.display_map.update(cx, |map, _| {
20621 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20622 });
20623 cx.notify();
20624 }
20625
20626 pub fn text_highlights<'a, T: 'static>(
20627 &'a self,
20628 cx: &'a App,
20629 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20630 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20631 }
20632
20633 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20634 let cleared = self
20635 .display_map
20636 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20637 if cleared {
20638 cx.notify();
20639 }
20640 }
20641
20642 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20643 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20644 && self.focus_handle.is_focused(window)
20645 }
20646
20647 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20648 self.show_cursor_when_unfocused = is_enabled;
20649 cx.notify();
20650 }
20651
20652 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20653 cx.notify();
20654 }
20655
20656 fn on_debug_session_event(
20657 &mut self,
20658 _session: Entity<Session>,
20659 event: &SessionEvent,
20660 cx: &mut Context<Self>,
20661 ) {
20662 if let SessionEvent::InvalidateInlineValue = event {
20663 self.refresh_inline_values(cx);
20664 }
20665 }
20666
20667 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20668 let Some(project) = self.project.clone() else {
20669 return;
20670 };
20671
20672 if !self.inline_value_cache.enabled {
20673 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20674 self.splice_inlays(&inlays, Vec::new(), cx);
20675 return;
20676 }
20677
20678 let current_execution_position = self
20679 .highlighted_rows
20680 .get(&TypeId::of::<ActiveDebugLine>())
20681 .and_then(|lines| lines.last().map(|line| line.range.end));
20682
20683 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20684 let inline_values = editor
20685 .update(cx, |editor, cx| {
20686 let Some(current_execution_position) = current_execution_position else {
20687 return Some(Task::ready(Ok(Vec::new())));
20688 };
20689
20690 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20691 let snapshot = buffer.snapshot(cx);
20692
20693 let excerpt = snapshot.excerpt_containing(
20694 current_execution_position..current_execution_position,
20695 )?;
20696
20697 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20698 })?;
20699
20700 let range =
20701 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20702
20703 project.inline_values(buffer, range, cx)
20704 })
20705 .ok()
20706 .flatten()?
20707 .await
20708 .context("refreshing debugger inlays")
20709 .log_err()?;
20710
20711 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20712
20713 for (buffer_id, inline_value) in inline_values
20714 .into_iter()
20715 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20716 {
20717 buffer_inline_values
20718 .entry(buffer_id)
20719 .or_default()
20720 .push(inline_value);
20721 }
20722
20723 editor
20724 .update(cx, |editor, cx| {
20725 let snapshot = editor.buffer.read(cx).snapshot(cx);
20726 let mut new_inlays = Vec::default();
20727
20728 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20729 let buffer_id = buffer_snapshot.remote_id();
20730 buffer_inline_values
20731 .get(&buffer_id)
20732 .into_iter()
20733 .flatten()
20734 .for_each(|hint| {
20735 let inlay = Inlay::debugger(
20736 post_inc(&mut editor.next_inlay_id),
20737 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20738 hint.text(),
20739 );
20740 if !inlay.text().chars().contains(&'\n') {
20741 new_inlays.push(inlay);
20742 }
20743 });
20744 }
20745
20746 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20747 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20748
20749 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20750 })
20751 .ok()?;
20752 Some(())
20753 });
20754 }
20755
20756 fn on_buffer_event(
20757 &mut self,
20758 multibuffer: &Entity<MultiBuffer>,
20759 event: &multi_buffer::Event,
20760 window: &mut Window,
20761 cx: &mut Context<Self>,
20762 ) {
20763 match event {
20764 multi_buffer::Event::Edited { edited_buffer } => {
20765 self.scrollbar_marker_state.dirty = true;
20766 self.active_indent_guides_state.dirty = true;
20767 self.refresh_active_diagnostics(cx);
20768 self.refresh_code_actions(window, cx);
20769 self.refresh_selected_text_highlights(true, window, cx);
20770 self.refresh_single_line_folds(window, cx);
20771 refresh_matching_bracket_highlights(self, cx);
20772 if self.has_active_edit_prediction() {
20773 self.update_visible_edit_prediction(window, cx);
20774 }
20775
20776 if let Some(buffer) = edited_buffer {
20777 if buffer.read(cx).file().is_none() {
20778 cx.emit(EditorEvent::TitleChanged);
20779 }
20780
20781 if self.project.is_some() {
20782 let buffer_id = buffer.read(cx).remote_id();
20783 self.register_buffer(buffer_id, cx);
20784 self.update_lsp_data(Some(buffer_id), window, cx);
20785 self.refresh_inlay_hints(
20786 InlayHintRefreshReason::BufferEdited(buffer_id),
20787 cx,
20788 );
20789 }
20790 }
20791
20792 cx.emit(EditorEvent::BufferEdited);
20793 cx.emit(SearchEvent::MatchesInvalidated);
20794
20795 let Some(project) = &self.project else { return };
20796 let (telemetry, is_via_ssh) = {
20797 let project = project.read(cx);
20798 let telemetry = project.client().telemetry().clone();
20799 let is_via_ssh = project.is_via_remote_server();
20800 (telemetry, is_via_ssh)
20801 };
20802 telemetry.log_edit_event("editor", is_via_ssh);
20803 }
20804 multi_buffer::Event::ExcerptsAdded {
20805 buffer,
20806 predecessor,
20807 excerpts,
20808 } => {
20809 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20810 let buffer_id = buffer.read(cx).remote_id();
20811 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20812 && let Some(project) = &self.project
20813 {
20814 update_uncommitted_diff_for_buffer(
20815 cx.entity(),
20816 project,
20817 [buffer.clone()],
20818 self.buffer.clone(),
20819 cx,
20820 )
20821 .detach();
20822 }
20823 self.update_lsp_data(Some(buffer_id), window, cx);
20824 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20825 cx.emit(EditorEvent::ExcerptsAdded {
20826 buffer: buffer.clone(),
20827 predecessor: *predecessor,
20828 excerpts: excerpts.clone(),
20829 });
20830 }
20831 multi_buffer::Event::ExcerptsRemoved {
20832 ids,
20833 removed_buffer_ids,
20834 } => {
20835 if let Some(inlay_hints) = &mut self.inlay_hints {
20836 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
20837 }
20838 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20839 for buffer_id in removed_buffer_ids {
20840 self.registered_buffers.remove(buffer_id);
20841 }
20842 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20843 cx.emit(EditorEvent::ExcerptsRemoved {
20844 ids: ids.clone(),
20845 removed_buffer_ids: removed_buffer_ids.clone(),
20846 });
20847 }
20848 multi_buffer::Event::ExcerptsEdited {
20849 excerpt_ids,
20850 buffer_ids,
20851 } => {
20852 self.display_map.update(cx, |map, cx| {
20853 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20854 });
20855 cx.emit(EditorEvent::ExcerptsEdited {
20856 ids: excerpt_ids.clone(),
20857 });
20858 }
20859 multi_buffer::Event::ExcerptsExpanded { ids } => {
20860 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20861 self.refresh_document_highlights(cx);
20862 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20863 }
20864 multi_buffer::Event::Reparsed(buffer_id) => {
20865 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20866 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20867
20868 cx.emit(EditorEvent::Reparsed(*buffer_id));
20869 }
20870 multi_buffer::Event::DiffHunksToggled => {
20871 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20872 }
20873 multi_buffer::Event::LanguageChanged(buffer_id) => {
20874 self.registered_buffers.remove(&buffer_id);
20875 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20876 cx.emit(EditorEvent::Reparsed(*buffer_id));
20877 cx.notify();
20878 }
20879 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20880 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20881 multi_buffer::Event::FileHandleChanged
20882 | multi_buffer::Event::Reloaded
20883 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20884 multi_buffer::Event::DiagnosticsUpdated => {
20885 self.update_diagnostics_state(window, cx);
20886 }
20887 _ => {}
20888 };
20889 }
20890
20891 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20892 if !self.diagnostics_enabled() {
20893 return;
20894 }
20895 self.refresh_active_diagnostics(cx);
20896 self.refresh_inline_diagnostics(true, window, cx);
20897 self.scrollbar_marker_state.dirty = true;
20898 cx.notify();
20899 }
20900
20901 pub fn start_temporary_diff_override(&mut self) {
20902 self.load_diff_task.take();
20903 self.temporary_diff_override = true;
20904 }
20905
20906 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20907 self.temporary_diff_override = false;
20908 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20909 self.buffer.update(cx, |buffer, cx| {
20910 buffer.set_all_diff_hunks_collapsed(cx);
20911 });
20912
20913 if let Some(project) = self.project.clone() {
20914 self.load_diff_task = Some(
20915 update_uncommitted_diff_for_buffer(
20916 cx.entity(),
20917 &project,
20918 self.buffer.read(cx).all_buffers(),
20919 self.buffer.clone(),
20920 cx,
20921 )
20922 .shared(),
20923 );
20924 }
20925 }
20926
20927 fn on_display_map_changed(
20928 &mut self,
20929 _: Entity<DisplayMap>,
20930 _: &mut Window,
20931 cx: &mut Context<Self>,
20932 ) {
20933 cx.notify();
20934 }
20935
20936 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20937 if self.diagnostics_enabled() {
20938 let new_severity = EditorSettings::get_global(cx)
20939 .diagnostics_max_severity
20940 .unwrap_or(DiagnosticSeverity::Hint);
20941 self.set_max_diagnostics_severity(new_severity, cx);
20942 }
20943 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20944 self.update_edit_prediction_settings(cx);
20945 self.refresh_edit_prediction(true, false, window, cx);
20946 self.refresh_inline_values(cx);
20947 self.refresh_inlay_hints(
20948 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20949 self.selections.newest_anchor().head(),
20950 &self.buffer.read(cx).snapshot(cx),
20951 cx,
20952 )),
20953 cx,
20954 );
20955
20956 let old_cursor_shape = self.cursor_shape;
20957 let old_show_breadcrumbs = self.show_breadcrumbs;
20958
20959 {
20960 let editor_settings = EditorSettings::get_global(cx);
20961 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20962 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20963 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20964 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20965 }
20966
20967 if old_cursor_shape != self.cursor_shape {
20968 cx.emit(EditorEvent::CursorShapeChanged);
20969 }
20970
20971 if old_show_breadcrumbs != self.show_breadcrumbs {
20972 cx.emit(EditorEvent::BreadcrumbsChanged);
20973 }
20974
20975 let project_settings = ProjectSettings::get_global(cx);
20976 self.serialize_dirty_buffers =
20977 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20978
20979 if self.mode.is_full() {
20980 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20981 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20982 if self.show_inline_diagnostics != show_inline_diagnostics {
20983 self.show_inline_diagnostics = show_inline_diagnostics;
20984 self.refresh_inline_diagnostics(false, window, cx);
20985 }
20986
20987 if self.git_blame_inline_enabled != inline_blame_enabled {
20988 self.toggle_git_blame_inline_internal(false, window, cx);
20989 }
20990
20991 let minimap_settings = EditorSettings::get_global(cx).minimap;
20992 if self.minimap_visibility != MinimapVisibility::Disabled {
20993 if self.minimap_visibility.settings_visibility()
20994 != minimap_settings.minimap_enabled()
20995 {
20996 self.set_minimap_visibility(
20997 MinimapVisibility::for_mode(self.mode(), cx),
20998 window,
20999 cx,
21000 );
21001 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21002 minimap_entity.update(cx, |minimap_editor, cx| {
21003 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21004 })
21005 }
21006 }
21007 }
21008
21009 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21010 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21011 }) {
21012 if !inlay_splice.is_empty() {
21013 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21014 }
21015 self.refresh_colors_for_visible_range(None, window, cx);
21016 }
21017
21018 cx.notify();
21019 }
21020
21021 pub fn set_searchable(&mut self, searchable: bool) {
21022 self.searchable = searchable;
21023 }
21024
21025 pub fn searchable(&self) -> bool {
21026 self.searchable
21027 }
21028
21029 pub fn open_excerpts_in_split(
21030 &mut self,
21031 _: &OpenExcerptsSplit,
21032 window: &mut Window,
21033 cx: &mut Context<Self>,
21034 ) {
21035 self.open_excerpts_common(None, true, window, cx)
21036 }
21037
21038 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21039 self.open_excerpts_common(None, false, window, cx)
21040 }
21041
21042 fn open_excerpts_common(
21043 &mut self,
21044 jump_data: Option<JumpData>,
21045 split: bool,
21046 window: &mut Window,
21047 cx: &mut Context<Self>,
21048 ) {
21049 let Some(workspace) = self.workspace() else {
21050 cx.propagate();
21051 return;
21052 };
21053
21054 if self.buffer.read(cx).is_singleton() {
21055 cx.propagate();
21056 return;
21057 }
21058
21059 let mut new_selections_by_buffer = HashMap::default();
21060 match &jump_data {
21061 Some(JumpData::MultiBufferPoint {
21062 excerpt_id,
21063 position,
21064 anchor,
21065 line_offset_from_top,
21066 }) => {
21067 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21068 if let Some(buffer) = multi_buffer_snapshot
21069 .buffer_id_for_excerpt(*excerpt_id)
21070 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21071 {
21072 let buffer_snapshot = buffer.read(cx).snapshot();
21073 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21074 language::ToPoint::to_point(anchor, &buffer_snapshot)
21075 } else {
21076 buffer_snapshot.clip_point(*position, Bias::Left)
21077 };
21078 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21079 new_selections_by_buffer.insert(
21080 buffer,
21081 (
21082 vec![jump_to_offset..jump_to_offset],
21083 Some(*line_offset_from_top),
21084 ),
21085 );
21086 }
21087 }
21088 Some(JumpData::MultiBufferRow {
21089 row,
21090 line_offset_from_top,
21091 }) => {
21092 let point = MultiBufferPoint::new(row.0, 0);
21093 if let Some((buffer, buffer_point, _)) =
21094 self.buffer.read(cx).point_to_buffer_point(point, cx)
21095 {
21096 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21097 new_selections_by_buffer
21098 .entry(buffer)
21099 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21100 .0
21101 .push(buffer_offset..buffer_offset)
21102 }
21103 }
21104 None => {
21105 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21106 let multi_buffer = self.buffer.read(cx);
21107 for selection in selections {
21108 for (snapshot, range, _, anchor) in multi_buffer
21109 .snapshot(cx)
21110 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21111 {
21112 if let Some(anchor) = anchor {
21113 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21114 else {
21115 continue;
21116 };
21117 let offset = text::ToOffset::to_offset(
21118 &anchor.text_anchor,
21119 &buffer_handle.read(cx).snapshot(),
21120 );
21121 let range = offset..offset;
21122 new_selections_by_buffer
21123 .entry(buffer_handle)
21124 .or_insert((Vec::new(), None))
21125 .0
21126 .push(range)
21127 } else {
21128 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21129 else {
21130 continue;
21131 };
21132 new_selections_by_buffer
21133 .entry(buffer_handle)
21134 .or_insert((Vec::new(), None))
21135 .0
21136 .push(range)
21137 }
21138 }
21139 }
21140 }
21141 }
21142
21143 new_selections_by_buffer
21144 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21145
21146 if new_selections_by_buffer.is_empty() {
21147 return;
21148 }
21149
21150 // We defer the pane interaction because we ourselves are a workspace item
21151 // and activating a new item causes the pane to call a method on us reentrantly,
21152 // which panics if we're on the stack.
21153 window.defer(cx, move |window, cx| {
21154 workspace.update(cx, |workspace, cx| {
21155 let pane = if split {
21156 workspace.adjacent_pane(window, cx)
21157 } else {
21158 workspace.active_pane().clone()
21159 };
21160
21161 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21162 let editor = buffer
21163 .read(cx)
21164 .file()
21165 .is_none()
21166 .then(|| {
21167 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21168 // so `workspace.open_project_item` will never find them, always opening a new editor.
21169 // Instead, we try to activate the existing editor in the pane first.
21170 let (editor, pane_item_index) =
21171 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21172 let editor = item.downcast::<Editor>()?;
21173 let singleton_buffer =
21174 editor.read(cx).buffer().read(cx).as_singleton()?;
21175 if singleton_buffer == buffer {
21176 Some((editor, i))
21177 } else {
21178 None
21179 }
21180 })?;
21181 pane.update(cx, |pane, cx| {
21182 pane.activate_item(pane_item_index, true, true, window, cx)
21183 });
21184 Some(editor)
21185 })
21186 .flatten()
21187 .unwrap_or_else(|| {
21188 workspace.open_project_item::<Self>(
21189 pane.clone(),
21190 buffer,
21191 true,
21192 true,
21193 window,
21194 cx,
21195 )
21196 });
21197
21198 editor.update(cx, |editor, cx| {
21199 let autoscroll = match scroll_offset {
21200 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21201 None => Autoscroll::newest(),
21202 };
21203 let nav_history = editor.nav_history.take();
21204 editor.change_selections(
21205 SelectionEffects::scroll(autoscroll),
21206 window,
21207 cx,
21208 |s| {
21209 s.select_ranges(ranges);
21210 },
21211 );
21212 editor.nav_history = nav_history;
21213 });
21214 }
21215 })
21216 });
21217 }
21218
21219 // For now, don't allow opening excerpts in buffers that aren't backed by
21220 // regular project files.
21221 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21222 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21223 }
21224
21225 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21226 let snapshot = self.buffer.read(cx).read(cx);
21227 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21228 Some(
21229 ranges
21230 .iter()
21231 .map(move |range| {
21232 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21233 })
21234 .collect(),
21235 )
21236 }
21237
21238 fn selection_replacement_ranges(
21239 &self,
21240 range: Range<OffsetUtf16>,
21241 cx: &mut App,
21242 ) -> Vec<Range<OffsetUtf16>> {
21243 let selections = self
21244 .selections
21245 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21246 let newest_selection = selections
21247 .iter()
21248 .max_by_key(|selection| selection.id)
21249 .unwrap();
21250 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21251 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21252 let snapshot = self.buffer.read(cx).read(cx);
21253 selections
21254 .into_iter()
21255 .map(|mut selection| {
21256 selection.start.0 =
21257 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21258 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21259 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21260 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21261 })
21262 .collect()
21263 }
21264
21265 fn report_editor_event(
21266 &self,
21267 reported_event: ReportEditorEvent,
21268 file_extension: Option<String>,
21269 cx: &App,
21270 ) {
21271 if cfg!(any(test, feature = "test-support")) {
21272 return;
21273 }
21274
21275 let Some(project) = &self.project else { return };
21276
21277 // If None, we are in a file without an extension
21278 let file = self
21279 .buffer
21280 .read(cx)
21281 .as_singleton()
21282 .and_then(|b| b.read(cx).file());
21283 let file_extension = file_extension.or(file
21284 .as_ref()
21285 .and_then(|file| Path::new(file.file_name(cx)).extension())
21286 .and_then(|e| e.to_str())
21287 .map(|a| a.to_string()));
21288
21289 let vim_mode = vim_enabled(cx);
21290
21291 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21292 let copilot_enabled = edit_predictions_provider
21293 == language::language_settings::EditPredictionProvider::Copilot;
21294 let copilot_enabled_for_language = self
21295 .buffer
21296 .read(cx)
21297 .language_settings(cx)
21298 .show_edit_predictions;
21299
21300 let project = project.read(cx);
21301 let event_type = reported_event.event_type();
21302
21303 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21304 telemetry::event!(
21305 event_type,
21306 type = if auto_saved {"autosave"} else {"manual"},
21307 file_extension,
21308 vim_mode,
21309 copilot_enabled,
21310 copilot_enabled_for_language,
21311 edit_predictions_provider,
21312 is_via_ssh = project.is_via_remote_server(),
21313 );
21314 } else {
21315 telemetry::event!(
21316 event_type,
21317 file_extension,
21318 vim_mode,
21319 copilot_enabled,
21320 copilot_enabled_for_language,
21321 edit_predictions_provider,
21322 is_via_ssh = project.is_via_remote_server(),
21323 );
21324 };
21325 }
21326
21327 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21328 /// with each line being an array of {text, highlight} objects.
21329 fn copy_highlight_json(
21330 &mut self,
21331 _: &CopyHighlightJson,
21332 window: &mut Window,
21333 cx: &mut Context<Self>,
21334 ) {
21335 #[derive(Serialize)]
21336 struct Chunk<'a> {
21337 text: String,
21338 highlight: Option<&'a str>,
21339 }
21340
21341 let snapshot = self.buffer.read(cx).snapshot(cx);
21342 let range = self
21343 .selected_text_range(false, window, cx)
21344 .and_then(|selection| {
21345 if selection.range.is_empty() {
21346 None
21347 } else {
21348 Some(
21349 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21350 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21351 )
21352 }
21353 })
21354 .unwrap_or_else(|| 0..snapshot.len());
21355
21356 let chunks = snapshot.chunks(range, true);
21357 let mut lines = Vec::new();
21358 let mut line: VecDeque<Chunk> = VecDeque::new();
21359
21360 let Some(style) = self.style.as_ref() else {
21361 return;
21362 };
21363
21364 for chunk in chunks {
21365 let highlight = chunk
21366 .syntax_highlight_id
21367 .and_then(|id| id.name(&style.syntax));
21368 let mut chunk_lines = chunk.text.split('\n').peekable();
21369 while let Some(text) = chunk_lines.next() {
21370 let mut merged_with_last_token = false;
21371 if let Some(last_token) = line.back_mut()
21372 && last_token.highlight == highlight
21373 {
21374 last_token.text.push_str(text);
21375 merged_with_last_token = true;
21376 }
21377
21378 if !merged_with_last_token {
21379 line.push_back(Chunk {
21380 text: text.into(),
21381 highlight,
21382 });
21383 }
21384
21385 if chunk_lines.peek().is_some() {
21386 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21387 line.pop_front();
21388 }
21389 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21390 line.pop_back();
21391 }
21392
21393 lines.push(mem::take(&mut line));
21394 }
21395 }
21396 }
21397
21398 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21399 return;
21400 };
21401 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21402 }
21403
21404 pub fn open_context_menu(
21405 &mut self,
21406 _: &OpenContextMenu,
21407 window: &mut Window,
21408 cx: &mut Context<Self>,
21409 ) {
21410 self.request_autoscroll(Autoscroll::newest(), cx);
21411 let position = self
21412 .selections
21413 .newest_display(&self.display_snapshot(cx))
21414 .start;
21415 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21416 }
21417
21418 pub fn replay_insert_event(
21419 &mut self,
21420 text: &str,
21421 relative_utf16_range: Option<Range<isize>>,
21422 window: &mut Window,
21423 cx: &mut Context<Self>,
21424 ) {
21425 if !self.input_enabled {
21426 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21427 return;
21428 }
21429 if let Some(relative_utf16_range) = relative_utf16_range {
21430 let selections = self
21431 .selections
21432 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21433 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21434 let new_ranges = selections.into_iter().map(|range| {
21435 let start = OffsetUtf16(
21436 range
21437 .head()
21438 .0
21439 .saturating_add_signed(relative_utf16_range.start),
21440 );
21441 let end = OffsetUtf16(
21442 range
21443 .head()
21444 .0
21445 .saturating_add_signed(relative_utf16_range.end),
21446 );
21447 start..end
21448 });
21449 s.select_ranges(new_ranges);
21450 });
21451 }
21452
21453 self.handle_input(text, window, cx);
21454 }
21455
21456 pub fn is_focused(&self, window: &Window) -> bool {
21457 self.focus_handle.is_focused(window)
21458 }
21459
21460 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21461 cx.emit(EditorEvent::Focused);
21462
21463 if let Some(descendant) = self
21464 .last_focused_descendant
21465 .take()
21466 .and_then(|descendant| descendant.upgrade())
21467 {
21468 window.focus(&descendant);
21469 } else {
21470 if let Some(blame) = self.blame.as_ref() {
21471 blame.update(cx, GitBlame::focus)
21472 }
21473
21474 self.blink_manager.update(cx, BlinkManager::enable);
21475 self.show_cursor_names(window, cx);
21476 self.buffer.update(cx, |buffer, cx| {
21477 buffer.finalize_last_transaction(cx);
21478 if self.leader_id.is_none() {
21479 buffer.set_active_selections(
21480 &self.selections.disjoint_anchors_arc(),
21481 self.selections.line_mode(),
21482 self.cursor_shape,
21483 cx,
21484 );
21485 }
21486 });
21487 }
21488 }
21489
21490 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21491 cx.emit(EditorEvent::FocusedIn)
21492 }
21493
21494 fn handle_focus_out(
21495 &mut self,
21496 event: FocusOutEvent,
21497 _window: &mut Window,
21498 cx: &mut Context<Self>,
21499 ) {
21500 if event.blurred != self.focus_handle {
21501 self.last_focused_descendant = Some(event.blurred);
21502 }
21503 self.selection_drag_state = SelectionDragState::None;
21504 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21505 }
21506
21507 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21508 self.blink_manager.update(cx, BlinkManager::disable);
21509 self.buffer
21510 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21511
21512 if let Some(blame) = self.blame.as_ref() {
21513 blame.update(cx, GitBlame::blur)
21514 }
21515 if !self.hover_state.focused(window, cx) {
21516 hide_hover(self, cx);
21517 }
21518 if !self
21519 .context_menu
21520 .borrow()
21521 .as_ref()
21522 .is_some_and(|context_menu| context_menu.focused(window, cx))
21523 {
21524 self.hide_context_menu(window, cx);
21525 }
21526 self.take_active_edit_prediction(cx);
21527 cx.emit(EditorEvent::Blurred);
21528 cx.notify();
21529 }
21530
21531 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21532 let mut pending: String = window
21533 .pending_input_keystrokes()
21534 .into_iter()
21535 .flatten()
21536 .filter_map(|keystroke| {
21537 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21538 keystroke.key_char.clone()
21539 } else {
21540 None
21541 }
21542 })
21543 .collect();
21544
21545 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21546 pending = "".to_string();
21547 }
21548
21549 let existing_pending = self
21550 .text_highlights::<PendingInput>(cx)
21551 .map(|(_, ranges)| ranges.to_vec());
21552 if existing_pending.is_none() && pending.is_empty() {
21553 return;
21554 }
21555 let transaction =
21556 self.transact(window, cx, |this, window, cx| {
21557 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21558 let edits = selections
21559 .iter()
21560 .map(|selection| (selection.end..selection.end, pending.clone()));
21561 this.edit(edits, cx);
21562 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21563 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21564 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21565 }));
21566 });
21567 if let Some(existing_ranges) = existing_pending {
21568 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21569 this.edit(edits, cx);
21570 }
21571 });
21572
21573 let snapshot = self.snapshot(window, cx);
21574 let ranges = self
21575 .selections
21576 .all::<usize>(&snapshot.display_snapshot)
21577 .into_iter()
21578 .map(|selection| {
21579 snapshot.buffer_snapshot().anchor_after(selection.end)
21580 ..snapshot
21581 .buffer_snapshot()
21582 .anchor_before(selection.end + pending.len())
21583 })
21584 .collect();
21585
21586 if pending.is_empty() {
21587 self.clear_highlights::<PendingInput>(cx);
21588 } else {
21589 self.highlight_text::<PendingInput>(
21590 ranges,
21591 HighlightStyle {
21592 underline: Some(UnderlineStyle {
21593 thickness: px(1.),
21594 color: None,
21595 wavy: false,
21596 }),
21597 ..Default::default()
21598 },
21599 cx,
21600 );
21601 }
21602
21603 self.ime_transaction = self.ime_transaction.or(transaction);
21604 if let Some(transaction) = self.ime_transaction {
21605 self.buffer.update(cx, |buffer, cx| {
21606 buffer.group_until_transaction(transaction, cx);
21607 });
21608 }
21609
21610 if self.text_highlights::<PendingInput>(cx).is_none() {
21611 self.ime_transaction.take();
21612 }
21613 }
21614
21615 pub fn register_action_renderer(
21616 &mut self,
21617 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21618 ) -> Subscription {
21619 let id = self.next_editor_action_id.post_inc();
21620 self.editor_actions
21621 .borrow_mut()
21622 .insert(id, Box::new(listener));
21623
21624 let editor_actions = self.editor_actions.clone();
21625 Subscription::new(move || {
21626 editor_actions.borrow_mut().remove(&id);
21627 })
21628 }
21629
21630 pub fn register_action<A: Action>(
21631 &mut self,
21632 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21633 ) -> Subscription {
21634 let id = self.next_editor_action_id.post_inc();
21635 let listener = Arc::new(listener);
21636 self.editor_actions.borrow_mut().insert(
21637 id,
21638 Box::new(move |_, window, _| {
21639 let listener = listener.clone();
21640 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21641 let action = action.downcast_ref().unwrap();
21642 if phase == DispatchPhase::Bubble {
21643 listener(action, window, cx)
21644 }
21645 })
21646 }),
21647 );
21648
21649 let editor_actions = self.editor_actions.clone();
21650 Subscription::new(move || {
21651 editor_actions.borrow_mut().remove(&id);
21652 })
21653 }
21654
21655 pub fn file_header_size(&self) -> u32 {
21656 FILE_HEADER_HEIGHT
21657 }
21658
21659 pub fn restore(
21660 &mut self,
21661 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21662 window: &mut Window,
21663 cx: &mut Context<Self>,
21664 ) {
21665 let workspace = self.workspace();
21666 let project = self.project();
21667 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21668 let mut tasks = Vec::new();
21669 for (buffer_id, changes) in revert_changes {
21670 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21671 buffer.update(cx, |buffer, cx| {
21672 buffer.edit(
21673 changes
21674 .into_iter()
21675 .map(|(range, text)| (range, text.to_string())),
21676 None,
21677 cx,
21678 );
21679 });
21680
21681 if let Some(project) =
21682 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21683 {
21684 project.update(cx, |project, cx| {
21685 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21686 })
21687 }
21688 }
21689 }
21690 tasks
21691 });
21692 cx.spawn_in(window, async move |_, cx| {
21693 for (buffer, task) in save_tasks {
21694 let result = task.await;
21695 if result.is_err() {
21696 let Some(path) = buffer
21697 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21698 .ok()
21699 else {
21700 continue;
21701 };
21702 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21703 let Some(task) = cx
21704 .update_window_entity(workspace, |workspace, window, cx| {
21705 workspace
21706 .open_path_preview(path, None, false, false, false, window, cx)
21707 })
21708 .ok()
21709 else {
21710 continue;
21711 };
21712 task.await.log_err();
21713 }
21714 }
21715 }
21716 })
21717 .detach();
21718 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21719 selections.refresh()
21720 });
21721 }
21722
21723 pub fn to_pixel_point(
21724 &self,
21725 source: multi_buffer::Anchor,
21726 editor_snapshot: &EditorSnapshot,
21727 window: &mut Window,
21728 ) -> Option<gpui::Point<Pixels>> {
21729 let source_point = source.to_display_point(editor_snapshot);
21730 self.display_to_pixel_point(source_point, editor_snapshot, window)
21731 }
21732
21733 pub fn display_to_pixel_point(
21734 &self,
21735 source: DisplayPoint,
21736 editor_snapshot: &EditorSnapshot,
21737 window: &mut Window,
21738 ) -> Option<gpui::Point<Pixels>> {
21739 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21740 let text_layout_details = self.text_layout_details(window);
21741 let scroll_top = text_layout_details
21742 .scroll_anchor
21743 .scroll_position(editor_snapshot)
21744 .y;
21745
21746 if source.row().as_f64() < scroll_top.floor() {
21747 return None;
21748 }
21749 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21750 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21751 Some(gpui::Point::new(source_x, source_y))
21752 }
21753
21754 pub fn has_visible_completions_menu(&self) -> bool {
21755 !self.edit_prediction_preview_is_active()
21756 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21757 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21758 })
21759 }
21760
21761 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21762 if self.mode.is_minimap() {
21763 return;
21764 }
21765 self.addons
21766 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21767 }
21768
21769 pub fn unregister_addon<T: Addon>(&mut self) {
21770 self.addons.remove(&std::any::TypeId::of::<T>());
21771 }
21772
21773 pub fn addon<T: Addon>(&self) -> Option<&T> {
21774 let type_id = std::any::TypeId::of::<T>();
21775 self.addons
21776 .get(&type_id)
21777 .and_then(|item| item.to_any().downcast_ref::<T>())
21778 }
21779
21780 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21781 let type_id = std::any::TypeId::of::<T>();
21782 self.addons
21783 .get_mut(&type_id)
21784 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21785 }
21786
21787 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21788 let text_layout_details = self.text_layout_details(window);
21789 let style = &text_layout_details.editor_style;
21790 let font_id = window.text_system().resolve_font(&style.text.font());
21791 let font_size = style.text.font_size.to_pixels(window.rem_size());
21792 let line_height = style.text.line_height_in_pixels(window.rem_size());
21793 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21794 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21795
21796 CharacterDimensions {
21797 em_width,
21798 em_advance,
21799 line_height,
21800 }
21801 }
21802
21803 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21804 self.load_diff_task.clone()
21805 }
21806
21807 fn read_metadata_from_db(
21808 &mut self,
21809 item_id: u64,
21810 workspace_id: WorkspaceId,
21811 window: &mut Window,
21812 cx: &mut Context<Editor>,
21813 ) {
21814 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21815 && !self.mode.is_minimap()
21816 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21817 {
21818 let buffer_snapshot = OnceCell::new();
21819
21820 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21821 && !folds.is_empty()
21822 {
21823 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21824 self.fold_ranges(
21825 folds
21826 .into_iter()
21827 .map(|(start, end)| {
21828 snapshot.clip_offset(start, Bias::Left)
21829 ..snapshot.clip_offset(end, Bias::Right)
21830 })
21831 .collect(),
21832 false,
21833 window,
21834 cx,
21835 );
21836 }
21837
21838 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21839 && !selections.is_empty()
21840 {
21841 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21842 // skip adding the initial selection to selection history
21843 self.selection_history.mode = SelectionHistoryMode::Skipping;
21844 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21845 s.select_ranges(selections.into_iter().map(|(start, end)| {
21846 snapshot.clip_offset(start, Bias::Left)
21847 ..snapshot.clip_offset(end, Bias::Right)
21848 }));
21849 });
21850 self.selection_history.mode = SelectionHistoryMode::Normal;
21851 };
21852 }
21853
21854 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21855 }
21856
21857 fn update_lsp_data(
21858 &mut self,
21859 for_buffer: Option<BufferId>,
21860 window: &mut Window,
21861 cx: &mut Context<'_, Self>,
21862 ) {
21863 self.pull_diagnostics(for_buffer, window, cx);
21864 self.refresh_colors_for_visible_range(for_buffer, window, cx);
21865 }
21866
21867 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
21868 if self.ignore_lsp_data() {
21869 return;
21870 }
21871 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
21872 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
21873 }
21874 }
21875
21876 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
21877 if !self.registered_buffers.contains_key(&buffer_id)
21878 && let Some(project) = self.project.as_ref()
21879 {
21880 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
21881 project.update(cx, |project, cx| {
21882 self.registered_buffers.insert(
21883 buffer_id,
21884 project.register_buffer_with_language_servers(&buffer, cx),
21885 );
21886 });
21887 } else {
21888 self.registered_buffers.remove(&buffer_id);
21889 }
21890 }
21891 }
21892
21893 fn ignore_lsp_data(&self) -> bool {
21894 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
21895 // skip any LSP updates for it.
21896 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
21897 }
21898}
21899
21900fn edit_for_markdown_paste<'a>(
21901 buffer: &MultiBufferSnapshot,
21902 range: Range<usize>,
21903 to_insert: &'a str,
21904 url: Option<url::Url>,
21905) -> (Range<usize>, Cow<'a, str>) {
21906 if url.is_none() {
21907 return (range, Cow::Borrowed(to_insert));
21908 };
21909
21910 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21911
21912 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21913 Cow::Borrowed(to_insert)
21914 } else {
21915 Cow::Owned(format!("[{old_text}]({to_insert})"))
21916 };
21917 (range, new_text)
21918}
21919
21920fn vim_enabled(cx: &App) -> bool {
21921 vim_mode_setting::VimModeSetting::try_get(cx)
21922 .map(|vim_mode| vim_mode.0)
21923 .unwrap_or(false)
21924}
21925
21926fn process_completion_for_edit(
21927 completion: &Completion,
21928 intent: CompletionIntent,
21929 buffer: &Entity<Buffer>,
21930 cursor_position: &text::Anchor,
21931 cx: &mut Context<Editor>,
21932) -> CompletionEdit {
21933 let buffer = buffer.read(cx);
21934 let buffer_snapshot = buffer.snapshot();
21935 let (snippet, new_text) = if completion.is_snippet() {
21936 let mut snippet_source = completion.new_text.clone();
21937 // Workaround for typescript language server issues so that methods don't expand within
21938 // strings and functions with type expressions. The previous point is used because the query
21939 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21940 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
21941 let previous_point = if previous_point.column > 0 {
21942 cursor_position.to_previous_offset(&buffer_snapshot)
21943 } else {
21944 cursor_position.to_offset(&buffer_snapshot)
21945 };
21946 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21947 && scope.prefers_label_for_snippet_in_completion()
21948 && let Some(label) = completion.label()
21949 && matches!(
21950 completion.kind(),
21951 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21952 )
21953 {
21954 snippet_source = label;
21955 }
21956 match Snippet::parse(&snippet_source).log_err() {
21957 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21958 None => (None, completion.new_text.clone()),
21959 }
21960 } else {
21961 (None, completion.new_text.clone())
21962 };
21963
21964 let mut range_to_replace = {
21965 let replace_range = &completion.replace_range;
21966 if let CompletionSource::Lsp {
21967 insert_range: Some(insert_range),
21968 ..
21969 } = &completion.source
21970 {
21971 debug_assert_eq!(
21972 insert_range.start, replace_range.start,
21973 "insert_range and replace_range should start at the same position"
21974 );
21975 debug_assert!(
21976 insert_range
21977 .start
21978 .cmp(cursor_position, &buffer_snapshot)
21979 .is_le(),
21980 "insert_range should start before or at cursor position"
21981 );
21982 debug_assert!(
21983 replace_range
21984 .start
21985 .cmp(cursor_position, &buffer_snapshot)
21986 .is_le(),
21987 "replace_range should start before or at cursor position"
21988 );
21989
21990 let should_replace = match intent {
21991 CompletionIntent::CompleteWithInsert => false,
21992 CompletionIntent::CompleteWithReplace => true,
21993 CompletionIntent::Complete | CompletionIntent::Compose => {
21994 let insert_mode =
21995 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21996 .completions
21997 .lsp_insert_mode;
21998 match insert_mode {
21999 LspInsertMode::Insert => false,
22000 LspInsertMode::Replace => true,
22001 LspInsertMode::ReplaceSubsequence => {
22002 let mut text_to_replace = buffer.chars_for_range(
22003 buffer.anchor_before(replace_range.start)
22004 ..buffer.anchor_after(replace_range.end),
22005 );
22006 let mut current_needle = text_to_replace.next();
22007 for haystack_ch in completion.label.text.chars() {
22008 if let Some(needle_ch) = current_needle
22009 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22010 {
22011 current_needle = text_to_replace.next();
22012 }
22013 }
22014 current_needle.is_none()
22015 }
22016 LspInsertMode::ReplaceSuffix => {
22017 if replace_range
22018 .end
22019 .cmp(cursor_position, &buffer_snapshot)
22020 .is_gt()
22021 {
22022 let range_after_cursor = *cursor_position..replace_range.end;
22023 let text_after_cursor = buffer
22024 .text_for_range(
22025 buffer.anchor_before(range_after_cursor.start)
22026 ..buffer.anchor_after(range_after_cursor.end),
22027 )
22028 .collect::<String>()
22029 .to_ascii_lowercase();
22030 completion
22031 .label
22032 .text
22033 .to_ascii_lowercase()
22034 .ends_with(&text_after_cursor)
22035 } else {
22036 true
22037 }
22038 }
22039 }
22040 }
22041 };
22042
22043 if should_replace {
22044 replace_range.clone()
22045 } else {
22046 insert_range.clone()
22047 }
22048 } else {
22049 replace_range.clone()
22050 }
22051 };
22052
22053 if range_to_replace
22054 .end
22055 .cmp(cursor_position, &buffer_snapshot)
22056 .is_lt()
22057 {
22058 range_to_replace.end = *cursor_position;
22059 }
22060
22061 CompletionEdit {
22062 new_text,
22063 replace_range: range_to_replace.to_offset(buffer),
22064 snippet,
22065 }
22066}
22067
22068struct CompletionEdit {
22069 new_text: String,
22070 replace_range: Range<usize>,
22071 snippet: Option<Snippet>,
22072}
22073
22074fn insert_extra_newline_brackets(
22075 buffer: &MultiBufferSnapshot,
22076 range: Range<usize>,
22077 language: &language::LanguageScope,
22078) -> bool {
22079 let leading_whitespace_len = buffer
22080 .reversed_chars_at(range.start)
22081 .take_while(|c| c.is_whitespace() && *c != '\n')
22082 .map(|c| c.len_utf8())
22083 .sum::<usize>();
22084 let trailing_whitespace_len = buffer
22085 .chars_at(range.end)
22086 .take_while(|c| c.is_whitespace() && *c != '\n')
22087 .map(|c| c.len_utf8())
22088 .sum::<usize>();
22089 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22090
22091 language.brackets().any(|(pair, enabled)| {
22092 let pair_start = pair.start.trim_end();
22093 let pair_end = pair.end.trim_start();
22094
22095 enabled
22096 && pair.newline
22097 && buffer.contains_str_at(range.end, pair_end)
22098 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22099 })
22100}
22101
22102fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22103 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22104 [(buffer, range, _)] => (*buffer, range.clone()),
22105 _ => return false,
22106 };
22107 let pair = {
22108 let mut result: Option<BracketMatch> = None;
22109
22110 for pair in buffer
22111 .all_bracket_ranges(range.clone())
22112 .filter(move |pair| {
22113 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22114 })
22115 {
22116 let len = pair.close_range.end - pair.open_range.start;
22117
22118 if let Some(existing) = &result {
22119 let existing_len = existing.close_range.end - existing.open_range.start;
22120 if len > existing_len {
22121 continue;
22122 }
22123 }
22124
22125 result = Some(pair);
22126 }
22127
22128 result
22129 };
22130 let Some(pair) = pair else {
22131 return false;
22132 };
22133 pair.newline_only
22134 && buffer
22135 .chars_for_range(pair.open_range.end..range.start)
22136 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22137 .all(|c| c.is_whitespace() && c != '\n')
22138}
22139
22140fn update_uncommitted_diff_for_buffer(
22141 editor: Entity<Editor>,
22142 project: &Entity<Project>,
22143 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22144 buffer: Entity<MultiBuffer>,
22145 cx: &mut App,
22146) -> Task<()> {
22147 let mut tasks = Vec::new();
22148 project.update(cx, |project, cx| {
22149 for buffer in buffers {
22150 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22151 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22152 }
22153 }
22154 });
22155 cx.spawn(async move |cx| {
22156 let diffs = future::join_all(tasks).await;
22157 if editor
22158 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22159 .unwrap_or(false)
22160 {
22161 return;
22162 }
22163
22164 buffer
22165 .update(cx, |buffer, cx| {
22166 for diff in diffs.into_iter().flatten() {
22167 buffer.add_diff(diff, cx);
22168 }
22169 })
22170 .ok();
22171 })
22172}
22173
22174fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22175 let tab_size = tab_size.get() as usize;
22176 let mut width = offset;
22177
22178 for ch in text.chars() {
22179 width += if ch == '\t' {
22180 tab_size - (width % tab_size)
22181 } else {
22182 1
22183 };
22184 }
22185
22186 width - offset
22187}
22188
22189#[cfg(test)]
22190mod tests {
22191 use super::*;
22192
22193 #[test]
22194 fn test_string_size_with_expanded_tabs() {
22195 let nz = |val| NonZeroU32::new(val).unwrap();
22196 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22197 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22198 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22199 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22200 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22201 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22202 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22203 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22204 }
22205}
22206
22207/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22208struct WordBreakingTokenizer<'a> {
22209 input: &'a str,
22210}
22211
22212impl<'a> WordBreakingTokenizer<'a> {
22213 fn new(input: &'a str) -> Self {
22214 Self { input }
22215 }
22216}
22217
22218fn is_char_ideographic(ch: char) -> bool {
22219 use unicode_script::Script::*;
22220 use unicode_script::UnicodeScript;
22221 matches!(ch.script(), Han | Tangut | Yi)
22222}
22223
22224fn is_grapheme_ideographic(text: &str) -> bool {
22225 text.chars().any(is_char_ideographic)
22226}
22227
22228fn is_grapheme_whitespace(text: &str) -> bool {
22229 text.chars().any(|x| x.is_whitespace())
22230}
22231
22232fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22233 text.chars()
22234 .next()
22235 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22236}
22237
22238#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22239enum WordBreakToken<'a> {
22240 Word { token: &'a str, grapheme_len: usize },
22241 InlineWhitespace { token: &'a str, grapheme_len: usize },
22242 Newline,
22243}
22244
22245impl<'a> Iterator for WordBreakingTokenizer<'a> {
22246 /// Yields a span, the count of graphemes in the token, and whether it was
22247 /// whitespace. Note that it also breaks at word boundaries.
22248 type Item = WordBreakToken<'a>;
22249
22250 fn next(&mut self) -> Option<Self::Item> {
22251 use unicode_segmentation::UnicodeSegmentation;
22252 if self.input.is_empty() {
22253 return None;
22254 }
22255
22256 let mut iter = self.input.graphemes(true).peekable();
22257 let mut offset = 0;
22258 let mut grapheme_len = 0;
22259 if let Some(first_grapheme) = iter.next() {
22260 let is_newline = first_grapheme == "\n";
22261 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22262 offset += first_grapheme.len();
22263 grapheme_len += 1;
22264 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22265 if let Some(grapheme) = iter.peek().copied()
22266 && should_stay_with_preceding_ideograph(grapheme)
22267 {
22268 offset += grapheme.len();
22269 grapheme_len += 1;
22270 }
22271 } else {
22272 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22273 let mut next_word_bound = words.peek().copied();
22274 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22275 next_word_bound = words.next();
22276 }
22277 while let Some(grapheme) = iter.peek().copied() {
22278 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22279 break;
22280 };
22281 if is_grapheme_whitespace(grapheme) != is_whitespace
22282 || (grapheme == "\n") != is_newline
22283 {
22284 break;
22285 };
22286 offset += grapheme.len();
22287 grapheme_len += 1;
22288 iter.next();
22289 }
22290 }
22291 let token = &self.input[..offset];
22292 self.input = &self.input[offset..];
22293 if token == "\n" {
22294 Some(WordBreakToken::Newline)
22295 } else if is_whitespace {
22296 Some(WordBreakToken::InlineWhitespace {
22297 token,
22298 grapheme_len,
22299 })
22300 } else {
22301 Some(WordBreakToken::Word {
22302 token,
22303 grapheme_len,
22304 })
22305 }
22306 } else {
22307 None
22308 }
22309 }
22310}
22311
22312#[test]
22313fn test_word_breaking_tokenizer() {
22314 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22315 ("", &[]),
22316 (" ", &[whitespace(" ", 2)]),
22317 ("Ʒ", &[word("Ʒ", 1)]),
22318 ("Ǽ", &[word("Ǽ", 1)]),
22319 ("⋑", &[word("⋑", 1)]),
22320 ("⋑⋑", &[word("⋑⋑", 2)]),
22321 (
22322 "原理,进而",
22323 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22324 ),
22325 (
22326 "hello world",
22327 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22328 ),
22329 (
22330 "hello, world",
22331 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22332 ),
22333 (
22334 " hello world",
22335 &[
22336 whitespace(" ", 2),
22337 word("hello", 5),
22338 whitespace(" ", 1),
22339 word("world", 5),
22340 ],
22341 ),
22342 (
22343 "这是什么 \n 钢笔",
22344 &[
22345 word("这", 1),
22346 word("是", 1),
22347 word("什", 1),
22348 word("么", 1),
22349 whitespace(" ", 1),
22350 newline(),
22351 whitespace(" ", 1),
22352 word("钢", 1),
22353 word("笔", 1),
22354 ],
22355 ),
22356 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22357 ];
22358
22359 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22360 WordBreakToken::Word {
22361 token,
22362 grapheme_len,
22363 }
22364 }
22365
22366 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22367 WordBreakToken::InlineWhitespace {
22368 token,
22369 grapheme_len,
22370 }
22371 }
22372
22373 fn newline() -> WordBreakToken<'static> {
22374 WordBreakToken::Newline
22375 }
22376
22377 for (input, result) in tests {
22378 assert_eq!(
22379 WordBreakingTokenizer::new(input)
22380 .collect::<Vec<_>>()
22381 .as_slice(),
22382 *result,
22383 );
22384 }
22385}
22386
22387fn wrap_with_prefix(
22388 first_line_prefix: String,
22389 subsequent_lines_prefix: String,
22390 unwrapped_text: String,
22391 wrap_column: usize,
22392 tab_size: NonZeroU32,
22393 preserve_existing_whitespace: bool,
22394) -> String {
22395 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22396 let subsequent_lines_prefix_len =
22397 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22398 let mut wrapped_text = String::new();
22399 let mut current_line = first_line_prefix;
22400 let mut is_first_line = true;
22401
22402 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22403 let mut current_line_len = first_line_prefix_len;
22404 let mut in_whitespace = false;
22405 for token in tokenizer {
22406 let have_preceding_whitespace = in_whitespace;
22407 match token {
22408 WordBreakToken::Word {
22409 token,
22410 grapheme_len,
22411 } => {
22412 in_whitespace = false;
22413 let current_prefix_len = if is_first_line {
22414 first_line_prefix_len
22415 } else {
22416 subsequent_lines_prefix_len
22417 };
22418 if current_line_len + grapheme_len > wrap_column
22419 && current_line_len != current_prefix_len
22420 {
22421 wrapped_text.push_str(current_line.trim_end());
22422 wrapped_text.push('\n');
22423 is_first_line = false;
22424 current_line = subsequent_lines_prefix.clone();
22425 current_line_len = subsequent_lines_prefix_len;
22426 }
22427 current_line.push_str(token);
22428 current_line_len += grapheme_len;
22429 }
22430 WordBreakToken::InlineWhitespace {
22431 mut token,
22432 mut grapheme_len,
22433 } => {
22434 in_whitespace = true;
22435 if have_preceding_whitespace && !preserve_existing_whitespace {
22436 continue;
22437 }
22438 if !preserve_existing_whitespace {
22439 // Keep a single whitespace grapheme as-is
22440 if let Some(first) =
22441 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22442 {
22443 token = first;
22444 } else {
22445 token = " ";
22446 }
22447 grapheme_len = 1;
22448 }
22449 let current_prefix_len = if is_first_line {
22450 first_line_prefix_len
22451 } else {
22452 subsequent_lines_prefix_len
22453 };
22454 if current_line_len + grapheme_len > wrap_column {
22455 wrapped_text.push_str(current_line.trim_end());
22456 wrapped_text.push('\n');
22457 is_first_line = false;
22458 current_line = subsequent_lines_prefix.clone();
22459 current_line_len = subsequent_lines_prefix_len;
22460 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22461 current_line.push_str(token);
22462 current_line_len += grapheme_len;
22463 }
22464 }
22465 WordBreakToken::Newline => {
22466 in_whitespace = true;
22467 let current_prefix_len = if is_first_line {
22468 first_line_prefix_len
22469 } else {
22470 subsequent_lines_prefix_len
22471 };
22472 if preserve_existing_whitespace {
22473 wrapped_text.push_str(current_line.trim_end());
22474 wrapped_text.push('\n');
22475 is_first_line = false;
22476 current_line = subsequent_lines_prefix.clone();
22477 current_line_len = subsequent_lines_prefix_len;
22478 } else if have_preceding_whitespace {
22479 continue;
22480 } else if current_line_len + 1 > wrap_column
22481 && current_line_len != current_prefix_len
22482 {
22483 wrapped_text.push_str(current_line.trim_end());
22484 wrapped_text.push('\n');
22485 is_first_line = false;
22486 current_line = subsequent_lines_prefix.clone();
22487 current_line_len = subsequent_lines_prefix_len;
22488 } else if current_line_len != current_prefix_len {
22489 current_line.push(' ');
22490 current_line_len += 1;
22491 }
22492 }
22493 }
22494 }
22495
22496 if !current_line.is_empty() {
22497 wrapped_text.push_str(¤t_line);
22498 }
22499 wrapped_text
22500}
22501
22502#[test]
22503fn test_wrap_with_prefix() {
22504 assert_eq!(
22505 wrap_with_prefix(
22506 "# ".to_string(),
22507 "# ".to_string(),
22508 "abcdefg".to_string(),
22509 4,
22510 NonZeroU32::new(4).unwrap(),
22511 false,
22512 ),
22513 "# abcdefg"
22514 );
22515 assert_eq!(
22516 wrap_with_prefix(
22517 "".to_string(),
22518 "".to_string(),
22519 "\thello world".to_string(),
22520 8,
22521 NonZeroU32::new(4).unwrap(),
22522 false,
22523 ),
22524 "hello\nworld"
22525 );
22526 assert_eq!(
22527 wrap_with_prefix(
22528 "// ".to_string(),
22529 "// ".to_string(),
22530 "xx \nyy zz aa bb cc".to_string(),
22531 12,
22532 NonZeroU32::new(4).unwrap(),
22533 false,
22534 ),
22535 "// xx yy zz\n// aa bb cc"
22536 );
22537 assert_eq!(
22538 wrap_with_prefix(
22539 String::new(),
22540 String::new(),
22541 "这是什么 \n 钢笔".to_string(),
22542 3,
22543 NonZeroU32::new(4).unwrap(),
22544 false,
22545 ),
22546 "这是什\n么 钢\n笔"
22547 );
22548 assert_eq!(
22549 wrap_with_prefix(
22550 String::new(),
22551 String::new(),
22552 format!("foo{}bar", '\u{2009}'), // thin space
22553 80,
22554 NonZeroU32::new(4).unwrap(),
22555 false,
22556 ),
22557 format!("foo{}bar", '\u{2009}')
22558 );
22559}
22560
22561pub trait CollaborationHub {
22562 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22563 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22564 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22565}
22566
22567impl CollaborationHub for Entity<Project> {
22568 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22569 self.read(cx).collaborators()
22570 }
22571
22572 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22573 self.read(cx).user_store().read(cx).participant_indices()
22574 }
22575
22576 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22577 let this = self.read(cx);
22578 let user_ids = this.collaborators().values().map(|c| c.user_id);
22579 this.user_store().read(cx).participant_names(user_ids, cx)
22580 }
22581}
22582
22583pub trait SemanticsProvider {
22584 fn hover(
22585 &self,
22586 buffer: &Entity<Buffer>,
22587 position: text::Anchor,
22588 cx: &mut App,
22589 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22590
22591 fn inline_values(
22592 &self,
22593 buffer_handle: Entity<Buffer>,
22594 range: Range<text::Anchor>,
22595 cx: &mut App,
22596 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22597
22598 fn applicable_inlay_chunks(
22599 &self,
22600 buffer: &Entity<Buffer>,
22601 ranges: &[Range<text::Anchor>],
22602 cx: &mut App,
22603 ) -> Vec<Range<BufferRow>>;
22604
22605 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22606
22607 fn inlay_hints(
22608 &self,
22609 invalidate: InvalidationStrategy,
22610 buffer: Entity<Buffer>,
22611 ranges: Vec<Range<text::Anchor>>,
22612 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22613 cx: &mut App,
22614 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22615
22616 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22617
22618 fn document_highlights(
22619 &self,
22620 buffer: &Entity<Buffer>,
22621 position: text::Anchor,
22622 cx: &mut App,
22623 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22624
22625 fn definitions(
22626 &self,
22627 buffer: &Entity<Buffer>,
22628 position: text::Anchor,
22629 kind: GotoDefinitionKind,
22630 cx: &mut App,
22631 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22632
22633 fn range_for_rename(
22634 &self,
22635 buffer: &Entity<Buffer>,
22636 position: text::Anchor,
22637 cx: &mut App,
22638 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22639
22640 fn perform_rename(
22641 &self,
22642 buffer: &Entity<Buffer>,
22643 position: text::Anchor,
22644 new_name: String,
22645 cx: &mut App,
22646 ) -> Option<Task<Result<ProjectTransaction>>>;
22647}
22648
22649pub trait CompletionProvider {
22650 fn completions(
22651 &self,
22652 excerpt_id: ExcerptId,
22653 buffer: &Entity<Buffer>,
22654 buffer_position: text::Anchor,
22655 trigger: CompletionContext,
22656 window: &mut Window,
22657 cx: &mut Context<Editor>,
22658 ) -> Task<Result<Vec<CompletionResponse>>>;
22659
22660 fn resolve_completions(
22661 &self,
22662 _buffer: Entity<Buffer>,
22663 _completion_indices: Vec<usize>,
22664 _completions: Rc<RefCell<Box<[Completion]>>>,
22665 _cx: &mut Context<Editor>,
22666 ) -> Task<Result<bool>> {
22667 Task::ready(Ok(false))
22668 }
22669
22670 fn apply_additional_edits_for_completion(
22671 &self,
22672 _buffer: Entity<Buffer>,
22673 _completions: Rc<RefCell<Box<[Completion]>>>,
22674 _completion_index: usize,
22675 _push_to_history: bool,
22676 _cx: &mut Context<Editor>,
22677 ) -> Task<Result<Option<language::Transaction>>> {
22678 Task::ready(Ok(None))
22679 }
22680
22681 fn is_completion_trigger(
22682 &self,
22683 buffer: &Entity<Buffer>,
22684 position: language::Anchor,
22685 text: &str,
22686 trigger_in_words: bool,
22687 menu_is_open: bool,
22688 cx: &mut Context<Editor>,
22689 ) -> bool;
22690
22691 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22692
22693 fn sort_completions(&self) -> bool {
22694 true
22695 }
22696
22697 fn filter_completions(&self) -> bool {
22698 true
22699 }
22700}
22701
22702pub trait CodeActionProvider {
22703 fn id(&self) -> Arc<str>;
22704
22705 fn code_actions(
22706 &self,
22707 buffer: &Entity<Buffer>,
22708 range: Range<text::Anchor>,
22709 window: &mut Window,
22710 cx: &mut App,
22711 ) -> Task<Result<Vec<CodeAction>>>;
22712
22713 fn apply_code_action(
22714 &self,
22715 buffer_handle: Entity<Buffer>,
22716 action: CodeAction,
22717 excerpt_id: ExcerptId,
22718 push_to_history: bool,
22719 window: &mut Window,
22720 cx: &mut App,
22721 ) -> Task<Result<ProjectTransaction>>;
22722}
22723
22724impl CodeActionProvider for Entity<Project> {
22725 fn id(&self) -> Arc<str> {
22726 "project".into()
22727 }
22728
22729 fn code_actions(
22730 &self,
22731 buffer: &Entity<Buffer>,
22732 range: Range<text::Anchor>,
22733 _window: &mut Window,
22734 cx: &mut App,
22735 ) -> Task<Result<Vec<CodeAction>>> {
22736 self.update(cx, |project, cx| {
22737 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22738 let code_actions = project.code_actions(buffer, range, None, cx);
22739 cx.background_spawn(async move {
22740 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22741 Ok(code_lens_actions
22742 .context("code lens fetch")?
22743 .into_iter()
22744 .flatten()
22745 .chain(
22746 code_actions
22747 .context("code action fetch")?
22748 .into_iter()
22749 .flatten(),
22750 )
22751 .collect())
22752 })
22753 })
22754 }
22755
22756 fn apply_code_action(
22757 &self,
22758 buffer_handle: Entity<Buffer>,
22759 action: CodeAction,
22760 _excerpt_id: ExcerptId,
22761 push_to_history: bool,
22762 _window: &mut Window,
22763 cx: &mut App,
22764 ) -> Task<Result<ProjectTransaction>> {
22765 self.update(cx, |project, cx| {
22766 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22767 })
22768 }
22769}
22770
22771fn snippet_completions(
22772 project: &Project,
22773 buffer: &Entity<Buffer>,
22774 buffer_position: text::Anchor,
22775 cx: &mut App,
22776) -> Task<Result<CompletionResponse>> {
22777 let languages = buffer.read(cx).languages_at(buffer_position);
22778 let snippet_store = project.snippets().read(cx);
22779
22780 let scopes: Vec<_> = languages
22781 .iter()
22782 .filter_map(|language| {
22783 let language_name = language.lsp_id();
22784 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22785
22786 if snippets.is_empty() {
22787 None
22788 } else {
22789 Some((language.default_scope(), snippets))
22790 }
22791 })
22792 .collect();
22793
22794 if scopes.is_empty() {
22795 return Task::ready(Ok(CompletionResponse {
22796 completions: vec![],
22797 display_options: CompletionDisplayOptions::default(),
22798 is_incomplete: false,
22799 }));
22800 }
22801
22802 let snapshot = buffer.read(cx).text_snapshot();
22803 let executor = cx.background_executor().clone();
22804
22805 cx.background_spawn(async move {
22806 let mut is_incomplete = false;
22807 let mut completions: Vec<Completion> = Vec::new();
22808 for (scope, snippets) in scopes.into_iter() {
22809 let classifier =
22810 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22811
22812 const MAX_WORD_PREFIX_LEN: usize = 128;
22813 let last_word: String = snapshot
22814 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22815 .take(MAX_WORD_PREFIX_LEN)
22816 .take_while(|c| classifier.is_word(*c))
22817 .collect::<String>()
22818 .chars()
22819 .rev()
22820 .collect();
22821
22822 if last_word.is_empty() {
22823 return Ok(CompletionResponse {
22824 completions: vec![],
22825 display_options: CompletionDisplayOptions::default(),
22826 is_incomplete: true,
22827 });
22828 }
22829
22830 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22831 let to_lsp = |point: &text::Anchor| {
22832 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22833 point_to_lsp(end)
22834 };
22835 let lsp_end = to_lsp(&buffer_position);
22836
22837 let candidates = snippets
22838 .iter()
22839 .enumerate()
22840 .flat_map(|(ix, snippet)| {
22841 snippet
22842 .prefix
22843 .iter()
22844 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22845 })
22846 .collect::<Vec<StringMatchCandidate>>();
22847
22848 const MAX_RESULTS: usize = 100;
22849 let mut matches = fuzzy::match_strings(
22850 &candidates,
22851 &last_word,
22852 last_word.chars().any(|c| c.is_uppercase()),
22853 true,
22854 MAX_RESULTS,
22855 &Default::default(),
22856 executor.clone(),
22857 )
22858 .await;
22859
22860 if matches.len() >= MAX_RESULTS {
22861 is_incomplete = true;
22862 }
22863
22864 // Remove all candidates where the query's start does not match the start of any word in the candidate
22865 if let Some(query_start) = last_word.chars().next() {
22866 matches.retain(|string_match| {
22867 split_words(&string_match.string).any(|word| {
22868 // Check that the first codepoint of the word as lowercase matches the first
22869 // codepoint of the query as lowercase
22870 word.chars()
22871 .flat_map(|codepoint| codepoint.to_lowercase())
22872 .zip(query_start.to_lowercase())
22873 .all(|(word_cp, query_cp)| word_cp == query_cp)
22874 })
22875 });
22876 }
22877
22878 let matched_strings = matches
22879 .into_iter()
22880 .map(|m| m.string)
22881 .collect::<HashSet<_>>();
22882
22883 completions.extend(snippets.iter().filter_map(|snippet| {
22884 let matching_prefix = snippet
22885 .prefix
22886 .iter()
22887 .find(|prefix| matched_strings.contains(*prefix))?;
22888 let start = as_offset - last_word.len();
22889 let start = snapshot.anchor_before(start);
22890 let range = start..buffer_position;
22891 let lsp_start = to_lsp(&start);
22892 let lsp_range = lsp::Range {
22893 start: lsp_start,
22894 end: lsp_end,
22895 };
22896 Some(Completion {
22897 replace_range: range,
22898 new_text: snippet.body.clone(),
22899 source: CompletionSource::Lsp {
22900 insert_range: None,
22901 server_id: LanguageServerId(usize::MAX),
22902 resolved: true,
22903 lsp_completion: Box::new(lsp::CompletionItem {
22904 label: snippet.prefix.first().unwrap().clone(),
22905 kind: Some(CompletionItemKind::SNIPPET),
22906 label_details: snippet.description.as_ref().map(|description| {
22907 lsp::CompletionItemLabelDetails {
22908 detail: Some(description.clone()),
22909 description: None,
22910 }
22911 }),
22912 insert_text_format: Some(InsertTextFormat::SNIPPET),
22913 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22914 lsp::InsertReplaceEdit {
22915 new_text: snippet.body.clone(),
22916 insert: lsp_range,
22917 replace: lsp_range,
22918 },
22919 )),
22920 filter_text: Some(snippet.body.clone()),
22921 sort_text: Some(char::MAX.to_string()),
22922 ..lsp::CompletionItem::default()
22923 }),
22924 lsp_defaults: None,
22925 },
22926 label: CodeLabel::plain(matching_prefix.clone(), None),
22927 icon_path: None,
22928 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22929 single_line: snippet.name.clone().into(),
22930 plain_text: snippet
22931 .description
22932 .clone()
22933 .map(|description| description.into()),
22934 }),
22935 insert_text_mode: None,
22936 confirm: None,
22937 })
22938 }))
22939 }
22940
22941 Ok(CompletionResponse {
22942 completions,
22943 display_options: CompletionDisplayOptions::default(),
22944 is_incomplete,
22945 })
22946 })
22947}
22948
22949impl CompletionProvider for Entity<Project> {
22950 fn completions(
22951 &self,
22952 _excerpt_id: ExcerptId,
22953 buffer: &Entity<Buffer>,
22954 buffer_position: text::Anchor,
22955 options: CompletionContext,
22956 _window: &mut Window,
22957 cx: &mut Context<Editor>,
22958 ) -> Task<Result<Vec<CompletionResponse>>> {
22959 self.update(cx, |project, cx| {
22960 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22961 let project_completions = project.completions(buffer, buffer_position, options, cx);
22962 cx.background_spawn(async move {
22963 let mut responses = project_completions.await?;
22964 let snippets = snippets.await?;
22965 if !snippets.completions.is_empty() {
22966 responses.push(snippets);
22967 }
22968 Ok(responses)
22969 })
22970 })
22971 }
22972
22973 fn resolve_completions(
22974 &self,
22975 buffer: Entity<Buffer>,
22976 completion_indices: Vec<usize>,
22977 completions: Rc<RefCell<Box<[Completion]>>>,
22978 cx: &mut Context<Editor>,
22979 ) -> Task<Result<bool>> {
22980 self.update(cx, |project, cx| {
22981 project.lsp_store().update(cx, |lsp_store, cx| {
22982 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22983 })
22984 })
22985 }
22986
22987 fn apply_additional_edits_for_completion(
22988 &self,
22989 buffer: Entity<Buffer>,
22990 completions: Rc<RefCell<Box<[Completion]>>>,
22991 completion_index: usize,
22992 push_to_history: bool,
22993 cx: &mut Context<Editor>,
22994 ) -> Task<Result<Option<language::Transaction>>> {
22995 self.update(cx, |project, cx| {
22996 project.lsp_store().update(cx, |lsp_store, cx| {
22997 lsp_store.apply_additional_edits_for_completion(
22998 buffer,
22999 completions,
23000 completion_index,
23001 push_to_history,
23002 cx,
23003 )
23004 })
23005 })
23006 }
23007
23008 fn is_completion_trigger(
23009 &self,
23010 buffer: &Entity<Buffer>,
23011 position: language::Anchor,
23012 text: &str,
23013 trigger_in_words: bool,
23014 menu_is_open: bool,
23015 cx: &mut Context<Editor>,
23016 ) -> bool {
23017 let mut chars = text.chars();
23018 let char = if let Some(char) = chars.next() {
23019 char
23020 } else {
23021 return false;
23022 };
23023 if chars.next().is_some() {
23024 return false;
23025 }
23026
23027 let buffer = buffer.read(cx);
23028 let snapshot = buffer.snapshot();
23029 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23030 return false;
23031 }
23032 let classifier = snapshot
23033 .char_classifier_at(position)
23034 .scope_context(Some(CharScopeContext::Completion));
23035 if trigger_in_words && classifier.is_word(char) {
23036 return true;
23037 }
23038
23039 buffer.completion_triggers().contains(text)
23040 }
23041}
23042
23043impl SemanticsProvider for Entity<Project> {
23044 fn hover(
23045 &self,
23046 buffer: &Entity<Buffer>,
23047 position: text::Anchor,
23048 cx: &mut App,
23049 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23050 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23051 }
23052
23053 fn document_highlights(
23054 &self,
23055 buffer: &Entity<Buffer>,
23056 position: text::Anchor,
23057 cx: &mut App,
23058 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23059 Some(self.update(cx, |project, cx| {
23060 project.document_highlights(buffer, position, cx)
23061 }))
23062 }
23063
23064 fn definitions(
23065 &self,
23066 buffer: &Entity<Buffer>,
23067 position: text::Anchor,
23068 kind: GotoDefinitionKind,
23069 cx: &mut App,
23070 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23071 Some(self.update(cx, |project, cx| match kind {
23072 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23073 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23074 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23075 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23076 }))
23077 }
23078
23079 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23080 self.update(cx, |project, cx| {
23081 if project
23082 .active_debug_session(cx)
23083 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23084 {
23085 return true;
23086 }
23087
23088 buffer.update(cx, |buffer, cx| {
23089 project.any_language_server_supports_inlay_hints(buffer, cx)
23090 })
23091 })
23092 }
23093
23094 fn inline_values(
23095 &self,
23096 buffer_handle: Entity<Buffer>,
23097 range: Range<text::Anchor>,
23098 cx: &mut App,
23099 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23100 self.update(cx, |project, cx| {
23101 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23102
23103 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23104 })
23105 }
23106
23107 fn applicable_inlay_chunks(
23108 &self,
23109 buffer: &Entity<Buffer>,
23110 ranges: &[Range<text::Anchor>],
23111 cx: &mut App,
23112 ) -> Vec<Range<BufferRow>> {
23113 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23114 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23115 })
23116 }
23117
23118 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23119 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23120 lsp_store.invalidate_inlay_hints(for_buffers)
23121 });
23122 }
23123
23124 fn inlay_hints(
23125 &self,
23126 invalidate: InvalidationStrategy,
23127 buffer: Entity<Buffer>,
23128 ranges: Vec<Range<text::Anchor>>,
23129 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23130 cx: &mut App,
23131 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23132 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23133 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23134 }))
23135 }
23136
23137 fn range_for_rename(
23138 &self,
23139 buffer: &Entity<Buffer>,
23140 position: text::Anchor,
23141 cx: &mut App,
23142 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23143 Some(self.update(cx, |project, cx| {
23144 let buffer = buffer.clone();
23145 let task = project.prepare_rename(buffer.clone(), position, cx);
23146 cx.spawn(async move |_, cx| {
23147 Ok(match task.await? {
23148 PrepareRenameResponse::Success(range) => Some(range),
23149 PrepareRenameResponse::InvalidPosition => None,
23150 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23151 // Fallback on using TreeSitter info to determine identifier range
23152 buffer.read_with(cx, |buffer, _| {
23153 let snapshot = buffer.snapshot();
23154 let (range, kind) = snapshot.surrounding_word(position, None);
23155 if kind != Some(CharKind::Word) {
23156 return None;
23157 }
23158 Some(
23159 snapshot.anchor_before(range.start)
23160 ..snapshot.anchor_after(range.end),
23161 )
23162 })?
23163 }
23164 })
23165 })
23166 }))
23167 }
23168
23169 fn perform_rename(
23170 &self,
23171 buffer: &Entity<Buffer>,
23172 position: text::Anchor,
23173 new_name: String,
23174 cx: &mut App,
23175 ) -> Option<Task<Result<ProjectTransaction>>> {
23176 Some(self.update(cx, |project, cx| {
23177 project.perform_rename(buffer.clone(), position, new_name, cx)
23178 }))
23179 }
23180}
23181
23182fn consume_contiguous_rows(
23183 contiguous_row_selections: &mut Vec<Selection<Point>>,
23184 selection: &Selection<Point>,
23185 display_map: &DisplaySnapshot,
23186 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23187) -> (MultiBufferRow, MultiBufferRow) {
23188 contiguous_row_selections.push(selection.clone());
23189 let start_row = starting_row(selection, display_map);
23190 let mut end_row = ending_row(selection, display_map);
23191
23192 while let Some(next_selection) = selections.peek() {
23193 if next_selection.start.row <= end_row.0 {
23194 end_row = ending_row(next_selection, display_map);
23195 contiguous_row_selections.push(selections.next().unwrap().clone());
23196 } else {
23197 break;
23198 }
23199 }
23200 (start_row, end_row)
23201}
23202
23203fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23204 if selection.start.column > 0 {
23205 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23206 } else {
23207 MultiBufferRow(selection.start.row)
23208 }
23209}
23210
23211fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23212 if next_selection.end.column > 0 || next_selection.is_empty() {
23213 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23214 } else {
23215 MultiBufferRow(next_selection.end.row)
23216 }
23217}
23218
23219impl EditorSnapshot {
23220 pub fn remote_selections_in_range<'a>(
23221 &'a self,
23222 range: &'a Range<Anchor>,
23223 collaboration_hub: &dyn CollaborationHub,
23224 cx: &'a App,
23225 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23226 let participant_names = collaboration_hub.user_names(cx);
23227 let participant_indices = collaboration_hub.user_participant_indices(cx);
23228 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23229 let collaborators_by_replica_id = collaborators_by_peer_id
23230 .values()
23231 .map(|collaborator| (collaborator.replica_id, collaborator))
23232 .collect::<HashMap<_, _>>();
23233 self.buffer_snapshot()
23234 .selections_in_range(range, false)
23235 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23236 if replica_id == ReplicaId::AGENT {
23237 Some(RemoteSelection {
23238 replica_id,
23239 selection,
23240 cursor_shape,
23241 line_mode,
23242 collaborator_id: CollaboratorId::Agent,
23243 user_name: Some("Agent".into()),
23244 color: cx.theme().players().agent(),
23245 })
23246 } else {
23247 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23248 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23249 let user_name = participant_names.get(&collaborator.user_id).cloned();
23250 Some(RemoteSelection {
23251 replica_id,
23252 selection,
23253 cursor_shape,
23254 line_mode,
23255 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23256 user_name,
23257 color: if let Some(index) = participant_index {
23258 cx.theme().players().color_for_participant(index.0)
23259 } else {
23260 cx.theme().players().absent()
23261 },
23262 })
23263 }
23264 })
23265 }
23266
23267 pub fn hunks_for_ranges(
23268 &self,
23269 ranges: impl IntoIterator<Item = Range<Point>>,
23270 ) -> Vec<MultiBufferDiffHunk> {
23271 let mut hunks = Vec::new();
23272 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23273 HashMap::default();
23274 for query_range in ranges {
23275 let query_rows =
23276 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23277 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23278 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23279 ) {
23280 // Include deleted hunks that are adjacent to the query range, because
23281 // otherwise they would be missed.
23282 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23283 if hunk.status().is_deleted() {
23284 intersects_range |= hunk.row_range.start == query_rows.end;
23285 intersects_range |= hunk.row_range.end == query_rows.start;
23286 }
23287 if intersects_range {
23288 if !processed_buffer_rows
23289 .entry(hunk.buffer_id)
23290 .or_default()
23291 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23292 {
23293 continue;
23294 }
23295 hunks.push(hunk);
23296 }
23297 }
23298 }
23299
23300 hunks
23301 }
23302
23303 fn display_diff_hunks_for_rows<'a>(
23304 &'a self,
23305 display_rows: Range<DisplayRow>,
23306 folded_buffers: &'a HashSet<BufferId>,
23307 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23308 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23309 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23310
23311 self.buffer_snapshot()
23312 .diff_hunks_in_range(buffer_start..buffer_end)
23313 .filter_map(|hunk| {
23314 if folded_buffers.contains(&hunk.buffer_id) {
23315 return None;
23316 }
23317
23318 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23319 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23320
23321 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23322 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23323
23324 let display_hunk = if hunk_display_start.column() != 0 {
23325 DisplayDiffHunk::Folded {
23326 display_row: hunk_display_start.row(),
23327 }
23328 } else {
23329 let mut end_row = hunk_display_end.row();
23330 if hunk_display_end.column() > 0 {
23331 end_row.0 += 1;
23332 }
23333 let is_created_file = hunk.is_created_file();
23334 DisplayDiffHunk::Unfolded {
23335 status: hunk.status(),
23336 diff_base_byte_range: hunk.diff_base_byte_range,
23337 display_row_range: hunk_display_start.row()..end_row,
23338 multi_buffer_range: Anchor::range_in_buffer(
23339 hunk.excerpt_id,
23340 hunk.buffer_id,
23341 hunk.buffer_range,
23342 ),
23343 is_created_file,
23344 }
23345 };
23346
23347 Some(display_hunk)
23348 })
23349 }
23350
23351 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23352 self.display_snapshot
23353 .buffer_snapshot()
23354 .language_at(position)
23355 }
23356
23357 pub fn is_focused(&self) -> bool {
23358 self.is_focused
23359 }
23360
23361 pub fn placeholder_text(&self) -> Option<String> {
23362 self.placeholder_display_snapshot
23363 .as_ref()
23364 .map(|display_map| display_map.text())
23365 }
23366
23367 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23368 self.scroll_anchor.scroll_position(&self.display_snapshot)
23369 }
23370
23371 fn gutter_dimensions(
23372 &self,
23373 font_id: FontId,
23374 font_size: Pixels,
23375 max_line_number_width: Pixels,
23376 cx: &App,
23377 ) -> Option<GutterDimensions> {
23378 if !self.show_gutter {
23379 return None;
23380 }
23381
23382 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23383 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23384
23385 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23386 matches!(
23387 ProjectSettings::get_global(cx).git.git_gutter,
23388 GitGutterSetting::TrackedFiles
23389 )
23390 });
23391 let gutter_settings = EditorSettings::get_global(cx).gutter;
23392 let show_line_numbers = self
23393 .show_line_numbers
23394 .unwrap_or(gutter_settings.line_numbers);
23395 let line_gutter_width = if show_line_numbers {
23396 // Avoid flicker-like gutter resizes when the line number gains another digit by
23397 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23398 let min_width_for_number_on_gutter =
23399 ch_advance * gutter_settings.min_line_number_digits as f32;
23400 max_line_number_width.max(min_width_for_number_on_gutter)
23401 } else {
23402 0.0.into()
23403 };
23404
23405 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23406 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23407
23408 let git_blame_entries_width =
23409 self.git_blame_gutter_max_author_length
23410 .map(|max_author_length| {
23411 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23412 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23413
23414 /// The number of characters to dedicate to gaps and margins.
23415 const SPACING_WIDTH: usize = 4;
23416
23417 let max_char_count = max_author_length.min(renderer.max_author_length())
23418 + ::git::SHORT_SHA_LENGTH
23419 + MAX_RELATIVE_TIMESTAMP.len()
23420 + SPACING_WIDTH;
23421
23422 ch_advance * max_char_count
23423 });
23424
23425 let is_singleton = self.buffer_snapshot().is_singleton();
23426
23427 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23428 left_padding += if !is_singleton {
23429 ch_width * 4.0
23430 } else if show_runnables || show_breakpoints {
23431 ch_width * 3.0
23432 } else if show_git_gutter && show_line_numbers {
23433 ch_width * 2.0
23434 } else if show_git_gutter || show_line_numbers {
23435 ch_width
23436 } else {
23437 px(0.)
23438 };
23439
23440 let shows_folds = is_singleton && gutter_settings.folds;
23441
23442 let right_padding = if shows_folds && show_line_numbers {
23443 ch_width * 4.0
23444 } else if shows_folds || (!is_singleton && show_line_numbers) {
23445 ch_width * 3.0
23446 } else if show_line_numbers {
23447 ch_width
23448 } else {
23449 px(0.)
23450 };
23451
23452 Some(GutterDimensions {
23453 left_padding,
23454 right_padding,
23455 width: line_gutter_width + left_padding + right_padding,
23456 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23457 git_blame_entries_width,
23458 })
23459 }
23460
23461 pub fn render_crease_toggle(
23462 &self,
23463 buffer_row: MultiBufferRow,
23464 row_contains_cursor: bool,
23465 editor: Entity<Editor>,
23466 window: &mut Window,
23467 cx: &mut App,
23468 ) -> Option<AnyElement> {
23469 let folded = self.is_line_folded(buffer_row);
23470 let mut is_foldable = false;
23471
23472 if let Some(crease) = self
23473 .crease_snapshot
23474 .query_row(buffer_row, self.buffer_snapshot())
23475 {
23476 is_foldable = true;
23477 match crease {
23478 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23479 if let Some(render_toggle) = render_toggle {
23480 let toggle_callback =
23481 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23482 if folded {
23483 editor.update(cx, |editor, cx| {
23484 editor.fold_at(buffer_row, window, cx)
23485 });
23486 } else {
23487 editor.update(cx, |editor, cx| {
23488 editor.unfold_at(buffer_row, window, cx)
23489 });
23490 }
23491 });
23492 return Some((render_toggle)(
23493 buffer_row,
23494 folded,
23495 toggle_callback,
23496 window,
23497 cx,
23498 ));
23499 }
23500 }
23501 }
23502 }
23503
23504 is_foldable |= self.starts_indent(buffer_row);
23505
23506 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23507 Some(
23508 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23509 .toggle_state(folded)
23510 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23511 if folded {
23512 this.unfold_at(buffer_row, window, cx);
23513 } else {
23514 this.fold_at(buffer_row, window, cx);
23515 }
23516 }))
23517 .into_any_element(),
23518 )
23519 } else {
23520 None
23521 }
23522 }
23523
23524 pub fn render_crease_trailer(
23525 &self,
23526 buffer_row: MultiBufferRow,
23527 window: &mut Window,
23528 cx: &mut App,
23529 ) -> Option<AnyElement> {
23530 let folded = self.is_line_folded(buffer_row);
23531 if let Crease::Inline { render_trailer, .. } = self
23532 .crease_snapshot
23533 .query_row(buffer_row, self.buffer_snapshot())?
23534 {
23535 let render_trailer = render_trailer.as_ref()?;
23536 Some(render_trailer(buffer_row, folded, window, cx))
23537 } else {
23538 None
23539 }
23540 }
23541}
23542
23543impl Deref for EditorSnapshot {
23544 type Target = DisplaySnapshot;
23545
23546 fn deref(&self) -> &Self::Target {
23547 &self.display_snapshot
23548 }
23549}
23550
23551#[derive(Clone, Debug, PartialEq, Eq)]
23552pub enum EditorEvent {
23553 InputIgnored {
23554 text: Arc<str>,
23555 },
23556 InputHandled {
23557 utf16_range_to_replace: Option<Range<isize>>,
23558 text: Arc<str>,
23559 },
23560 ExcerptsAdded {
23561 buffer: Entity<Buffer>,
23562 predecessor: ExcerptId,
23563 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23564 },
23565 ExcerptsRemoved {
23566 ids: Vec<ExcerptId>,
23567 removed_buffer_ids: Vec<BufferId>,
23568 },
23569 BufferFoldToggled {
23570 ids: Vec<ExcerptId>,
23571 folded: bool,
23572 },
23573 ExcerptsEdited {
23574 ids: Vec<ExcerptId>,
23575 },
23576 ExcerptsExpanded {
23577 ids: Vec<ExcerptId>,
23578 },
23579 BufferEdited,
23580 Edited {
23581 transaction_id: clock::Lamport,
23582 },
23583 Reparsed(BufferId),
23584 Focused,
23585 FocusedIn,
23586 Blurred,
23587 DirtyChanged,
23588 Saved,
23589 TitleChanged,
23590 SelectionsChanged {
23591 local: bool,
23592 },
23593 ScrollPositionChanged {
23594 local: bool,
23595 autoscroll: bool,
23596 },
23597 TransactionUndone {
23598 transaction_id: clock::Lamport,
23599 },
23600 TransactionBegun {
23601 transaction_id: clock::Lamport,
23602 },
23603 CursorShapeChanged,
23604 BreadcrumbsChanged,
23605 PushedToNavHistory {
23606 anchor: Anchor,
23607 is_deactivate: bool,
23608 },
23609}
23610
23611impl EventEmitter<EditorEvent> for Editor {}
23612
23613impl Focusable for Editor {
23614 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23615 self.focus_handle.clone()
23616 }
23617}
23618
23619impl Render for Editor {
23620 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23621 let settings = ThemeSettings::get_global(cx);
23622
23623 let mut text_style = match self.mode {
23624 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23625 color: cx.theme().colors().editor_foreground,
23626 font_family: settings.ui_font.family.clone(),
23627 font_features: settings.ui_font.features.clone(),
23628 font_fallbacks: settings.ui_font.fallbacks.clone(),
23629 font_size: rems(0.875).into(),
23630 font_weight: settings.ui_font.weight,
23631 line_height: relative(settings.buffer_line_height.value()),
23632 ..Default::default()
23633 },
23634 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23635 color: cx.theme().colors().editor_foreground,
23636 font_family: settings.buffer_font.family.clone(),
23637 font_features: settings.buffer_font.features.clone(),
23638 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23639 font_size: settings.buffer_font_size(cx).into(),
23640 font_weight: settings.buffer_font.weight,
23641 line_height: relative(settings.buffer_line_height.value()),
23642 ..Default::default()
23643 },
23644 };
23645 if let Some(text_style_refinement) = &self.text_style_refinement {
23646 text_style.refine(text_style_refinement)
23647 }
23648
23649 let background = match self.mode {
23650 EditorMode::SingleLine => cx.theme().system().transparent,
23651 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23652 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23653 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23654 };
23655
23656 EditorElement::new(
23657 &cx.entity(),
23658 EditorStyle {
23659 background,
23660 border: cx.theme().colors().border,
23661 local_player: cx.theme().players().local(),
23662 text: text_style,
23663 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23664 syntax: cx.theme().syntax().clone(),
23665 status: cx.theme().status().clone(),
23666 inlay_hints_style: make_inlay_hints_style(cx),
23667 edit_prediction_styles: make_suggestion_styles(cx),
23668 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23669 show_underlines: self.diagnostics_enabled(),
23670 },
23671 )
23672 }
23673}
23674
23675impl EntityInputHandler for Editor {
23676 fn text_for_range(
23677 &mut self,
23678 range_utf16: Range<usize>,
23679 adjusted_range: &mut Option<Range<usize>>,
23680 _: &mut Window,
23681 cx: &mut Context<Self>,
23682 ) -> Option<String> {
23683 let snapshot = self.buffer.read(cx).read(cx);
23684 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23685 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23686 if (start.0..end.0) != range_utf16 {
23687 adjusted_range.replace(start.0..end.0);
23688 }
23689 Some(snapshot.text_for_range(start..end).collect())
23690 }
23691
23692 fn selected_text_range(
23693 &mut self,
23694 ignore_disabled_input: bool,
23695 _: &mut Window,
23696 cx: &mut Context<Self>,
23697 ) -> Option<UTF16Selection> {
23698 // Prevent the IME menu from appearing when holding down an alphabetic key
23699 // while input is disabled.
23700 if !ignore_disabled_input && !self.input_enabled {
23701 return None;
23702 }
23703
23704 let selection = self
23705 .selections
23706 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23707 let range = selection.range();
23708
23709 Some(UTF16Selection {
23710 range: range.start.0..range.end.0,
23711 reversed: selection.reversed,
23712 })
23713 }
23714
23715 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23716 let snapshot = self.buffer.read(cx).read(cx);
23717 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23718 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23719 }
23720
23721 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23722 self.clear_highlights::<InputComposition>(cx);
23723 self.ime_transaction.take();
23724 }
23725
23726 fn replace_text_in_range(
23727 &mut self,
23728 range_utf16: Option<Range<usize>>,
23729 text: &str,
23730 window: &mut Window,
23731 cx: &mut Context<Self>,
23732 ) {
23733 if !self.input_enabled {
23734 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23735 return;
23736 }
23737
23738 self.transact(window, cx, |this, window, cx| {
23739 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23740 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23741 Some(this.selection_replacement_ranges(range_utf16, cx))
23742 } else {
23743 this.marked_text_ranges(cx)
23744 };
23745
23746 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23747 let newest_selection_id = this.selections.newest_anchor().id;
23748 this.selections
23749 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23750 .iter()
23751 .zip(ranges_to_replace.iter())
23752 .find_map(|(selection, range)| {
23753 if selection.id == newest_selection_id {
23754 Some(
23755 (range.start.0 as isize - selection.head().0 as isize)
23756 ..(range.end.0 as isize - selection.head().0 as isize),
23757 )
23758 } else {
23759 None
23760 }
23761 })
23762 });
23763
23764 cx.emit(EditorEvent::InputHandled {
23765 utf16_range_to_replace: range_to_replace,
23766 text: text.into(),
23767 });
23768
23769 if let Some(new_selected_ranges) = new_selected_ranges {
23770 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23771 selections.select_ranges(new_selected_ranges)
23772 });
23773 this.backspace(&Default::default(), window, cx);
23774 }
23775
23776 this.handle_input(text, window, cx);
23777 });
23778
23779 if let Some(transaction) = self.ime_transaction {
23780 self.buffer.update(cx, |buffer, cx| {
23781 buffer.group_until_transaction(transaction, cx);
23782 });
23783 }
23784
23785 self.unmark_text(window, cx);
23786 }
23787
23788 fn replace_and_mark_text_in_range(
23789 &mut self,
23790 range_utf16: Option<Range<usize>>,
23791 text: &str,
23792 new_selected_range_utf16: Option<Range<usize>>,
23793 window: &mut Window,
23794 cx: &mut Context<Self>,
23795 ) {
23796 if !self.input_enabled {
23797 return;
23798 }
23799
23800 let transaction = self.transact(window, cx, |this, window, cx| {
23801 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23802 let snapshot = this.buffer.read(cx).read(cx);
23803 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23804 for marked_range in &mut marked_ranges {
23805 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23806 marked_range.start.0 += relative_range_utf16.start;
23807 marked_range.start =
23808 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23809 marked_range.end =
23810 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23811 }
23812 }
23813 Some(marked_ranges)
23814 } else if let Some(range_utf16) = range_utf16 {
23815 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23816 Some(this.selection_replacement_ranges(range_utf16, cx))
23817 } else {
23818 None
23819 };
23820
23821 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23822 let newest_selection_id = this.selections.newest_anchor().id;
23823 this.selections
23824 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23825 .iter()
23826 .zip(ranges_to_replace.iter())
23827 .find_map(|(selection, range)| {
23828 if selection.id == newest_selection_id {
23829 Some(
23830 (range.start.0 as isize - selection.head().0 as isize)
23831 ..(range.end.0 as isize - selection.head().0 as isize),
23832 )
23833 } else {
23834 None
23835 }
23836 })
23837 });
23838
23839 cx.emit(EditorEvent::InputHandled {
23840 utf16_range_to_replace: range_to_replace,
23841 text: text.into(),
23842 });
23843
23844 if let Some(ranges) = ranges_to_replace {
23845 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23846 s.select_ranges(ranges)
23847 });
23848 }
23849
23850 let marked_ranges = {
23851 let snapshot = this.buffer.read(cx).read(cx);
23852 this.selections
23853 .disjoint_anchors_arc()
23854 .iter()
23855 .map(|selection| {
23856 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23857 })
23858 .collect::<Vec<_>>()
23859 };
23860
23861 if text.is_empty() {
23862 this.unmark_text(window, cx);
23863 } else {
23864 this.highlight_text::<InputComposition>(
23865 marked_ranges.clone(),
23866 HighlightStyle {
23867 underline: Some(UnderlineStyle {
23868 thickness: px(1.),
23869 color: None,
23870 wavy: false,
23871 }),
23872 ..Default::default()
23873 },
23874 cx,
23875 );
23876 }
23877
23878 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23879 let use_autoclose = this.use_autoclose;
23880 let use_auto_surround = this.use_auto_surround;
23881 this.set_use_autoclose(false);
23882 this.set_use_auto_surround(false);
23883 this.handle_input(text, window, cx);
23884 this.set_use_autoclose(use_autoclose);
23885 this.set_use_auto_surround(use_auto_surround);
23886
23887 if let Some(new_selected_range) = new_selected_range_utf16 {
23888 let snapshot = this.buffer.read(cx).read(cx);
23889 let new_selected_ranges = marked_ranges
23890 .into_iter()
23891 .map(|marked_range| {
23892 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23893 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23894 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23895 snapshot.clip_offset_utf16(new_start, Bias::Left)
23896 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23897 })
23898 .collect::<Vec<_>>();
23899
23900 drop(snapshot);
23901 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23902 selections.select_ranges(new_selected_ranges)
23903 });
23904 }
23905 });
23906
23907 self.ime_transaction = self.ime_transaction.or(transaction);
23908 if let Some(transaction) = self.ime_transaction {
23909 self.buffer.update(cx, |buffer, cx| {
23910 buffer.group_until_transaction(transaction, cx);
23911 });
23912 }
23913
23914 if self.text_highlights::<InputComposition>(cx).is_none() {
23915 self.ime_transaction.take();
23916 }
23917 }
23918
23919 fn bounds_for_range(
23920 &mut self,
23921 range_utf16: Range<usize>,
23922 element_bounds: gpui::Bounds<Pixels>,
23923 window: &mut Window,
23924 cx: &mut Context<Self>,
23925 ) -> Option<gpui::Bounds<Pixels>> {
23926 let text_layout_details = self.text_layout_details(window);
23927 let CharacterDimensions {
23928 em_width,
23929 em_advance,
23930 line_height,
23931 } = self.character_dimensions(window);
23932
23933 let snapshot = self.snapshot(window, cx);
23934 let scroll_position = snapshot.scroll_position();
23935 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
23936
23937 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23938 let x = Pixels::from(
23939 ScrollOffset::from(
23940 snapshot.x_for_display_point(start, &text_layout_details)
23941 + self.gutter_dimensions.full_width(),
23942 ) - scroll_left,
23943 );
23944 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
23945
23946 Some(Bounds {
23947 origin: element_bounds.origin + point(x, y),
23948 size: size(em_width, line_height),
23949 })
23950 }
23951
23952 fn character_index_for_point(
23953 &mut self,
23954 point: gpui::Point<Pixels>,
23955 _window: &mut Window,
23956 _cx: &mut Context<Self>,
23957 ) -> Option<usize> {
23958 let position_map = self.last_position_map.as_ref()?;
23959 if !position_map.text_hitbox.contains(&point) {
23960 return None;
23961 }
23962 let display_point = position_map.point_for_position(point).previous_valid;
23963 let anchor = position_map
23964 .snapshot
23965 .display_point_to_anchor(display_point, Bias::Left);
23966 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
23967 Some(utf16_offset.0)
23968 }
23969}
23970
23971trait SelectionExt {
23972 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23973 fn spanned_rows(
23974 &self,
23975 include_end_if_at_line_start: bool,
23976 map: &DisplaySnapshot,
23977 ) -> Range<MultiBufferRow>;
23978}
23979
23980impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23981 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23982 let start = self
23983 .start
23984 .to_point(map.buffer_snapshot())
23985 .to_display_point(map);
23986 let end = self
23987 .end
23988 .to_point(map.buffer_snapshot())
23989 .to_display_point(map);
23990 if self.reversed {
23991 end..start
23992 } else {
23993 start..end
23994 }
23995 }
23996
23997 fn spanned_rows(
23998 &self,
23999 include_end_if_at_line_start: bool,
24000 map: &DisplaySnapshot,
24001 ) -> Range<MultiBufferRow> {
24002 let start = self.start.to_point(map.buffer_snapshot());
24003 let mut end = self.end.to_point(map.buffer_snapshot());
24004 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24005 end.row -= 1;
24006 }
24007
24008 let buffer_start = map.prev_line_boundary(start).0;
24009 let buffer_end = map.next_line_boundary(end).0;
24010 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24011 }
24012}
24013
24014impl<T: InvalidationRegion> InvalidationStack<T> {
24015 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24016 where
24017 S: Clone + ToOffset,
24018 {
24019 while let Some(region) = self.last() {
24020 let all_selections_inside_invalidation_ranges =
24021 if selections.len() == region.ranges().len() {
24022 selections
24023 .iter()
24024 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24025 .all(|(selection, invalidation_range)| {
24026 let head = selection.head().to_offset(buffer);
24027 invalidation_range.start <= head && invalidation_range.end >= head
24028 })
24029 } else {
24030 false
24031 };
24032
24033 if all_selections_inside_invalidation_ranges {
24034 break;
24035 } else {
24036 self.pop();
24037 }
24038 }
24039 }
24040}
24041
24042impl<T> Default for InvalidationStack<T> {
24043 fn default() -> Self {
24044 Self(Default::default())
24045 }
24046}
24047
24048impl<T> Deref for InvalidationStack<T> {
24049 type Target = Vec<T>;
24050
24051 fn deref(&self) -> &Self::Target {
24052 &self.0
24053 }
24054}
24055
24056impl<T> DerefMut for InvalidationStack<T> {
24057 fn deref_mut(&mut self) -> &mut Self::Target {
24058 &mut self.0
24059 }
24060}
24061
24062impl InvalidationRegion for SnippetState {
24063 fn ranges(&self) -> &[Range<Anchor>] {
24064 &self.ranges[self.active_index]
24065 }
24066}
24067
24068fn edit_prediction_edit_text(
24069 current_snapshot: &BufferSnapshot,
24070 edits: &[(Range<Anchor>, String)],
24071 edit_preview: &EditPreview,
24072 include_deletions: bool,
24073 cx: &App,
24074) -> HighlightedText {
24075 let edits = edits
24076 .iter()
24077 .map(|(anchor, text)| {
24078 (
24079 anchor.start.text_anchor..anchor.end.text_anchor,
24080 text.clone(),
24081 )
24082 })
24083 .collect::<Vec<_>>();
24084
24085 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24086}
24087
24088fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24089 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24090 // Just show the raw edit text with basic styling
24091 let mut text = String::new();
24092 let mut highlights = Vec::new();
24093
24094 let insertion_highlight_style = HighlightStyle {
24095 color: Some(cx.theme().colors().text),
24096 ..Default::default()
24097 };
24098
24099 for (_, edit_text) in edits {
24100 let start_offset = text.len();
24101 text.push_str(edit_text);
24102 let end_offset = text.len();
24103
24104 if start_offset < end_offset {
24105 highlights.push((start_offset..end_offset, insertion_highlight_style));
24106 }
24107 }
24108
24109 HighlightedText {
24110 text: text.into(),
24111 highlights,
24112 }
24113}
24114
24115pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24116 match severity {
24117 lsp::DiagnosticSeverity::ERROR => colors.error,
24118 lsp::DiagnosticSeverity::WARNING => colors.warning,
24119 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24120 lsp::DiagnosticSeverity::HINT => colors.info,
24121 _ => colors.ignored,
24122 }
24123}
24124
24125pub fn styled_runs_for_code_label<'a>(
24126 label: &'a CodeLabel,
24127 syntax_theme: &'a theme::SyntaxTheme,
24128) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24129 let fade_out = HighlightStyle {
24130 fade_out: Some(0.35),
24131 ..Default::default()
24132 };
24133
24134 let mut prev_end = label.filter_range.end;
24135 label
24136 .runs
24137 .iter()
24138 .enumerate()
24139 .flat_map(move |(ix, (range, highlight_id))| {
24140 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24141 style
24142 } else {
24143 return Default::default();
24144 };
24145 let muted_style = style.highlight(fade_out);
24146
24147 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24148 if range.start >= label.filter_range.end {
24149 if range.start > prev_end {
24150 runs.push((prev_end..range.start, fade_out));
24151 }
24152 runs.push((range.clone(), muted_style));
24153 } else if range.end <= label.filter_range.end {
24154 runs.push((range.clone(), style));
24155 } else {
24156 runs.push((range.start..label.filter_range.end, style));
24157 runs.push((label.filter_range.end..range.end, muted_style));
24158 }
24159 prev_end = cmp::max(prev_end, range.end);
24160
24161 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24162 runs.push((prev_end..label.text.len(), fade_out));
24163 }
24164
24165 runs
24166 })
24167}
24168
24169pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24170 let mut prev_index = 0;
24171 let mut prev_codepoint: Option<char> = None;
24172 text.char_indices()
24173 .chain([(text.len(), '\0')])
24174 .filter_map(move |(index, codepoint)| {
24175 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24176 let is_boundary = index == text.len()
24177 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24178 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24179 if is_boundary {
24180 let chunk = &text[prev_index..index];
24181 prev_index = index;
24182 Some(chunk)
24183 } else {
24184 None
24185 }
24186 })
24187}
24188
24189pub trait RangeToAnchorExt: Sized {
24190 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24191
24192 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24193 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24194 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24195 }
24196}
24197
24198impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24199 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24200 let start_offset = self.start.to_offset(snapshot);
24201 let end_offset = self.end.to_offset(snapshot);
24202 if start_offset == end_offset {
24203 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24204 } else {
24205 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24206 }
24207 }
24208}
24209
24210pub trait RowExt {
24211 fn as_f64(&self) -> f64;
24212
24213 fn next_row(&self) -> Self;
24214
24215 fn previous_row(&self) -> Self;
24216
24217 fn minus(&self, other: Self) -> u32;
24218}
24219
24220impl RowExt for DisplayRow {
24221 fn as_f64(&self) -> f64 {
24222 self.0 as _
24223 }
24224
24225 fn next_row(&self) -> Self {
24226 Self(self.0 + 1)
24227 }
24228
24229 fn previous_row(&self) -> Self {
24230 Self(self.0.saturating_sub(1))
24231 }
24232
24233 fn minus(&self, other: Self) -> u32 {
24234 self.0 - other.0
24235 }
24236}
24237
24238impl RowExt for MultiBufferRow {
24239 fn as_f64(&self) -> f64 {
24240 self.0 as _
24241 }
24242
24243 fn next_row(&self) -> Self {
24244 Self(self.0 + 1)
24245 }
24246
24247 fn previous_row(&self) -> Self {
24248 Self(self.0.saturating_sub(1))
24249 }
24250
24251 fn minus(&self, other: Self) -> u32 {
24252 self.0 - other.0
24253 }
24254}
24255
24256trait RowRangeExt {
24257 type Row;
24258
24259 fn len(&self) -> usize;
24260
24261 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24262}
24263
24264impl RowRangeExt for Range<MultiBufferRow> {
24265 type Row = MultiBufferRow;
24266
24267 fn len(&self) -> usize {
24268 (self.end.0 - self.start.0) as usize
24269 }
24270
24271 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24272 (self.start.0..self.end.0).map(MultiBufferRow)
24273 }
24274}
24275
24276impl RowRangeExt for Range<DisplayRow> {
24277 type Row = DisplayRow;
24278
24279 fn len(&self) -> usize {
24280 (self.end.0 - self.start.0) as usize
24281 }
24282
24283 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24284 (self.start.0..self.end.0).map(DisplayRow)
24285 }
24286}
24287
24288/// If select range has more than one line, we
24289/// just point the cursor to range.start.
24290fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24291 if range.start.row == range.end.row {
24292 range
24293 } else {
24294 range.start..range.start
24295 }
24296}
24297pub struct KillRing(ClipboardItem);
24298impl Global for KillRing {}
24299
24300const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24301
24302enum BreakpointPromptEditAction {
24303 Log,
24304 Condition,
24305 HitCondition,
24306}
24307
24308struct BreakpointPromptEditor {
24309 pub(crate) prompt: Entity<Editor>,
24310 editor: WeakEntity<Editor>,
24311 breakpoint_anchor: Anchor,
24312 breakpoint: Breakpoint,
24313 edit_action: BreakpointPromptEditAction,
24314 block_ids: HashSet<CustomBlockId>,
24315 editor_margins: Arc<Mutex<EditorMargins>>,
24316 _subscriptions: Vec<Subscription>,
24317}
24318
24319impl BreakpointPromptEditor {
24320 const MAX_LINES: u8 = 4;
24321
24322 fn new(
24323 editor: WeakEntity<Editor>,
24324 breakpoint_anchor: Anchor,
24325 breakpoint: Breakpoint,
24326 edit_action: BreakpointPromptEditAction,
24327 window: &mut Window,
24328 cx: &mut Context<Self>,
24329 ) -> Self {
24330 let base_text = match edit_action {
24331 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24332 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24333 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24334 }
24335 .map(|msg| msg.to_string())
24336 .unwrap_or_default();
24337
24338 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24339 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24340
24341 let prompt = cx.new(|cx| {
24342 let mut prompt = Editor::new(
24343 EditorMode::AutoHeight {
24344 min_lines: 1,
24345 max_lines: Some(Self::MAX_LINES as usize),
24346 },
24347 buffer,
24348 None,
24349 window,
24350 cx,
24351 );
24352 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24353 prompt.set_show_cursor_when_unfocused(false, cx);
24354 prompt.set_placeholder_text(
24355 match edit_action {
24356 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24357 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24358 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24359 },
24360 window,
24361 cx,
24362 );
24363
24364 prompt
24365 });
24366
24367 Self {
24368 prompt,
24369 editor,
24370 breakpoint_anchor,
24371 breakpoint,
24372 edit_action,
24373 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24374 block_ids: Default::default(),
24375 _subscriptions: vec![],
24376 }
24377 }
24378
24379 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24380 self.block_ids.extend(block_ids)
24381 }
24382
24383 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24384 if let Some(editor) = self.editor.upgrade() {
24385 let message = self
24386 .prompt
24387 .read(cx)
24388 .buffer
24389 .read(cx)
24390 .as_singleton()
24391 .expect("A multi buffer in breakpoint prompt isn't possible")
24392 .read(cx)
24393 .as_rope()
24394 .to_string();
24395
24396 editor.update(cx, |editor, cx| {
24397 editor.edit_breakpoint_at_anchor(
24398 self.breakpoint_anchor,
24399 self.breakpoint.clone(),
24400 match self.edit_action {
24401 BreakpointPromptEditAction::Log => {
24402 BreakpointEditAction::EditLogMessage(message.into())
24403 }
24404 BreakpointPromptEditAction::Condition => {
24405 BreakpointEditAction::EditCondition(message.into())
24406 }
24407 BreakpointPromptEditAction::HitCondition => {
24408 BreakpointEditAction::EditHitCondition(message.into())
24409 }
24410 },
24411 cx,
24412 );
24413
24414 editor.remove_blocks(self.block_ids.clone(), None, cx);
24415 cx.focus_self(window);
24416 });
24417 }
24418 }
24419
24420 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24421 self.editor
24422 .update(cx, |editor, cx| {
24423 editor.remove_blocks(self.block_ids.clone(), None, cx);
24424 window.focus(&editor.focus_handle);
24425 })
24426 .log_err();
24427 }
24428
24429 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24430 let settings = ThemeSettings::get_global(cx);
24431 let text_style = TextStyle {
24432 color: if self.prompt.read(cx).read_only(cx) {
24433 cx.theme().colors().text_disabled
24434 } else {
24435 cx.theme().colors().text
24436 },
24437 font_family: settings.buffer_font.family.clone(),
24438 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24439 font_size: settings.buffer_font_size(cx).into(),
24440 font_weight: settings.buffer_font.weight,
24441 line_height: relative(settings.buffer_line_height.value()),
24442 ..Default::default()
24443 };
24444 EditorElement::new(
24445 &self.prompt,
24446 EditorStyle {
24447 background: cx.theme().colors().editor_background,
24448 local_player: cx.theme().players().local(),
24449 text: text_style,
24450 ..Default::default()
24451 },
24452 )
24453 }
24454}
24455
24456impl Render for BreakpointPromptEditor {
24457 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24458 let editor_margins = *self.editor_margins.lock();
24459 let gutter_dimensions = editor_margins.gutter;
24460 h_flex()
24461 .key_context("Editor")
24462 .bg(cx.theme().colors().editor_background)
24463 .border_y_1()
24464 .border_color(cx.theme().status().info_border)
24465 .size_full()
24466 .py(window.line_height() / 2.5)
24467 .on_action(cx.listener(Self::confirm))
24468 .on_action(cx.listener(Self::cancel))
24469 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24470 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24471 }
24472}
24473
24474impl Focusable for BreakpointPromptEditor {
24475 fn focus_handle(&self, cx: &App) -> FocusHandle {
24476 self.prompt.focus_handle(cx)
24477 }
24478}
24479
24480fn all_edits_insertions_or_deletions(
24481 edits: &Vec<(Range<Anchor>, String)>,
24482 snapshot: &MultiBufferSnapshot,
24483) -> bool {
24484 let mut all_insertions = true;
24485 let mut all_deletions = true;
24486
24487 for (range, new_text) in edits.iter() {
24488 let range_is_empty = range.to_offset(snapshot).is_empty();
24489 let text_is_empty = new_text.is_empty();
24490
24491 if range_is_empty != text_is_empty {
24492 if range_is_empty {
24493 all_deletions = false;
24494 } else {
24495 all_insertions = false;
24496 }
24497 } else {
24498 return false;
24499 }
24500
24501 if !all_insertions && !all_deletions {
24502 return false;
24503 }
24504 }
24505 all_insertions || all_deletions
24506}
24507
24508struct MissingEditPredictionKeybindingTooltip;
24509
24510impl Render for MissingEditPredictionKeybindingTooltip {
24511 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24512 ui::tooltip_container(cx, |container, cx| {
24513 container
24514 .flex_shrink_0()
24515 .max_w_80()
24516 .min_h(rems_from_px(124.))
24517 .justify_between()
24518 .child(
24519 v_flex()
24520 .flex_1()
24521 .text_ui_sm(cx)
24522 .child(Label::new("Conflict with Accept Keybinding"))
24523 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24524 )
24525 .child(
24526 h_flex()
24527 .pb_1()
24528 .gap_1()
24529 .items_end()
24530 .w_full()
24531 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24532 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24533 }))
24534 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24535 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24536 })),
24537 )
24538 })
24539 }
24540}
24541
24542#[derive(Debug, Clone, Copy, PartialEq)]
24543pub struct LineHighlight {
24544 pub background: Background,
24545 pub border: Option<gpui::Hsla>,
24546 pub include_gutter: bool,
24547 pub type_id: Option<TypeId>,
24548}
24549
24550struct LineManipulationResult {
24551 pub new_text: String,
24552 pub line_count_before: usize,
24553 pub line_count_after: usize,
24554}
24555
24556fn render_diff_hunk_controls(
24557 row: u32,
24558 status: &DiffHunkStatus,
24559 hunk_range: Range<Anchor>,
24560 is_created_file: bool,
24561 line_height: Pixels,
24562 editor: &Entity<Editor>,
24563 _window: &mut Window,
24564 cx: &mut App,
24565) -> AnyElement {
24566 h_flex()
24567 .h(line_height)
24568 .mr_1()
24569 .gap_1()
24570 .px_0p5()
24571 .pb_1()
24572 .border_x_1()
24573 .border_b_1()
24574 .border_color(cx.theme().colors().border_variant)
24575 .rounded_b_lg()
24576 .bg(cx.theme().colors().editor_background)
24577 .gap_1()
24578 .block_mouse_except_scroll()
24579 .shadow_md()
24580 .child(if status.has_secondary_hunk() {
24581 Button::new(("stage", row as u64), "Stage")
24582 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24583 .tooltip({
24584 let focus_handle = editor.focus_handle(cx);
24585 move |_window, cx| {
24586 Tooltip::for_action_in(
24587 "Stage Hunk",
24588 &::git::ToggleStaged,
24589 &focus_handle,
24590 cx,
24591 )
24592 }
24593 })
24594 .on_click({
24595 let editor = editor.clone();
24596 move |_event, _window, cx| {
24597 editor.update(cx, |editor, cx| {
24598 editor.stage_or_unstage_diff_hunks(
24599 true,
24600 vec![hunk_range.start..hunk_range.start],
24601 cx,
24602 );
24603 });
24604 }
24605 })
24606 } else {
24607 Button::new(("unstage", row as u64), "Unstage")
24608 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24609 .tooltip({
24610 let focus_handle = editor.focus_handle(cx);
24611 move |_window, cx| {
24612 Tooltip::for_action_in(
24613 "Unstage Hunk",
24614 &::git::ToggleStaged,
24615 &focus_handle,
24616 cx,
24617 )
24618 }
24619 })
24620 .on_click({
24621 let editor = editor.clone();
24622 move |_event, _window, cx| {
24623 editor.update(cx, |editor, cx| {
24624 editor.stage_or_unstage_diff_hunks(
24625 false,
24626 vec![hunk_range.start..hunk_range.start],
24627 cx,
24628 );
24629 });
24630 }
24631 })
24632 })
24633 .child(
24634 Button::new(("restore", row as u64), "Restore")
24635 .tooltip({
24636 let focus_handle = editor.focus_handle(cx);
24637 move |_window, cx| {
24638 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24639 }
24640 })
24641 .on_click({
24642 let editor = editor.clone();
24643 move |_event, window, cx| {
24644 editor.update(cx, |editor, cx| {
24645 let snapshot = editor.snapshot(window, cx);
24646 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24647 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24648 });
24649 }
24650 })
24651 .disabled(is_created_file),
24652 )
24653 .when(
24654 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24655 |el| {
24656 el.child(
24657 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24658 .shape(IconButtonShape::Square)
24659 .icon_size(IconSize::Small)
24660 // .disabled(!has_multiple_hunks)
24661 .tooltip({
24662 let focus_handle = editor.focus_handle(cx);
24663 move |_window, cx| {
24664 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24665 }
24666 })
24667 .on_click({
24668 let editor = editor.clone();
24669 move |_event, window, cx| {
24670 editor.update(cx, |editor, cx| {
24671 let snapshot = editor.snapshot(window, cx);
24672 let position =
24673 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24674 editor.go_to_hunk_before_or_after_position(
24675 &snapshot,
24676 position,
24677 Direction::Next,
24678 window,
24679 cx,
24680 );
24681 editor.expand_selected_diff_hunks(cx);
24682 });
24683 }
24684 }),
24685 )
24686 .child(
24687 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24688 .shape(IconButtonShape::Square)
24689 .icon_size(IconSize::Small)
24690 // .disabled(!has_multiple_hunks)
24691 .tooltip({
24692 let focus_handle = editor.focus_handle(cx);
24693 move |_window, cx| {
24694 Tooltip::for_action_in(
24695 "Previous Hunk",
24696 &GoToPreviousHunk,
24697 &focus_handle,
24698 cx,
24699 )
24700 }
24701 })
24702 .on_click({
24703 let editor = editor.clone();
24704 move |_event, window, cx| {
24705 editor.update(cx, |editor, cx| {
24706 let snapshot = editor.snapshot(window, cx);
24707 let point =
24708 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24709 editor.go_to_hunk_before_or_after_position(
24710 &snapshot,
24711 point,
24712 Direction::Prev,
24713 window,
24714 cx,
24715 );
24716 editor.expand_selected_diff_hunks(cx);
24717 });
24718 }
24719 }),
24720 )
24721 },
24722 )
24723 .into_any_element()
24724}
24725
24726pub fn multibuffer_context_lines(cx: &App) -> u32 {
24727 EditorSettings::try_get(cx)
24728 .map(|settings| settings.excerpt_context_lines)
24729 .unwrap_or(2)
24730 .min(32)
24731}