1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionIntent, CompletionResponse,
151 CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, Location, LocationLink,
152 PrepareRenameResponse, Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::breakpoint_store::Breakpoint,
154 debugger::{
155 breakpoint_store::{
156 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
157 BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
164 project_settings::{GitGutterSetting, ProjectSettings},
165};
166use rand::{seq::SliceRandom, thread_rng};
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::TypeId,
178 borrow::Cow,
179 cell::OnceCell,
180 cell::RefCell,
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::Not,
186 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use sum_tree::TreeMap;
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
223const MAX_LINE_LEN: usize = 1024;
224const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
225const MAX_SELECTION_HISTORY_LEN: usize = 1024;
226pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
227#[doc(hidden)]
228pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
229const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
230
231pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
234
235pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
236pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252enum ReportEditorEvent {
253 Saved { auto_saved: bool },
254 EditorOpened,
255 Closed,
256}
257
258impl ReportEditorEvent {
259 pub fn event_type(&self) -> &'static str {
260 match self {
261 Self::Saved { .. } => "Editor Saved",
262 Self::EditorOpened => "Editor Opened",
263 Self::Closed => "Editor Closed",
264 }
265 }
266}
267
268struct InlineValueCache {
269 enabled: bool,
270 inlays: Vec<InlayId>,
271 refresh_task: Task<Option<()>>,
272}
273
274impl InlineValueCache {
275 fn new(enabled: bool) -> Self {
276 Self {
277 enabled,
278 inlays: Vec::new(),
279 refresh_task: Task::ready(None),
280 }
281 }
282}
283
284#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
285pub enum InlayId {
286 EditPrediction(usize),
287 DebuggerValue(usize),
288 // LSP
289 Hint(usize),
290 Color(usize),
291}
292
293impl InlayId {
294 fn id(&self) -> usize {
295 match self {
296 Self::EditPrediction(id) => *id,
297 Self::DebuggerValue(id) => *id,
298 Self::Hint(id) => *id,
299 Self::Color(id) => *id,
300 }
301 }
302}
303
304pub enum ActiveDebugLine {}
305pub enum DebugStackFrameLine {}
306enum DocumentHighlightRead {}
307enum DocumentHighlightWrite {}
308enum InputComposition {}
309pub enum PendingInput {}
310enum SelectedTextHighlight {}
311
312pub enum ConflictsOuter {}
313pub enum ConflictsOurs {}
314pub enum ConflictsTheirs {}
315pub enum ConflictsOursMarker {}
316pub enum ConflictsTheirsMarker {}
317
318#[derive(Debug, Copy, Clone, PartialEq, Eq)]
319pub enum Navigated {
320 Yes,
321 No,
322}
323
324impl Navigated {
325 pub fn from_bool(yes: bool) -> Navigated {
326 if yes { Navigated::Yes } else { Navigated::No }
327 }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
331enum DisplayDiffHunk {
332 Folded {
333 display_row: DisplayRow,
334 },
335 Unfolded {
336 is_created_file: bool,
337 diff_base_byte_range: Range<usize>,
338 display_row_range: Range<DisplayRow>,
339 multi_buffer_range: Range<Anchor>,
340 status: DiffHunkStatus,
341 },
342}
343
344pub enum HideMouseCursorOrigin {
345 TypingAction,
346 MovementAction,
347}
348
349pub fn init_settings(cx: &mut App) {
350 EditorSettings::register(cx);
351}
352
353pub fn init(cx: &mut App) {
354 init_settings(cx);
355
356 cx.set_global(GlobalBlameRenderer(Arc::new(())));
357
358 workspace::register_project_item::<Editor>(cx);
359 workspace::FollowableViewRegistry::register::<Editor>(cx);
360 workspace::register_serializable_item::<Editor>(cx);
361
362 cx.observe_new(
363 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
364 workspace.register_action(Editor::new_file);
365 workspace.register_action(Editor::new_file_vertical);
366 workspace.register_action(Editor::new_file_horizontal);
367 workspace.register_action(Editor::cancel_language_server_work);
368 workspace.register_action(Editor::toggle_focus);
369 },
370 )
371 .detach();
372
373 cx.on_action(move |_: &workspace::NewFile, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 Editor::new_file(workspace, &Default::default(), window, cx)
382 },
383 )
384 .detach();
385 }
386 });
387 cx.on_action(move |_: &workspace::NewWindow, cx| {
388 let app_state = workspace::AppState::global(cx);
389 if let Some(app_state) = app_state.upgrade() {
390 workspace::open_new(
391 Default::default(),
392 app_state,
393 cx,
394 |workspace, window, cx| {
395 cx.activate(true);
396 Editor::new_file(workspace, &Default::default(), window, cx)
397 },
398 )
399 .detach();
400 }
401 });
402}
403
404pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
405 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
406}
407
408pub trait DiagnosticRenderer {
409 fn render_group(
410 &self,
411 diagnostic_group: Vec<DiagnosticEntry<Point>>,
412 buffer_id: BufferId,
413 snapshot: EditorSnapshot,
414 editor: WeakEntity<Editor>,
415 cx: &mut App,
416 ) -> Vec<BlockProperties<Anchor>>;
417
418 fn render_hover(
419 &self,
420 diagnostic_group: Vec<DiagnosticEntry<Point>>,
421 range: Range<Point>,
422 buffer_id: BufferId,
423 cx: &mut App,
424 ) -> Option<Entity<markdown::Markdown>>;
425
426 fn open_link(
427 &self,
428 editor: &mut Editor,
429 link: SharedString,
430 window: &mut Window,
431 cx: &mut Context<Editor>,
432 );
433}
434
435pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
436
437impl GlobalDiagnosticRenderer {
438 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
439 cx.try_global::<Self>().map(|g| g.0.clone())
440 }
441}
442
443impl gpui::Global for GlobalDiagnosticRenderer {}
444pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
445 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
446}
447
448pub struct SearchWithinRange;
449
450trait InvalidationRegion {
451 fn ranges(&self) -> &[Range<Anchor>];
452}
453
454#[derive(Clone, Debug, PartialEq)]
455pub enum SelectPhase {
456 Begin {
457 position: DisplayPoint,
458 add: bool,
459 click_count: usize,
460 },
461 BeginColumnar {
462 position: DisplayPoint,
463 reset: bool,
464 mode: ColumnarMode,
465 goal_column: u32,
466 },
467 Extend {
468 position: DisplayPoint,
469 click_count: usize,
470 },
471 Update {
472 position: DisplayPoint,
473 goal_column: u32,
474 scroll_delta: gpui::Point<f32>,
475 },
476 End,
477}
478
479#[derive(Clone, Debug, PartialEq)]
480pub enum ColumnarMode {
481 FromMouse,
482 FromSelection,
483}
484
485#[derive(Clone, Debug)]
486pub enum SelectMode {
487 Character,
488 Word(Range<Anchor>),
489 Line(Range<Anchor>),
490 All,
491}
492
493#[derive(Clone, PartialEq, Eq, Debug)]
494pub enum EditorMode {
495 SingleLine,
496 AutoHeight {
497 min_lines: usize,
498 max_lines: Option<usize>,
499 },
500 Full {
501 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
502 scale_ui_elements_with_buffer_font_size: bool,
503 /// When set to `true`, the editor will render a background for the active line.
504 show_active_line_background: bool,
505 /// When set to `true`, the editor's height will be determined by its content.
506 sized_by_content: bool,
507 },
508 Minimap {
509 parent: WeakEntity<Editor>,
510 },
511}
512
513impl EditorMode {
514 pub fn full() -> Self {
515 Self::Full {
516 scale_ui_elements_with_buffer_font_size: true,
517 show_active_line_background: true,
518 sized_by_content: false,
519 }
520 }
521
522 #[inline]
523 pub fn is_full(&self) -> bool {
524 matches!(self, Self::Full { .. })
525 }
526
527 #[inline]
528 pub fn is_single_line(&self) -> bool {
529 matches!(self, Self::SingleLine { .. })
530 }
531
532 #[inline]
533 fn is_minimap(&self) -> bool {
534 matches!(self, Self::Minimap { .. })
535 }
536}
537
538#[derive(Copy, Clone, Debug)]
539pub enum SoftWrap {
540 /// Prefer not to wrap at all.
541 ///
542 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
543 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
544 GitDiff,
545 /// Prefer a single line generally, unless an overly long line is encountered.
546 None,
547 /// Soft wrap lines that exceed the editor width.
548 EditorWidth,
549 /// Soft wrap lines at the preferred line length.
550 Column(u32),
551 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
552 Bounded(u32),
553}
554
555#[derive(Clone)]
556pub struct EditorStyle {
557 pub background: Hsla,
558 pub border: Hsla,
559 pub local_player: PlayerColor,
560 pub text: TextStyle,
561 pub scrollbar_width: Pixels,
562 pub syntax: Arc<SyntaxTheme>,
563 pub status: StatusColors,
564 pub inlay_hints_style: HighlightStyle,
565 pub edit_prediction_styles: EditPredictionStyles,
566 pub unnecessary_code_fade: f32,
567 pub show_underlines: bool,
568}
569
570impl Default for EditorStyle {
571 fn default() -> Self {
572 Self {
573 background: Hsla::default(),
574 border: Hsla::default(),
575 local_player: PlayerColor::default(),
576 text: TextStyle::default(),
577 scrollbar_width: Pixels::default(),
578 syntax: Default::default(),
579 // HACK: Status colors don't have a real default.
580 // We should look into removing the status colors from the editor
581 // style and retrieve them directly from the theme.
582 status: StatusColors::dark(),
583 inlay_hints_style: HighlightStyle::default(),
584 edit_prediction_styles: EditPredictionStyles {
585 insertion: HighlightStyle::default(),
586 whitespace: HighlightStyle::default(),
587 },
588 unnecessary_code_fade: Default::default(),
589 show_underlines: true,
590 }
591 }
592}
593
594pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
595 let show_background = language_settings::language_settings(None, None, cx)
596 .inlay_hints
597 .show_background;
598
599 HighlightStyle {
600 color: Some(cx.theme().status().hint),
601 background_color: show_background.then(|| cx.theme().status().hint_background),
602 ..HighlightStyle::default()
603 }
604}
605
606pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
607 EditPredictionStyles {
608 insertion: HighlightStyle {
609 color: Some(cx.theme().status().predictive),
610 ..HighlightStyle::default()
611 },
612 whitespace: HighlightStyle {
613 background_color: Some(cx.theme().status().created_background),
614 ..HighlightStyle::default()
615 },
616 }
617}
618
619type CompletionId = usize;
620
621pub(crate) enum EditDisplayMode {
622 TabAccept,
623 DiffPopover,
624 Inline,
625}
626
627enum EditPrediction {
628 Edit {
629 edits: Vec<(Range<Anchor>, String)>,
630 edit_preview: Option<EditPreview>,
631 display_mode: EditDisplayMode,
632 snapshot: BufferSnapshot,
633 },
634 Move {
635 target: Anchor,
636 snapshot: BufferSnapshot,
637 },
638}
639
640struct EditPredictionState {
641 inlay_ids: Vec<InlayId>,
642 completion: EditPrediction,
643 completion_id: Option<SharedString>,
644 invalidation_range: Range<Anchor>,
645}
646
647enum EditPredictionSettings {
648 Disabled,
649 Enabled {
650 show_in_menu: bool,
651 preview_requires_modifier: bool,
652 },
653}
654
655enum EditPredictionHighlight {}
656
657#[derive(Debug, Clone)]
658struct InlineDiagnostic {
659 message: SharedString,
660 group_id: usize,
661 is_primary: bool,
662 start: Point,
663 severity: lsp::DiagnosticSeverity,
664}
665
666pub enum MenuEditPredictionsPolicy {
667 Never,
668 ByProvider,
669}
670
671pub enum EditPredictionPreview {
672 /// Modifier is not pressed
673 Inactive { released_too_fast: bool },
674 /// Modifier pressed
675 Active {
676 since: Instant,
677 previous_scroll_position: Option<ScrollAnchor>,
678 },
679}
680
681impl EditPredictionPreview {
682 pub fn released_too_fast(&self) -> bool {
683 match self {
684 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
685 EditPredictionPreview::Active { .. } => false,
686 }
687 }
688
689 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
690 if let EditPredictionPreview::Active {
691 previous_scroll_position,
692 ..
693 } = self
694 {
695 *previous_scroll_position = scroll_position;
696 }
697 }
698}
699
700pub struct ContextMenuOptions {
701 pub min_entries_visible: usize,
702 pub max_entries_visible: usize,
703 pub placement: Option<ContextMenuPlacement>,
704}
705
706#[derive(Debug, Clone, PartialEq, Eq)]
707pub enum ContextMenuPlacement {
708 Above,
709 Below,
710}
711
712#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
713struct EditorActionId(usize);
714
715impl EditorActionId {
716 pub fn post_inc(&mut self) -> Self {
717 let answer = self.0;
718
719 *self = Self(answer + 1);
720
721 Self(answer)
722 }
723}
724
725// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
726// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
727
728type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
729type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
730
731#[derive(Default)]
732struct ScrollbarMarkerState {
733 scrollbar_size: Size<Pixels>,
734 dirty: bool,
735 markers: Arc<[PaintQuad]>,
736 pending_refresh: Option<Task<Result<()>>>,
737}
738
739impl ScrollbarMarkerState {
740 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
741 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
742 }
743}
744
745#[derive(Clone, Copy, PartialEq, Eq)]
746pub enum MinimapVisibility {
747 Disabled,
748 Enabled {
749 /// The configuration currently present in the users settings.
750 setting_configuration: bool,
751 /// Whether to override the currently set visibility from the users setting.
752 toggle_override: bool,
753 },
754}
755
756impl MinimapVisibility {
757 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
758 if mode.is_full() {
759 Self::Enabled {
760 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
761 toggle_override: false,
762 }
763 } else {
764 Self::Disabled
765 }
766 }
767
768 fn hidden(&self) -> Self {
769 match *self {
770 Self::Enabled {
771 setting_configuration,
772 ..
773 } => Self::Enabled {
774 setting_configuration,
775 toggle_override: setting_configuration,
776 },
777 Self::Disabled => Self::Disabled,
778 }
779 }
780
781 fn disabled(&self) -> bool {
782 matches!(*self, Self::Disabled)
783 }
784
785 fn settings_visibility(&self) -> bool {
786 match *self {
787 Self::Enabled {
788 setting_configuration,
789 ..
790 } => setting_configuration,
791 _ => false,
792 }
793 }
794
795 fn visible(&self) -> bool {
796 match *self {
797 Self::Enabled {
798 setting_configuration,
799 toggle_override,
800 } => setting_configuration ^ toggle_override,
801 _ => false,
802 }
803 }
804
805 fn toggle_visibility(&self) -> Self {
806 match *self {
807 Self::Enabled {
808 toggle_override,
809 setting_configuration,
810 } => Self::Enabled {
811 setting_configuration,
812 toggle_override: !toggle_override,
813 },
814 Self::Disabled => Self::Disabled,
815 }
816 }
817}
818
819#[derive(Clone, Debug)]
820struct RunnableTasks {
821 templates: Vec<(TaskSourceKind, TaskTemplate)>,
822 offset: multi_buffer::Anchor,
823 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
824 column: u32,
825 // Values of all named captures, including those starting with '_'
826 extra_variables: HashMap<String, String>,
827 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
828 context_range: Range<BufferOffset>,
829}
830
831impl RunnableTasks {
832 fn resolve<'a>(
833 &'a self,
834 cx: &'a task::TaskContext,
835 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
836 self.templates.iter().filter_map(|(kind, template)| {
837 template
838 .resolve_task(&kind.to_id_base(), cx)
839 .map(|task| (kind.clone(), task))
840 })
841 }
842}
843
844#[derive(Clone)]
845pub struct ResolvedTasks {
846 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
847 position: Anchor,
848}
849
850#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
851struct BufferOffset(usize);
852
853// Addons allow storing per-editor state in other crates (e.g. Vim)
854pub trait Addon: 'static {
855 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
856
857 fn render_buffer_header_controls(
858 &self,
859 _: &ExcerptInfo,
860 _: &Window,
861 _: &App,
862 ) -> Option<AnyElement> {
863 None
864 }
865
866 fn to_any(&self) -> &dyn std::any::Any;
867
868 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
869 None
870 }
871}
872
873struct ChangeLocation {
874 current: Option<Vec<Anchor>>,
875 original: Vec<Anchor>,
876}
877impl ChangeLocation {
878 fn locations(&self) -> &[Anchor] {
879 self.current.as_ref().unwrap_or(&self.original)
880 }
881}
882
883/// A set of caret positions, registered when the editor was edited.
884pub struct ChangeList {
885 changes: Vec<ChangeLocation>,
886 /// Currently "selected" change.
887 position: Option<usize>,
888}
889
890impl ChangeList {
891 pub fn new() -> Self {
892 Self {
893 changes: Vec::new(),
894 position: None,
895 }
896 }
897
898 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
899 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
900 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
901 if self.changes.is_empty() {
902 return None;
903 }
904
905 let prev = self.position.unwrap_or(self.changes.len());
906 let next = if direction == Direction::Prev {
907 prev.saturating_sub(count)
908 } else {
909 (prev + count).min(self.changes.len() - 1)
910 };
911 self.position = Some(next);
912 self.changes.get(next).map(|change| change.locations())
913 }
914
915 /// Adds a new change to the list, resetting the change list position.
916 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
917 self.position.take();
918 if let Some(last) = self.changes.last_mut()
919 && group
920 {
921 last.current = Some(new_positions)
922 } else {
923 self.changes.push(ChangeLocation {
924 original: new_positions,
925 current: None,
926 });
927 }
928 }
929
930 pub fn last(&self) -> Option<&[Anchor]> {
931 self.changes.last().map(|change| change.locations())
932 }
933
934 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
935 self.changes.last().map(|change| change.original.as_slice())
936 }
937
938 pub fn invert_last_group(&mut self) {
939 if let Some(last) = self.changes.last_mut()
940 && let Some(current) = last.current.as_mut()
941 {
942 mem::swap(&mut last.original, current);
943 }
944 }
945}
946
947#[derive(Clone)]
948struct InlineBlamePopoverState {
949 scroll_handle: ScrollHandle,
950 commit_message: Option<ParsedCommitMessage>,
951 markdown: Entity<Markdown>,
952}
953
954struct InlineBlamePopover {
955 position: gpui::Point<Pixels>,
956 hide_task: Option<Task<()>>,
957 popover_bounds: Option<Bounds<Pixels>>,
958 popover_state: InlineBlamePopoverState,
959 keyboard_grace: bool,
960}
961
962enum SelectionDragState {
963 /// State when no drag related activity is detected.
964 None,
965 /// State when the mouse is down on a selection that is about to be dragged.
966 ReadyToDrag {
967 selection: Selection<Anchor>,
968 click_position: gpui::Point<Pixels>,
969 mouse_down_time: Instant,
970 },
971 /// State when the mouse is dragging the selection in the editor.
972 Dragging {
973 selection: Selection<Anchor>,
974 drop_cursor: Selection<Anchor>,
975 hide_drop_cursor: bool,
976 },
977}
978
979enum ColumnarSelectionState {
980 FromMouse {
981 selection_tail: Anchor,
982 display_point: Option<DisplayPoint>,
983 },
984 FromSelection {
985 selection_tail: Anchor,
986 },
987}
988
989/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
990/// a breakpoint on them.
991#[derive(Clone, Copy, Debug, PartialEq, Eq)]
992struct PhantomBreakpointIndicator {
993 display_row: DisplayRow,
994 /// There's a small debounce between hovering over the line and showing the indicator.
995 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
996 is_active: bool,
997 collides_with_existing_breakpoint: bool,
998}
999
1000/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1001///
1002/// See the [module level documentation](self) for more information.
1003pub struct Editor {
1004 focus_handle: FocusHandle,
1005 last_focused_descendant: Option<WeakFocusHandle>,
1006 /// The text buffer being edited
1007 buffer: Entity<MultiBuffer>,
1008 /// Map of how text in the buffer should be displayed.
1009 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1010 pub display_map: Entity<DisplayMap>,
1011 pub selections: SelectionsCollection,
1012 pub scroll_manager: ScrollManager,
1013 /// When inline assist editors are linked, they all render cursors because
1014 /// typing enters text into each of them, even the ones that aren't focused.
1015 pub(crate) show_cursor_when_unfocused: bool,
1016 columnar_selection_state: Option<ColumnarSelectionState>,
1017 add_selections_state: Option<AddSelectionsState>,
1018 select_next_state: Option<SelectNextState>,
1019 select_prev_state: Option<SelectNextState>,
1020 selection_history: SelectionHistory,
1021 defer_selection_effects: bool,
1022 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1023 autoclose_regions: Vec<AutocloseRegion>,
1024 snippet_stack: InvalidationStack<SnippetState>,
1025 select_syntax_node_history: SelectSyntaxNodeHistory,
1026 ime_transaction: Option<TransactionId>,
1027 pub diagnostics_max_severity: DiagnosticSeverity,
1028 active_diagnostics: ActiveDiagnostic,
1029 show_inline_diagnostics: bool,
1030 inline_diagnostics_update: Task<()>,
1031 inline_diagnostics_enabled: bool,
1032 diagnostics_enabled: bool,
1033 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1034 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1035 hard_wrap: Option<usize>,
1036 project: Option<Entity<Project>>,
1037 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1038 completion_provider: Option<Rc<dyn CompletionProvider>>,
1039 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1040 blink_manager: Entity<BlinkManager>,
1041 show_cursor_names: bool,
1042 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1043 pub show_local_selections: bool,
1044 mode: EditorMode,
1045 show_breadcrumbs: bool,
1046 show_gutter: bool,
1047 show_scrollbars: ScrollbarAxes,
1048 minimap_visibility: MinimapVisibility,
1049 offset_content: bool,
1050 disable_expand_excerpt_buttons: bool,
1051 show_line_numbers: Option<bool>,
1052 use_relative_line_numbers: Option<bool>,
1053 show_git_diff_gutter: Option<bool>,
1054 show_code_actions: Option<bool>,
1055 show_runnables: Option<bool>,
1056 show_breakpoints: Option<bool>,
1057 show_wrap_guides: Option<bool>,
1058 show_indent_guides: Option<bool>,
1059 placeholder_text: Option<Arc<str>>,
1060 highlight_order: usize,
1061 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1062 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1063 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1064 scrollbar_marker_state: ScrollbarMarkerState,
1065 active_indent_guides_state: ActiveIndentGuidesState,
1066 nav_history: Option<ItemNavHistory>,
1067 context_menu: RefCell<Option<CodeContextMenu>>,
1068 context_menu_options: Option<ContextMenuOptions>,
1069 mouse_context_menu: Option<MouseContextMenu>,
1070 completion_tasks: Vec<(CompletionId, Task<()>)>,
1071 inline_blame_popover: Option<InlineBlamePopover>,
1072 inline_blame_popover_show_task: Option<Task<()>>,
1073 signature_help_state: SignatureHelpState,
1074 auto_signature_help: Option<bool>,
1075 find_all_references_task_sources: Vec<Anchor>,
1076 next_completion_id: CompletionId,
1077 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1078 code_actions_task: Option<Task<Result<()>>>,
1079 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1080 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1081 document_highlights_task: Option<Task<()>>,
1082 linked_editing_range_task: Option<Task<Option<()>>>,
1083 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1084 pending_rename: Option<RenameState>,
1085 searchable: bool,
1086 cursor_shape: CursorShape,
1087 current_line_highlight: Option<CurrentLineHighlight>,
1088 collapse_matches: bool,
1089 autoindent_mode: Option<AutoindentMode>,
1090 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1091 input_enabled: bool,
1092 use_modal_editing: bool,
1093 read_only: bool,
1094 leader_id: Option<CollaboratorId>,
1095 remote_id: Option<ViewId>,
1096 pub hover_state: HoverState,
1097 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1098 gutter_hovered: bool,
1099 hovered_link_state: Option<HoveredLinkState>,
1100 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1101 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1102 active_edit_prediction: Option<EditPredictionState>,
1103 /// Used to prevent flickering as the user types while the menu is open
1104 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1105 edit_prediction_settings: EditPredictionSettings,
1106 edit_predictions_hidden_for_vim_mode: bool,
1107 show_edit_predictions_override: Option<bool>,
1108 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1109 edit_prediction_preview: EditPredictionPreview,
1110 edit_prediction_indent_conflict: bool,
1111 edit_prediction_requires_modifier_in_indent_conflict: bool,
1112 inlay_hint_cache: InlayHintCache,
1113 next_inlay_id: usize,
1114 _subscriptions: Vec<Subscription>,
1115 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1116 gutter_dimensions: GutterDimensions,
1117 style: Option<EditorStyle>,
1118 text_style_refinement: Option<TextStyleRefinement>,
1119 next_editor_action_id: EditorActionId,
1120 editor_actions: Rc<
1121 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1122 >,
1123 use_autoclose: bool,
1124 use_auto_surround: bool,
1125 auto_replace_emoji_shortcode: bool,
1126 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1127 show_git_blame_gutter: bool,
1128 show_git_blame_inline: bool,
1129 show_git_blame_inline_delay_task: Option<Task<()>>,
1130 git_blame_inline_enabled: bool,
1131 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1132 serialize_dirty_buffers: bool,
1133 show_selection_menu: Option<bool>,
1134 blame: Option<Entity<GitBlame>>,
1135 blame_subscription: Option<Subscription>,
1136 custom_context_menu: Option<
1137 Box<
1138 dyn 'static
1139 + Fn(
1140 &mut Self,
1141 DisplayPoint,
1142 &mut Window,
1143 &mut Context<Self>,
1144 ) -> Option<Entity<ui::ContextMenu>>,
1145 >,
1146 >,
1147 last_bounds: Option<Bounds<Pixels>>,
1148 last_position_map: Option<Rc<PositionMap>>,
1149 expect_bounds_change: Option<Bounds<Pixels>>,
1150 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1151 tasks_update_task: Option<Task<()>>,
1152 breakpoint_store: Option<Entity<BreakpointStore>>,
1153 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1154 hovered_diff_hunk_row: Option<DisplayRow>,
1155 pull_diagnostics_task: Task<()>,
1156 in_project_search: bool,
1157 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1158 breadcrumb_header: Option<String>,
1159 focused_block: Option<FocusedBlock>,
1160 next_scroll_position: NextScrollCursorCenterTopBottom,
1161 addons: HashMap<TypeId, Box<dyn Addon>>,
1162 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1163 load_diff_task: Option<Shared<Task<()>>>,
1164 /// Whether we are temporarily displaying a diff other than git's
1165 temporary_diff_override: bool,
1166 selection_mark_mode: bool,
1167 toggle_fold_multiple_buffers: Task<()>,
1168 _scroll_cursor_center_top_bottom_task: Task<()>,
1169 serialize_selections: Task<()>,
1170 serialize_folds: Task<()>,
1171 mouse_cursor_hidden: bool,
1172 minimap: Option<Entity<Self>>,
1173 hide_mouse_mode: HideMouseMode,
1174 pub change_list: ChangeList,
1175 inline_value_cache: InlineValueCache,
1176 selection_drag_state: SelectionDragState,
1177 next_color_inlay_id: usize,
1178 colors: Option<LspColorData>,
1179 folding_newlines: Task<()>,
1180}
1181
1182#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1183enum NextScrollCursorCenterTopBottom {
1184 #[default]
1185 Center,
1186 Top,
1187 Bottom,
1188}
1189
1190impl NextScrollCursorCenterTopBottom {
1191 fn next(&self) -> Self {
1192 match self {
1193 Self::Center => Self::Top,
1194 Self::Top => Self::Bottom,
1195 Self::Bottom => Self::Center,
1196 }
1197 }
1198}
1199
1200#[derive(Clone)]
1201pub struct EditorSnapshot {
1202 pub mode: EditorMode,
1203 show_gutter: bool,
1204 show_line_numbers: Option<bool>,
1205 show_git_diff_gutter: Option<bool>,
1206 show_code_actions: Option<bool>,
1207 show_runnables: Option<bool>,
1208 show_breakpoints: Option<bool>,
1209 git_blame_gutter_max_author_length: Option<usize>,
1210 pub display_snapshot: DisplaySnapshot,
1211 pub placeholder_text: Option<Arc<str>>,
1212 is_focused: bool,
1213 scroll_anchor: ScrollAnchor,
1214 ongoing_scroll: OngoingScroll,
1215 current_line_highlight: CurrentLineHighlight,
1216 gutter_hovered: bool,
1217}
1218
1219#[derive(Default, Debug, Clone, Copy)]
1220pub struct GutterDimensions {
1221 pub left_padding: Pixels,
1222 pub right_padding: Pixels,
1223 pub width: Pixels,
1224 pub margin: Pixels,
1225 pub git_blame_entries_width: Option<Pixels>,
1226}
1227
1228impl GutterDimensions {
1229 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1230 Self {
1231 margin: Self::default_gutter_margin(font_id, font_size, cx),
1232 ..Default::default()
1233 }
1234 }
1235
1236 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1237 -cx.text_system().descent(font_id, font_size)
1238 }
1239 /// The full width of the space taken up by the gutter.
1240 pub fn full_width(&self) -> Pixels {
1241 self.margin + self.width
1242 }
1243
1244 /// The width of the space reserved for the fold indicators,
1245 /// use alongside 'justify_end' and `gutter_width` to
1246 /// right align content with the line numbers
1247 pub fn fold_area_width(&self) -> Pixels {
1248 self.margin + self.right_padding
1249 }
1250}
1251
1252struct CharacterDimensions {
1253 em_width: Pixels,
1254 em_advance: Pixels,
1255 line_height: Pixels,
1256}
1257
1258#[derive(Debug)]
1259pub struct RemoteSelection {
1260 pub replica_id: ReplicaId,
1261 pub selection: Selection<Anchor>,
1262 pub cursor_shape: CursorShape,
1263 pub collaborator_id: CollaboratorId,
1264 pub line_mode: bool,
1265 pub user_name: Option<SharedString>,
1266 pub color: PlayerColor,
1267}
1268
1269#[derive(Clone, Debug)]
1270struct SelectionHistoryEntry {
1271 selections: Arc<[Selection<Anchor>]>,
1272 select_next_state: Option<SelectNextState>,
1273 select_prev_state: Option<SelectNextState>,
1274 add_selections_state: Option<AddSelectionsState>,
1275}
1276
1277#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1278enum SelectionHistoryMode {
1279 Normal,
1280 Undoing,
1281 Redoing,
1282 Skipping,
1283}
1284
1285#[derive(Clone, PartialEq, Eq, Hash)]
1286struct HoveredCursor {
1287 replica_id: u16,
1288 selection_id: usize,
1289}
1290
1291impl Default for SelectionHistoryMode {
1292 fn default() -> Self {
1293 Self::Normal
1294 }
1295}
1296
1297#[derive(Debug)]
1298/// SelectionEffects controls the side-effects of updating the selection.
1299///
1300/// The default behaviour does "what you mostly want":
1301/// - it pushes to the nav history if the cursor moved by >10 lines
1302/// - it re-triggers completion requests
1303/// - it scrolls to fit
1304///
1305/// You might want to modify these behaviours. For example when doing a "jump"
1306/// like go to definition, we always want to add to nav history; but when scrolling
1307/// in vim mode we never do.
1308///
1309/// Similarly, you might want to disable scrolling if you don't want the viewport to
1310/// move.
1311#[derive(Clone)]
1312pub struct SelectionEffects {
1313 nav_history: Option<bool>,
1314 completions: bool,
1315 scroll: Option<Autoscroll>,
1316}
1317
1318impl Default for SelectionEffects {
1319 fn default() -> Self {
1320 Self {
1321 nav_history: None,
1322 completions: true,
1323 scroll: Some(Autoscroll::fit()),
1324 }
1325 }
1326}
1327impl SelectionEffects {
1328 pub fn scroll(scroll: Autoscroll) -> Self {
1329 Self {
1330 scroll: Some(scroll),
1331 ..Default::default()
1332 }
1333 }
1334
1335 pub fn no_scroll() -> Self {
1336 Self {
1337 scroll: None,
1338 ..Default::default()
1339 }
1340 }
1341
1342 pub fn completions(self, completions: bool) -> Self {
1343 Self {
1344 completions,
1345 ..self
1346 }
1347 }
1348
1349 pub fn nav_history(self, nav_history: bool) -> Self {
1350 Self {
1351 nav_history: Some(nav_history),
1352 ..self
1353 }
1354 }
1355}
1356
1357struct DeferredSelectionEffectsState {
1358 changed: bool,
1359 effects: SelectionEffects,
1360 old_cursor_position: Anchor,
1361 history_entry: SelectionHistoryEntry,
1362}
1363
1364#[derive(Default)]
1365struct SelectionHistory {
1366 #[allow(clippy::type_complexity)]
1367 selections_by_transaction:
1368 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1369 mode: SelectionHistoryMode,
1370 undo_stack: VecDeque<SelectionHistoryEntry>,
1371 redo_stack: VecDeque<SelectionHistoryEntry>,
1372}
1373
1374impl SelectionHistory {
1375 #[track_caller]
1376 fn insert_transaction(
1377 &mut self,
1378 transaction_id: TransactionId,
1379 selections: Arc<[Selection<Anchor>]>,
1380 ) {
1381 if selections.is_empty() {
1382 log::error!(
1383 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1384 std::panic::Location::caller()
1385 );
1386 return;
1387 }
1388 self.selections_by_transaction
1389 .insert(transaction_id, (selections, None));
1390 }
1391
1392 #[allow(clippy::type_complexity)]
1393 fn transaction(
1394 &self,
1395 transaction_id: TransactionId,
1396 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1397 self.selections_by_transaction.get(&transaction_id)
1398 }
1399
1400 #[allow(clippy::type_complexity)]
1401 fn transaction_mut(
1402 &mut self,
1403 transaction_id: TransactionId,
1404 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1405 self.selections_by_transaction.get_mut(&transaction_id)
1406 }
1407
1408 fn push(&mut self, entry: SelectionHistoryEntry) {
1409 if !entry.selections.is_empty() {
1410 match self.mode {
1411 SelectionHistoryMode::Normal => {
1412 self.push_undo(entry);
1413 self.redo_stack.clear();
1414 }
1415 SelectionHistoryMode::Undoing => self.push_redo(entry),
1416 SelectionHistoryMode::Redoing => self.push_undo(entry),
1417 SelectionHistoryMode::Skipping => {}
1418 }
1419 }
1420 }
1421
1422 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1423 if self
1424 .undo_stack
1425 .back()
1426 .is_none_or(|e| e.selections != entry.selections)
1427 {
1428 self.undo_stack.push_back(entry);
1429 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1430 self.undo_stack.pop_front();
1431 }
1432 }
1433 }
1434
1435 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1436 if self
1437 .redo_stack
1438 .back()
1439 .is_none_or(|e| e.selections != entry.selections)
1440 {
1441 self.redo_stack.push_back(entry);
1442 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1443 self.redo_stack.pop_front();
1444 }
1445 }
1446 }
1447}
1448
1449#[derive(Clone, Copy)]
1450pub struct RowHighlightOptions {
1451 pub autoscroll: bool,
1452 pub include_gutter: bool,
1453}
1454
1455impl Default for RowHighlightOptions {
1456 fn default() -> Self {
1457 Self {
1458 autoscroll: Default::default(),
1459 include_gutter: true,
1460 }
1461 }
1462}
1463
1464struct RowHighlight {
1465 index: usize,
1466 range: Range<Anchor>,
1467 color: Hsla,
1468 options: RowHighlightOptions,
1469 type_id: TypeId,
1470}
1471
1472#[derive(Clone, Debug)]
1473struct AddSelectionsState {
1474 groups: Vec<AddSelectionsGroup>,
1475}
1476
1477#[derive(Clone, Debug)]
1478struct AddSelectionsGroup {
1479 above: bool,
1480 stack: Vec<usize>,
1481}
1482
1483#[derive(Clone)]
1484struct SelectNextState {
1485 query: AhoCorasick,
1486 wordwise: bool,
1487 done: bool,
1488}
1489
1490impl std::fmt::Debug for SelectNextState {
1491 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1492 f.debug_struct(std::any::type_name::<Self>())
1493 .field("wordwise", &self.wordwise)
1494 .field("done", &self.done)
1495 .finish()
1496 }
1497}
1498
1499#[derive(Debug)]
1500struct AutocloseRegion {
1501 selection_id: usize,
1502 range: Range<Anchor>,
1503 pair: BracketPair,
1504}
1505
1506#[derive(Debug)]
1507struct SnippetState {
1508 ranges: Vec<Vec<Range<Anchor>>>,
1509 active_index: usize,
1510 choices: Vec<Option<Vec<String>>>,
1511}
1512
1513#[doc(hidden)]
1514pub struct RenameState {
1515 pub range: Range<Anchor>,
1516 pub old_name: Arc<str>,
1517 pub editor: Entity<Editor>,
1518 block_id: CustomBlockId,
1519}
1520
1521struct InvalidationStack<T>(Vec<T>);
1522
1523struct RegisteredEditPredictionProvider {
1524 provider: Arc<dyn EditPredictionProviderHandle>,
1525 _subscription: Subscription,
1526}
1527
1528#[derive(Debug, PartialEq, Eq)]
1529pub struct ActiveDiagnosticGroup {
1530 pub active_range: Range<Anchor>,
1531 pub active_message: String,
1532 pub group_id: usize,
1533 pub blocks: HashSet<CustomBlockId>,
1534}
1535
1536#[derive(Debug, PartialEq, Eq)]
1537
1538pub(crate) enum ActiveDiagnostic {
1539 None,
1540 All,
1541 Group(ActiveDiagnosticGroup),
1542}
1543
1544#[derive(Serialize, Deserialize, Clone, Debug)]
1545pub struct ClipboardSelection {
1546 /// The number of bytes in this selection.
1547 pub len: usize,
1548 /// Whether this was a full-line selection.
1549 pub is_entire_line: bool,
1550 /// The indentation of the first line when this content was originally copied.
1551 pub first_line_indent: u32,
1552}
1553
1554// selections, scroll behavior, was newest selection reversed
1555type SelectSyntaxNodeHistoryState = (
1556 Box<[Selection<usize>]>,
1557 SelectSyntaxNodeScrollBehavior,
1558 bool,
1559);
1560
1561#[derive(Default)]
1562struct SelectSyntaxNodeHistory {
1563 stack: Vec<SelectSyntaxNodeHistoryState>,
1564 // disable temporarily to allow changing selections without losing the stack
1565 pub disable_clearing: bool,
1566}
1567
1568impl SelectSyntaxNodeHistory {
1569 pub fn try_clear(&mut self) {
1570 if !self.disable_clearing {
1571 self.stack.clear();
1572 }
1573 }
1574
1575 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1576 self.stack.push(selection);
1577 }
1578
1579 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1580 self.stack.pop()
1581 }
1582}
1583
1584enum SelectSyntaxNodeScrollBehavior {
1585 CursorTop,
1586 FitSelection,
1587 CursorBottom,
1588}
1589
1590#[derive(Debug)]
1591pub(crate) struct NavigationData {
1592 cursor_anchor: Anchor,
1593 cursor_position: Point,
1594 scroll_anchor: ScrollAnchor,
1595 scroll_top_row: u32,
1596}
1597
1598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1599pub enum GotoDefinitionKind {
1600 Symbol,
1601 Declaration,
1602 Type,
1603 Implementation,
1604}
1605
1606#[derive(Debug, Clone)]
1607enum InlayHintRefreshReason {
1608 ModifiersChanged(bool),
1609 Toggle(bool),
1610 SettingsChange(InlayHintSettings),
1611 NewLinesShown,
1612 BufferEdited(HashSet<Arc<Language>>),
1613 RefreshRequested,
1614 ExcerptsRemoved(Vec<ExcerptId>),
1615}
1616
1617impl InlayHintRefreshReason {
1618 fn description(&self) -> &'static str {
1619 match self {
1620 Self::ModifiersChanged(_) => "modifiers changed",
1621 Self::Toggle(_) => "toggle",
1622 Self::SettingsChange(_) => "settings change",
1623 Self::NewLinesShown => "new lines shown",
1624 Self::BufferEdited(_) => "buffer edited",
1625 Self::RefreshRequested => "refresh requested",
1626 Self::ExcerptsRemoved(_) => "excerpts removed",
1627 }
1628 }
1629}
1630
1631pub enum FormatTarget {
1632 Buffers(HashSet<Entity<Buffer>>),
1633 Ranges(Vec<Range<MultiBufferPoint>>),
1634}
1635
1636pub(crate) struct FocusedBlock {
1637 id: BlockId,
1638 focus_handle: WeakFocusHandle,
1639}
1640
1641#[derive(Clone)]
1642enum JumpData {
1643 MultiBufferRow {
1644 row: MultiBufferRow,
1645 line_offset_from_top: u32,
1646 },
1647 MultiBufferPoint {
1648 excerpt_id: ExcerptId,
1649 position: Point,
1650 anchor: text::Anchor,
1651 line_offset_from_top: u32,
1652 },
1653}
1654
1655pub enum MultibufferSelectionMode {
1656 First,
1657 All,
1658}
1659
1660#[derive(Clone, Copy, Debug, Default)]
1661pub struct RewrapOptions {
1662 pub override_language_settings: bool,
1663 pub preserve_existing_whitespace: bool,
1664}
1665
1666impl Editor {
1667 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1668 let buffer = cx.new(|cx| Buffer::local("", cx));
1669 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1670 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1671 }
1672
1673 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let buffer = cx.new(|cx| Buffer::local("", cx));
1675 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1676 Self::new(EditorMode::full(), buffer, None, window, cx)
1677 }
1678
1679 pub fn auto_height(
1680 min_lines: usize,
1681 max_lines: usize,
1682 window: &mut Window,
1683 cx: &mut Context<Self>,
1684 ) -> Self {
1685 let buffer = cx.new(|cx| Buffer::local("", cx));
1686 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1687 Self::new(
1688 EditorMode::AutoHeight {
1689 min_lines,
1690 max_lines: Some(max_lines),
1691 },
1692 buffer,
1693 None,
1694 window,
1695 cx,
1696 )
1697 }
1698
1699 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1700 /// The editor grows as tall as needed to fit its content.
1701 pub fn auto_height_unbounded(
1702 min_lines: usize,
1703 window: &mut Window,
1704 cx: &mut Context<Self>,
1705 ) -> Self {
1706 let buffer = cx.new(|cx| Buffer::local("", cx));
1707 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1708 Self::new(
1709 EditorMode::AutoHeight {
1710 min_lines,
1711 max_lines: None,
1712 },
1713 buffer,
1714 None,
1715 window,
1716 cx,
1717 )
1718 }
1719
1720 pub fn for_buffer(
1721 buffer: Entity<Buffer>,
1722 project: Option<Entity<Project>>,
1723 window: &mut Window,
1724 cx: &mut Context<Self>,
1725 ) -> Self {
1726 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1727 Self::new(EditorMode::full(), buffer, project, window, cx)
1728 }
1729
1730 pub fn for_multibuffer(
1731 buffer: Entity<MultiBuffer>,
1732 project: Option<Entity<Project>>,
1733 window: &mut Window,
1734 cx: &mut Context<Self>,
1735 ) -> Self {
1736 Self::new(EditorMode::full(), buffer, project, window, cx)
1737 }
1738
1739 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1740 let mut clone = Self::new(
1741 self.mode.clone(),
1742 self.buffer.clone(),
1743 self.project.clone(),
1744 window,
1745 cx,
1746 );
1747 self.display_map.update(cx, |display_map, cx| {
1748 let snapshot = display_map.snapshot(cx);
1749 clone.display_map.update(cx, |display_map, cx| {
1750 display_map.set_state(&snapshot, cx);
1751 });
1752 });
1753 clone.folds_did_change(cx);
1754 clone.selections.clone_state(&self.selections);
1755 clone.scroll_manager.clone_state(&self.scroll_manager);
1756 clone.searchable = self.searchable;
1757 clone.read_only = self.read_only;
1758 clone
1759 }
1760
1761 pub fn new(
1762 mode: EditorMode,
1763 buffer: Entity<MultiBuffer>,
1764 project: Option<Entity<Project>>,
1765 window: &mut Window,
1766 cx: &mut Context<Self>,
1767 ) -> Self {
1768 Editor::new_internal(mode, buffer, project, None, window, cx)
1769 }
1770
1771 fn new_internal(
1772 mode: EditorMode,
1773 buffer: Entity<MultiBuffer>,
1774 project: Option<Entity<Project>>,
1775 display_map: Option<Entity<DisplayMap>>,
1776 window: &mut Window,
1777 cx: &mut Context<Self>,
1778 ) -> Self {
1779 debug_assert!(
1780 display_map.is_none() || mode.is_minimap(),
1781 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1782 );
1783
1784 let full_mode = mode.is_full();
1785 let is_minimap = mode.is_minimap();
1786 let diagnostics_max_severity = if full_mode {
1787 EditorSettings::get_global(cx)
1788 .diagnostics_max_severity
1789 .unwrap_or(DiagnosticSeverity::Hint)
1790 } else {
1791 DiagnosticSeverity::Off
1792 };
1793 let style = window.text_style();
1794 let font_size = style.font_size.to_pixels(window.rem_size());
1795 let editor = cx.entity().downgrade();
1796 let fold_placeholder = FoldPlaceholder {
1797 constrain_width: true,
1798 render: Arc::new(move |fold_id, fold_range, cx| {
1799 let editor = editor.clone();
1800 div()
1801 .id(fold_id)
1802 .bg(cx.theme().colors().ghost_element_background)
1803 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1804 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1805 .rounded_xs()
1806 .size_full()
1807 .cursor_pointer()
1808 .child("⋯")
1809 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1810 .on_click(move |_, _window, cx| {
1811 editor
1812 .update(cx, |editor, cx| {
1813 editor.unfold_ranges(
1814 &[fold_range.start..fold_range.end],
1815 true,
1816 false,
1817 cx,
1818 );
1819 cx.stop_propagation();
1820 })
1821 .ok();
1822 })
1823 .into_any()
1824 }),
1825 merge_adjacent: true,
1826 ..FoldPlaceholder::default()
1827 };
1828 let display_map = display_map.unwrap_or_else(|| {
1829 cx.new(|cx| {
1830 DisplayMap::new(
1831 buffer.clone(),
1832 style.font(),
1833 font_size,
1834 None,
1835 FILE_HEADER_HEIGHT,
1836 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1837 fold_placeholder,
1838 diagnostics_max_severity,
1839 cx,
1840 )
1841 })
1842 });
1843
1844 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1845
1846 let blink_manager = cx.new(|cx| {
1847 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1848 if is_minimap {
1849 blink_manager.disable(cx);
1850 }
1851 blink_manager
1852 });
1853
1854 let soft_wrap_mode_override =
1855 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1856
1857 let mut project_subscriptions = Vec::new();
1858 if full_mode && let Some(project) = project.as_ref() {
1859 project_subscriptions.push(cx.subscribe_in(
1860 project,
1861 window,
1862 |editor, _, event, window, cx| match event {
1863 project::Event::RefreshCodeLens => {
1864 // we always query lens with actions, without storing them, always refreshing them
1865 }
1866 project::Event::RefreshInlayHints => {
1867 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1868 }
1869 project::Event::LanguageServerAdded(..)
1870 | project::Event::LanguageServerRemoved(..) => {
1871 if editor.tasks_update_task.is_none() {
1872 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1873 }
1874 }
1875 project::Event::SnippetEdit(id, snippet_edits) => {
1876 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1877 let focus_handle = editor.focus_handle(cx);
1878 if focus_handle.is_focused(window) {
1879 let snapshot = buffer.read(cx).snapshot();
1880 for (range, snippet) in snippet_edits {
1881 let editor_range =
1882 language::range_from_lsp(*range).to_offset(&snapshot);
1883 editor
1884 .insert_snippet(
1885 &[editor_range],
1886 snippet.clone(),
1887 window,
1888 cx,
1889 )
1890 .ok();
1891 }
1892 }
1893 }
1894 }
1895 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1896 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1897 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1898 }
1899 }
1900
1901 project::Event::EntryRenamed(transaction) => {
1902 let Some(workspace) = editor.workspace() else {
1903 return;
1904 };
1905 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1906 else {
1907 return;
1908 };
1909 if active_editor.entity_id() == cx.entity_id() {
1910 let edited_buffers_already_open = {
1911 let other_editors: Vec<Entity<Editor>> = workspace
1912 .read(cx)
1913 .panes()
1914 .iter()
1915 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1916 .filter(|editor| editor.entity_id() != cx.entity_id())
1917 .collect();
1918
1919 transaction.0.keys().all(|buffer| {
1920 other_editors.iter().any(|editor| {
1921 let multi_buffer = editor.read(cx).buffer();
1922 multi_buffer.read(cx).is_singleton()
1923 && multi_buffer.read(cx).as_singleton().map_or(
1924 false,
1925 |singleton| {
1926 singleton.entity_id() == buffer.entity_id()
1927 },
1928 )
1929 })
1930 })
1931 };
1932
1933 if !edited_buffers_already_open {
1934 let workspace = workspace.downgrade();
1935 let transaction = transaction.clone();
1936 cx.defer_in(window, move |_, window, cx| {
1937 cx.spawn_in(window, async move |editor, cx| {
1938 Self::open_project_transaction(
1939 &editor,
1940 workspace,
1941 transaction,
1942 "Rename".to_string(),
1943 cx,
1944 )
1945 .await
1946 .ok()
1947 })
1948 .detach();
1949 });
1950 }
1951 }
1952 }
1953
1954 _ => {}
1955 },
1956 ));
1957 if let Some(task_inventory) = project
1958 .read(cx)
1959 .task_store()
1960 .read(cx)
1961 .task_inventory()
1962 .cloned()
1963 {
1964 project_subscriptions.push(cx.observe_in(
1965 &task_inventory,
1966 window,
1967 |editor, _, window, cx| {
1968 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1969 },
1970 ));
1971 };
1972
1973 project_subscriptions.push(cx.subscribe_in(
1974 &project.read(cx).breakpoint_store(),
1975 window,
1976 |editor, _, event, window, cx| match event {
1977 BreakpointStoreEvent::ClearDebugLines => {
1978 editor.clear_row_highlights::<ActiveDebugLine>();
1979 editor.refresh_inline_values(cx);
1980 }
1981 BreakpointStoreEvent::SetDebugLine => {
1982 if editor.go_to_active_debug_line(window, cx) {
1983 cx.stop_propagation();
1984 }
1985
1986 editor.refresh_inline_values(cx);
1987 }
1988 _ => {}
1989 },
1990 ));
1991 let git_store = project.read(cx).git_store().clone();
1992 let project = project.clone();
1993 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1994 if let GitStoreEvent::RepositoryUpdated(
1995 _,
1996 RepositoryEvent::Updated {
1997 new_instance: true, ..
1998 },
1999 _,
2000 ) = event
2001 {
2002 this.load_diff_task = Some(
2003 update_uncommitted_diff_for_buffer(
2004 cx.entity(),
2005 &project,
2006 this.buffer.read(cx).all_buffers(),
2007 this.buffer.clone(),
2008 cx,
2009 )
2010 .shared(),
2011 );
2012 }
2013 }));
2014 }
2015
2016 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2017
2018 let inlay_hint_settings =
2019 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2020 let focus_handle = cx.focus_handle();
2021 if !is_minimap {
2022 cx.on_focus(&focus_handle, window, Self::handle_focus)
2023 .detach();
2024 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2025 .detach();
2026 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2027 .detach();
2028 cx.on_blur(&focus_handle, window, Self::handle_blur)
2029 .detach();
2030 cx.observe_pending_input(window, Self::observe_pending_input)
2031 .detach();
2032 }
2033
2034 let show_indent_guides =
2035 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2036 Some(false)
2037 } else {
2038 None
2039 };
2040
2041 let breakpoint_store = match (&mode, project.as_ref()) {
2042 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2043 _ => None,
2044 };
2045
2046 let mut code_action_providers = Vec::new();
2047 let mut load_uncommitted_diff = None;
2048 if let Some(project) = project.clone() {
2049 load_uncommitted_diff = Some(
2050 update_uncommitted_diff_for_buffer(
2051 cx.entity(),
2052 &project,
2053 buffer.read(cx).all_buffers(),
2054 buffer.clone(),
2055 cx,
2056 )
2057 .shared(),
2058 );
2059 code_action_providers.push(Rc::new(project) as Rc<_>);
2060 }
2061
2062 let mut editor = Self {
2063 focus_handle,
2064 show_cursor_when_unfocused: false,
2065 last_focused_descendant: None,
2066 buffer: buffer.clone(),
2067 display_map: display_map.clone(),
2068 selections,
2069 scroll_manager: ScrollManager::new(cx),
2070 columnar_selection_state: None,
2071 add_selections_state: None,
2072 select_next_state: None,
2073 select_prev_state: None,
2074 selection_history: SelectionHistory::default(),
2075 defer_selection_effects: false,
2076 deferred_selection_effects_state: None,
2077 autoclose_regions: Vec::new(),
2078 snippet_stack: InvalidationStack::default(),
2079 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2080 ime_transaction: None,
2081 active_diagnostics: ActiveDiagnostic::None,
2082 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2083 inline_diagnostics_update: Task::ready(()),
2084 inline_diagnostics: Vec::new(),
2085 soft_wrap_mode_override,
2086 diagnostics_max_severity,
2087 hard_wrap: None,
2088 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2089 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2090 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2091 project,
2092 blink_manager: blink_manager.clone(),
2093 show_local_selections: true,
2094 show_scrollbars: ScrollbarAxes {
2095 horizontal: full_mode,
2096 vertical: full_mode,
2097 },
2098 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2099 offset_content: !matches!(mode, EditorMode::SingleLine),
2100 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2101 show_gutter: full_mode,
2102 show_line_numbers: (!full_mode).then_some(false),
2103 use_relative_line_numbers: None,
2104 disable_expand_excerpt_buttons: !full_mode,
2105 show_git_diff_gutter: None,
2106 show_code_actions: None,
2107 show_runnables: None,
2108 show_breakpoints: None,
2109 show_wrap_guides: None,
2110 show_indent_guides,
2111 placeholder_text: None,
2112 highlight_order: 0,
2113 highlighted_rows: HashMap::default(),
2114 background_highlights: TreeMap::default(),
2115 gutter_highlights: TreeMap::default(),
2116 scrollbar_marker_state: ScrollbarMarkerState::default(),
2117 active_indent_guides_state: ActiveIndentGuidesState::default(),
2118 nav_history: None,
2119 context_menu: RefCell::new(None),
2120 context_menu_options: None,
2121 mouse_context_menu: None,
2122 completion_tasks: Vec::new(),
2123 inline_blame_popover: None,
2124 inline_blame_popover_show_task: None,
2125 signature_help_state: SignatureHelpState::default(),
2126 auto_signature_help: None,
2127 find_all_references_task_sources: Vec::new(),
2128 next_completion_id: 0,
2129 next_inlay_id: 0,
2130 code_action_providers,
2131 available_code_actions: None,
2132 code_actions_task: None,
2133 quick_selection_highlight_task: None,
2134 debounced_selection_highlight_task: None,
2135 document_highlights_task: None,
2136 linked_editing_range_task: None,
2137 pending_rename: None,
2138 searchable: !is_minimap,
2139 cursor_shape: EditorSettings::get_global(cx)
2140 .cursor_shape
2141 .unwrap_or_default(),
2142 current_line_highlight: None,
2143 autoindent_mode: Some(AutoindentMode::EachLine),
2144 collapse_matches: false,
2145 workspace: None,
2146 input_enabled: !is_minimap,
2147 use_modal_editing: full_mode,
2148 read_only: is_minimap,
2149 use_autoclose: true,
2150 use_auto_surround: true,
2151 auto_replace_emoji_shortcode: false,
2152 jsx_tag_auto_close_enabled_in_any_buffer: false,
2153 leader_id: None,
2154 remote_id: None,
2155 hover_state: HoverState::default(),
2156 pending_mouse_down: None,
2157 hovered_link_state: None,
2158 edit_prediction_provider: None,
2159 active_edit_prediction: None,
2160 stale_edit_prediction_in_menu: None,
2161 edit_prediction_preview: EditPredictionPreview::Inactive {
2162 released_too_fast: false,
2163 },
2164 inline_diagnostics_enabled: full_mode,
2165 diagnostics_enabled: full_mode,
2166 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2167 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2168 gutter_hovered: false,
2169 pixel_position_of_newest_cursor: None,
2170 last_bounds: None,
2171 last_position_map: None,
2172 expect_bounds_change: None,
2173 gutter_dimensions: GutterDimensions::default(),
2174 style: None,
2175 show_cursor_names: false,
2176 hovered_cursors: HashMap::default(),
2177 next_editor_action_id: EditorActionId::default(),
2178 editor_actions: Rc::default(),
2179 edit_predictions_hidden_for_vim_mode: false,
2180 show_edit_predictions_override: None,
2181 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2182 edit_prediction_settings: EditPredictionSettings::Disabled,
2183 edit_prediction_indent_conflict: false,
2184 edit_prediction_requires_modifier_in_indent_conflict: true,
2185 custom_context_menu: None,
2186 show_git_blame_gutter: false,
2187 show_git_blame_inline: false,
2188 show_selection_menu: None,
2189 show_git_blame_inline_delay_task: None,
2190 git_blame_inline_enabled: full_mode
2191 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2192 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2193 serialize_dirty_buffers: !is_minimap
2194 && ProjectSettings::get_global(cx)
2195 .session
2196 .restore_unsaved_buffers,
2197 blame: None,
2198 blame_subscription: None,
2199 tasks: BTreeMap::default(),
2200
2201 breakpoint_store,
2202 gutter_breakpoint_indicator: (None, None),
2203 hovered_diff_hunk_row: None,
2204 _subscriptions: (!is_minimap)
2205 .then(|| {
2206 vec![
2207 cx.observe(&buffer, Self::on_buffer_changed),
2208 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2209 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2210 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2211 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2212 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2213 cx.observe_window_activation(window, |editor, window, cx| {
2214 let active = window.is_window_active();
2215 editor.blink_manager.update(cx, |blink_manager, cx| {
2216 if active {
2217 blink_manager.enable(cx);
2218 } else {
2219 blink_manager.disable(cx);
2220 }
2221 });
2222 if active {
2223 editor.show_mouse_cursor(cx);
2224 }
2225 }),
2226 ]
2227 })
2228 .unwrap_or_default(),
2229 tasks_update_task: None,
2230 pull_diagnostics_task: Task::ready(()),
2231 colors: None,
2232 next_color_inlay_id: 0,
2233 linked_edit_ranges: Default::default(),
2234 in_project_search: false,
2235 previous_search_ranges: None,
2236 breadcrumb_header: None,
2237 focused_block: None,
2238 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2239 addons: HashMap::default(),
2240 registered_buffers: HashMap::default(),
2241 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2242 selection_mark_mode: false,
2243 toggle_fold_multiple_buffers: Task::ready(()),
2244 serialize_selections: Task::ready(()),
2245 serialize_folds: Task::ready(()),
2246 text_style_refinement: None,
2247 load_diff_task: load_uncommitted_diff,
2248 temporary_diff_override: false,
2249 mouse_cursor_hidden: false,
2250 minimap: None,
2251 hide_mouse_mode: EditorSettings::get_global(cx)
2252 .hide_mouse
2253 .unwrap_or_default(),
2254 change_list: ChangeList::new(),
2255 mode,
2256 selection_drag_state: SelectionDragState::None,
2257 folding_newlines: Task::ready(()),
2258 };
2259
2260 if is_minimap {
2261 return editor;
2262 }
2263
2264 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2265 editor
2266 ._subscriptions
2267 .push(cx.observe(breakpoints, |_, _, cx| {
2268 cx.notify();
2269 }));
2270 }
2271 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2272 editor._subscriptions.extend(project_subscriptions);
2273
2274 editor._subscriptions.push(cx.subscribe_in(
2275 &cx.entity(),
2276 window,
2277 |editor, _, e: &EditorEvent, window, cx| match e {
2278 EditorEvent::ScrollPositionChanged { local, .. } => {
2279 if *local {
2280 let new_anchor = editor.scroll_manager.anchor();
2281 let snapshot = editor.snapshot(window, cx);
2282 editor.update_restoration_data(cx, move |data| {
2283 data.scroll_position = (
2284 new_anchor.top_row(&snapshot.buffer_snapshot),
2285 new_anchor.offset,
2286 );
2287 });
2288 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2289 editor.inline_blame_popover.take();
2290 }
2291 }
2292 EditorEvent::Edited { .. } => {
2293 if !vim_enabled(cx) {
2294 let (map, selections) = editor.selections.all_adjusted_display(cx);
2295 let pop_state = editor
2296 .change_list
2297 .last()
2298 .map(|previous| {
2299 previous.len() == selections.len()
2300 && previous.iter().enumerate().all(|(ix, p)| {
2301 p.to_display_point(&map).row()
2302 == selections[ix].head().row()
2303 })
2304 })
2305 .unwrap_or(false);
2306 let new_positions = selections
2307 .into_iter()
2308 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2309 .collect();
2310 editor
2311 .change_list
2312 .push_to_change_list(pop_state, new_positions);
2313 }
2314 }
2315 _ => (),
2316 },
2317 ));
2318
2319 if let Some(dap_store) = editor
2320 .project
2321 .as_ref()
2322 .map(|project| project.read(cx).dap_store())
2323 {
2324 let weak_editor = cx.weak_entity();
2325
2326 editor
2327 ._subscriptions
2328 .push(
2329 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2330 let session_entity = cx.entity();
2331 weak_editor
2332 .update(cx, |editor, cx| {
2333 editor._subscriptions.push(
2334 cx.subscribe(&session_entity, Self::on_debug_session_event),
2335 );
2336 })
2337 .ok();
2338 }),
2339 );
2340
2341 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2342 editor
2343 ._subscriptions
2344 .push(cx.subscribe(&session, Self::on_debug_session_event));
2345 }
2346 }
2347
2348 // skip adding the initial selection to selection history
2349 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2350 editor.end_selection(window, cx);
2351 editor.selection_history.mode = SelectionHistoryMode::Normal;
2352
2353 editor.scroll_manager.show_scrollbars(window, cx);
2354 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2355
2356 if full_mode {
2357 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2358 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2359
2360 if editor.git_blame_inline_enabled {
2361 editor.start_git_blame_inline(false, window, cx);
2362 }
2363
2364 editor.go_to_active_debug_line(window, cx);
2365
2366 if let Some(buffer) = buffer.read(cx).as_singleton()
2367 && let Some(project) = editor.project()
2368 {
2369 let handle = project.update(cx, |project, cx| {
2370 project.register_buffer_with_language_servers(&buffer, cx)
2371 });
2372 editor
2373 .registered_buffers
2374 .insert(buffer.read(cx).remote_id(), handle);
2375 }
2376
2377 editor.minimap =
2378 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2379 editor.colors = Some(LspColorData::new(cx));
2380 editor.update_lsp_data(false, None, window, cx);
2381 }
2382
2383 if editor.mode.is_full() {
2384 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2385 }
2386
2387 editor
2388 }
2389
2390 pub fn deploy_mouse_context_menu(
2391 &mut self,
2392 position: gpui::Point<Pixels>,
2393 context_menu: Entity<ContextMenu>,
2394 window: &mut Window,
2395 cx: &mut Context<Self>,
2396 ) {
2397 self.mouse_context_menu = Some(MouseContextMenu::new(
2398 self,
2399 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2400 context_menu,
2401 window,
2402 cx,
2403 ));
2404 }
2405
2406 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2407 self.mouse_context_menu
2408 .as_ref()
2409 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2410 }
2411
2412 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2413 if self
2414 .selections
2415 .pending
2416 .as_ref()
2417 .is_some_and(|pending_selection| {
2418 let snapshot = self.buffer().read(cx).snapshot(cx);
2419 pending_selection
2420 .selection
2421 .range()
2422 .includes(range, &snapshot)
2423 })
2424 {
2425 return true;
2426 }
2427
2428 self.selections
2429 .disjoint_in_range::<usize>(range.clone(), cx)
2430 .into_iter()
2431 .any(|selection| {
2432 // This is needed to cover a corner case, if we just check for an existing
2433 // selection in the fold range, having a cursor at the start of the fold
2434 // marks it as selected. Non-empty selections don't cause this.
2435 let length = selection.end - selection.start;
2436 length > 0
2437 })
2438 }
2439
2440 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2441 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2442 }
2443
2444 fn key_context_internal(
2445 &self,
2446 has_active_edit_prediction: bool,
2447 window: &Window,
2448 cx: &App,
2449 ) -> KeyContext {
2450 let mut key_context = KeyContext::new_with_defaults();
2451 key_context.add("Editor");
2452 let mode = match self.mode {
2453 EditorMode::SingleLine => "single_line",
2454 EditorMode::AutoHeight { .. } => "auto_height",
2455 EditorMode::Minimap { .. } => "minimap",
2456 EditorMode::Full { .. } => "full",
2457 };
2458
2459 if EditorSettings::jupyter_enabled(cx) {
2460 key_context.add("jupyter");
2461 }
2462
2463 key_context.set("mode", mode);
2464 if self.pending_rename.is_some() {
2465 key_context.add("renaming");
2466 }
2467
2468 match self.context_menu.borrow().as_ref() {
2469 Some(CodeContextMenu::Completions(menu)) => {
2470 if menu.visible() {
2471 key_context.add("menu");
2472 key_context.add("showing_completions");
2473 }
2474 }
2475 Some(CodeContextMenu::CodeActions(menu)) => {
2476 if menu.visible() {
2477 key_context.add("menu");
2478 key_context.add("showing_code_actions")
2479 }
2480 }
2481 None => {}
2482 }
2483
2484 if self.signature_help_state.has_multiple_signatures() {
2485 key_context.add("showing_signature_help");
2486 }
2487
2488 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2489 if !self.focus_handle(cx).contains_focused(window, cx)
2490 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2491 {
2492 for addon in self.addons.values() {
2493 addon.extend_key_context(&mut key_context, cx)
2494 }
2495 }
2496
2497 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2498 if let Some(extension) = singleton_buffer
2499 .read(cx)
2500 .file()
2501 .and_then(|file| file.path().extension()?.to_str())
2502 {
2503 key_context.set("extension", extension.to_string());
2504 }
2505 } else {
2506 key_context.add("multibuffer");
2507 }
2508
2509 if has_active_edit_prediction {
2510 if self.edit_prediction_in_conflict() {
2511 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2512 } else {
2513 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2514 key_context.add("copilot_suggestion");
2515 }
2516 }
2517
2518 if self.selection_mark_mode {
2519 key_context.add("selection_mode");
2520 }
2521
2522 key_context
2523 }
2524
2525 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2526 if self.mouse_cursor_hidden {
2527 self.mouse_cursor_hidden = false;
2528 cx.notify();
2529 }
2530 }
2531
2532 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2533 let hide_mouse_cursor = match origin {
2534 HideMouseCursorOrigin::TypingAction => {
2535 matches!(
2536 self.hide_mouse_mode,
2537 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2538 )
2539 }
2540 HideMouseCursorOrigin::MovementAction => {
2541 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2542 }
2543 };
2544 if self.mouse_cursor_hidden != hide_mouse_cursor {
2545 self.mouse_cursor_hidden = hide_mouse_cursor;
2546 cx.notify();
2547 }
2548 }
2549
2550 pub fn edit_prediction_in_conflict(&self) -> bool {
2551 if !self.show_edit_predictions_in_menu() {
2552 return false;
2553 }
2554
2555 let showing_completions = self
2556 .context_menu
2557 .borrow()
2558 .as_ref()
2559 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2560
2561 showing_completions
2562 || self.edit_prediction_requires_modifier()
2563 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2564 // bindings to insert tab characters.
2565 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2566 }
2567
2568 pub fn accept_edit_prediction_keybind(
2569 &self,
2570 accept_partial: bool,
2571 window: &Window,
2572 cx: &App,
2573 ) -> AcceptEditPredictionBinding {
2574 let key_context = self.key_context_internal(true, window, cx);
2575 let in_conflict = self.edit_prediction_in_conflict();
2576
2577 let bindings = if accept_partial {
2578 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2579 } else {
2580 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2581 };
2582
2583 // TODO: if the binding contains multiple keystrokes, display all of them, not
2584 // just the first one.
2585 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2586 !in_conflict
2587 || binding
2588 .keystrokes()
2589 .first()
2590 .is_some_and(|keystroke| keystroke.modifiers().modified())
2591 }))
2592 }
2593
2594 pub fn new_file(
2595 workspace: &mut Workspace,
2596 _: &workspace::NewFile,
2597 window: &mut Window,
2598 cx: &mut Context<Workspace>,
2599 ) {
2600 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2601 "Failed to create buffer",
2602 window,
2603 cx,
2604 |e, _, _| match e.error_code() {
2605 ErrorCode::RemoteUpgradeRequired => Some(format!(
2606 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2607 e.error_tag("required").unwrap_or("the latest version")
2608 )),
2609 _ => None,
2610 },
2611 );
2612 }
2613
2614 pub fn new_in_workspace(
2615 workspace: &mut Workspace,
2616 window: &mut Window,
2617 cx: &mut Context<Workspace>,
2618 ) -> Task<Result<Entity<Editor>>> {
2619 let project = workspace.project().clone();
2620 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2621
2622 cx.spawn_in(window, async move |workspace, cx| {
2623 let buffer = create.await?;
2624 workspace.update_in(cx, |workspace, window, cx| {
2625 let editor =
2626 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2627 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2628 editor
2629 })
2630 })
2631 }
2632
2633 fn new_file_vertical(
2634 workspace: &mut Workspace,
2635 _: &workspace::NewFileSplitVertical,
2636 window: &mut Window,
2637 cx: &mut Context<Workspace>,
2638 ) {
2639 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2640 }
2641
2642 fn new_file_horizontal(
2643 workspace: &mut Workspace,
2644 _: &workspace::NewFileSplitHorizontal,
2645 window: &mut Window,
2646 cx: &mut Context<Workspace>,
2647 ) {
2648 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2649 }
2650
2651 fn new_file_in_direction(
2652 workspace: &mut Workspace,
2653 direction: SplitDirection,
2654 window: &mut Window,
2655 cx: &mut Context<Workspace>,
2656 ) {
2657 let project = workspace.project().clone();
2658 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2659
2660 cx.spawn_in(window, async move |workspace, cx| {
2661 let buffer = create.await?;
2662 workspace.update_in(cx, move |workspace, window, cx| {
2663 workspace.split_item(
2664 direction,
2665 Box::new(
2666 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2667 ),
2668 window,
2669 cx,
2670 )
2671 })?;
2672 anyhow::Ok(())
2673 })
2674 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2675 match e.error_code() {
2676 ErrorCode::RemoteUpgradeRequired => Some(format!(
2677 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2678 e.error_tag("required").unwrap_or("the latest version")
2679 )),
2680 _ => None,
2681 }
2682 });
2683 }
2684
2685 pub fn leader_id(&self) -> Option<CollaboratorId> {
2686 self.leader_id
2687 }
2688
2689 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2690 &self.buffer
2691 }
2692
2693 pub fn project(&self) -> Option<&Entity<Project>> {
2694 self.project.as_ref()
2695 }
2696
2697 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2698 self.workspace.as_ref()?.0.upgrade()
2699 }
2700
2701 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2702 self.buffer().read(cx).title(cx)
2703 }
2704
2705 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2706 let git_blame_gutter_max_author_length = self
2707 .render_git_blame_gutter(cx)
2708 .then(|| {
2709 if let Some(blame) = self.blame.as_ref() {
2710 let max_author_length =
2711 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2712 Some(max_author_length)
2713 } else {
2714 None
2715 }
2716 })
2717 .flatten();
2718
2719 EditorSnapshot {
2720 mode: self.mode.clone(),
2721 show_gutter: self.show_gutter,
2722 show_line_numbers: self.show_line_numbers,
2723 show_git_diff_gutter: self.show_git_diff_gutter,
2724 show_code_actions: self.show_code_actions,
2725 show_runnables: self.show_runnables,
2726 show_breakpoints: self.show_breakpoints,
2727 git_blame_gutter_max_author_length,
2728 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2729 scroll_anchor: self.scroll_manager.anchor(),
2730 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2731 placeholder_text: self.placeholder_text.clone(),
2732 is_focused: self.focus_handle.is_focused(window),
2733 current_line_highlight: self
2734 .current_line_highlight
2735 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2736 gutter_hovered: self.gutter_hovered,
2737 }
2738 }
2739
2740 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2741 self.buffer.read(cx).language_at(point, cx)
2742 }
2743
2744 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2745 self.buffer.read(cx).read(cx).file_at(point).cloned()
2746 }
2747
2748 pub fn active_excerpt(
2749 &self,
2750 cx: &App,
2751 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2752 self.buffer
2753 .read(cx)
2754 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2755 }
2756
2757 pub fn mode(&self) -> &EditorMode {
2758 &self.mode
2759 }
2760
2761 pub fn set_mode(&mut self, mode: EditorMode) {
2762 self.mode = mode;
2763 }
2764
2765 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2766 self.collaboration_hub.as_deref()
2767 }
2768
2769 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2770 self.collaboration_hub = Some(hub);
2771 }
2772
2773 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2774 self.in_project_search = in_project_search;
2775 }
2776
2777 pub fn set_custom_context_menu(
2778 &mut self,
2779 f: impl 'static
2780 + Fn(
2781 &mut Self,
2782 DisplayPoint,
2783 &mut Window,
2784 &mut Context<Self>,
2785 ) -> Option<Entity<ui::ContextMenu>>,
2786 ) {
2787 self.custom_context_menu = Some(Box::new(f))
2788 }
2789
2790 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2791 self.completion_provider = provider;
2792 }
2793
2794 #[cfg(any(test, feature = "test-support"))]
2795 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2796 self.completion_provider.clone()
2797 }
2798
2799 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2800 self.semantics_provider.clone()
2801 }
2802
2803 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2804 self.semantics_provider = provider;
2805 }
2806
2807 pub fn set_edit_prediction_provider<T>(
2808 &mut self,
2809 provider: Option<Entity<T>>,
2810 window: &mut Window,
2811 cx: &mut Context<Self>,
2812 ) where
2813 T: EditPredictionProvider,
2814 {
2815 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2816 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2817 if this.focus_handle.is_focused(window) {
2818 this.update_visible_edit_prediction(window, cx);
2819 }
2820 }),
2821 provider: Arc::new(provider),
2822 });
2823 self.update_edit_prediction_settings(cx);
2824 self.refresh_edit_prediction(false, false, window, cx);
2825 }
2826
2827 pub fn placeholder_text(&self) -> Option<&str> {
2828 self.placeholder_text.as_deref()
2829 }
2830
2831 pub fn set_placeholder_text(
2832 &mut self,
2833 placeholder_text: impl Into<Arc<str>>,
2834 cx: &mut Context<Self>,
2835 ) {
2836 let placeholder_text = Some(placeholder_text.into());
2837 if self.placeholder_text != placeholder_text {
2838 self.placeholder_text = placeholder_text;
2839 cx.notify();
2840 }
2841 }
2842
2843 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2844 self.cursor_shape = cursor_shape;
2845
2846 // Disrupt blink for immediate user feedback that the cursor shape has changed
2847 self.blink_manager.update(cx, BlinkManager::show_cursor);
2848
2849 cx.notify();
2850 }
2851
2852 pub fn set_current_line_highlight(
2853 &mut self,
2854 current_line_highlight: Option<CurrentLineHighlight>,
2855 ) {
2856 self.current_line_highlight = current_line_highlight;
2857 }
2858
2859 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2860 self.collapse_matches = collapse_matches;
2861 }
2862
2863 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2864 let buffers = self.buffer.read(cx).all_buffers();
2865 let Some(project) = self.project.as_ref() else {
2866 return;
2867 };
2868 project.update(cx, |project, cx| {
2869 for buffer in buffers {
2870 self.registered_buffers
2871 .entry(buffer.read(cx).remote_id())
2872 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2873 }
2874 })
2875 }
2876
2877 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2878 if self.collapse_matches {
2879 return range.start..range.start;
2880 }
2881 range.clone()
2882 }
2883
2884 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2885 if self.display_map.read(cx).clip_at_line_ends != clip {
2886 self.display_map
2887 .update(cx, |map, _| map.clip_at_line_ends = clip);
2888 }
2889 }
2890
2891 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2892 self.input_enabled = input_enabled;
2893 }
2894
2895 pub fn set_edit_predictions_hidden_for_vim_mode(
2896 &mut self,
2897 hidden: bool,
2898 window: &mut Window,
2899 cx: &mut Context<Self>,
2900 ) {
2901 if hidden != self.edit_predictions_hidden_for_vim_mode {
2902 self.edit_predictions_hidden_for_vim_mode = hidden;
2903 if hidden {
2904 self.update_visible_edit_prediction(window, cx);
2905 } else {
2906 self.refresh_edit_prediction(true, false, window, cx);
2907 }
2908 }
2909 }
2910
2911 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2912 self.menu_edit_predictions_policy = value;
2913 }
2914
2915 pub fn set_autoindent(&mut self, autoindent: bool) {
2916 if autoindent {
2917 self.autoindent_mode = Some(AutoindentMode::EachLine);
2918 } else {
2919 self.autoindent_mode = None;
2920 }
2921 }
2922
2923 pub fn read_only(&self, cx: &App) -> bool {
2924 self.read_only || self.buffer.read(cx).read_only()
2925 }
2926
2927 pub fn set_read_only(&mut self, read_only: bool) {
2928 self.read_only = read_only;
2929 }
2930
2931 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2932 self.use_autoclose = autoclose;
2933 }
2934
2935 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2936 self.use_auto_surround = auto_surround;
2937 }
2938
2939 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2940 self.auto_replace_emoji_shortcode = auto_replace;
2941 }
2942
2943 pub fn toggle_edit_predictions(
2944 &mut self,
2945 _: &ToggleEditPrediction,
2946 window: &mut Window,
2947 cx: &mut Context<Self>,
2948 ) {
2949 if self.show_edit_predictions_override.is_some() {
2950 self.set_show_edit_predictions(None, window, cx);
2951 } else {
2952 let show_edit_predictions = !self.edit_predictions_enabled();
2953 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2954 }
2955 }
2956
2957 pub fn set_show_edit_predictions(
2958 &mut self,
2959 show_edit_predictions: Option<bool>,
2960 window: &mut Window,
2961 cx: &mut Context<Self>,
2962 ) {
2963 self.show_edit_predictions_override = show_edit_predictions;
2964 self.update_edit_prediction_settings(cx);
2965
2966 if let Some(false) = show_edit_predictions {
2967 self.discard_edit_prediction(false, cx);
2968 } else {
2969 self.refresh_edit_prediction(false, true, window, cx);
2970 }
2971 }
2972
2973 fn edit_predictions_disabled_in_scope(
2974 &self,
2975 buffer: &Entity<Buffer>,
2976 buffer_position: language::Anchor,
2977 cx: &App,
2978 ) -> bool {
2979 let snapshot = buffer.read(cx).snapshot();
2980 let settings = snapshot.settings_at(buffer_position, cx);
2981
2982 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2983 return false;
2984 };
2985
2986 scope.override_name().is_some_and(|scope_name| {
2987 settings
2988 .edit_predictions_disabled_in
2989 .iter()
2990 .any(|s| s == scope_name)
2991 })
2992 }
2993
2994 pub fn set_use_modal_editing(&mut self, to: bool) {
2995 self.use_modal_editing = to;
2996 }
2997
2998 pub fn use_modal_editing(&self) -> bool {
2999 self.use_modal_editing
3000 }
3001
3002 fn selections_did_change(
3003 &mut self,
3004 local: bool,
3005 old_cursor_position: &Anchor,
3006 effects: SelectionEffects,
3007 window: &mut Window,
3008 cx: &mut Context<Self>,
3009 ) {
3010 window.invalidate_character_coordinates();
3011
3012 // Copy selections to primary selection buffer
3013 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3014 if local {
3015 let selections = self.selections.all::<usize>(cx);
3016 let buffer_handle = self.buffer.read(cx).read(cx);
3017
3018 let mut text = String::new();
3019 for (index, selection) in selections.iter().enumerate() {
3020 let text_for_selection = buffer_handle
3021 .text_for_range(selection.start..selection.end)
3022 .collect::<String>();
3023
3024 text.push_str(&text_for_selection);
3025 if index != selections.len() - 1 {
3026 text.push('\n');
3027 }
3028 }
3029
3030 if !text.is_empty() {
3031 cx.write_to_primary(ClipboardItem::new_string(text));
3032 }
3033 }
3034
3035 let selection_anchors = self.selections.disjoint_anchors();
3036
3037 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3038 self.buffer.update(cx, |buffer, cx| {
3039 buffer.set_active_selections(
3040 &selection_anchors,
3041 self.selections.line_mode,
3042 self.cursor_shape,
3043 cx,
3044 )
3045 });
3046 }
3047 let display_map = self
3048 .display_map
3049 .update(cx, |display_map, cx| display_map.snapshot(cx));
3050 let buffer = &display_map.buffer_snapshot;
3051 if self.selections.count() == 1 {
3052 self.add_selections_state = None;
3053 }
3054 self.select_next_state = None;
3055 self.select_prev_state = None;
3056 self.select_syntax_node_history.try_clear();
3057 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3058 self.snippet_stack.invalidate(&selection_anchors, buffer);
3059 self.take_rename(false, window, cx);
3060
3061 let newest_selection = self.selections.newest_anchor();
3062 let new_cursor_position = newest_selection.head();
3063 let selection_start = newest_selection.start;
3064
3065 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3066 self.push_to_nav_history(
3067 *old_cursor_position,
3068 Some(new_cursor_position.to_point(buffer)),
3069 false,
3070 effects.nav_history == Some(true),
3071 cx,
3072 );
3073 }
3074
3075 if local {
3076 if let Some(buffer_id) = new_cursor_position.buffer_id
3077 && !self.registered_buffers.contains_key(&buffer_id)
3078 && let Some(project) = self.project.as_ref()
3079 {
3080 project.update(cx, |project, cx| {
3081 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3082 return;
3083 };
3084 self.registered_buffers.insert(
3085 buffer_id,
3086 project.register_buffer_with_language_servers(&buffer, cx),
3087 );
3088 })
3089 }
3090
3091 let mut context_menu = self.context_menu.borrow_mut();
3092 let completion_menu = match context_menu.as_ref() {
3093 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3094 Some(CodeContextMenu::CodeActions(_)) => {
3095 *context_menu = None;
3096 None
3097 }
3098 None => None,
3099 };
3100 let completion_position = completion_menu.map(|menu| menu.initial_position);
3101 drop(context_menu);
3102
3103 if effects.completions
3104 && let Some(completion_position) = completion_position
3105 {
3106 let start_offset = selection_start.to_offset(buffer);
3107 let position_matches = start_offset == completion_position.to_offset(buffer);
3108 let continue_showing = if position_matches {
3109 if self.snippet_stack.is_empty() {
3110 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3111 } else {
3112 // Snippet choices can be shown even when the cursor is in whitespace.
3113 // Dismissing the menu with actions like backspace is handled by
3114 // invalidation regions.
3115 true
3116 }
3117 } else {
3118 false
3119 };
3120
3121 if continue_showing {
3122 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3123 } else {
3124 self.hide_context_menu(window, cx);
3125 }
3126 }
3127
3128 hide_hover(self, cx);
3129
3130 if old_cursor_position.to_display_point(&display_map).row()
3131 != new_cursor_position.to_display_point(&display_map).row()
3132 {
3133 self.available_code_actions.take();
3134 }
3135 self.refresh_code_actions(window, cx);
3136 self.refresh_document_highlights(cx);
3137 self.refresh_selected_text_highlights(false, window, cx);
3138 refresh_matching_bracket_highlights(self, window, cx);
3139 self.update_visible_edit_prediction(window, cx);
3140 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3141 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
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;
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(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3190 .log_err();
3191 });
3192 }
3193 }
3194
3195 cx.notify();
3196 }
3197
3198 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3199 use text::ToOffset as _;
3200 use text::ToPoint as _;
3201
3202 if self.mode.is_minimap()
3203 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3204 {
3205 return;
3206 }
3207
3208 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3209 return;
3210 };
3211
3212 let snapshot = singleton.read(cx).snapshot();
3213 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3214 let display_snapshot = display_map.snapshot(cx);
3215
3216 display_snapshot
3217 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3218 .map(|fold| {
3219 fold.range.start.text_anchor.to_point(&snapshot)
3220 ..fold.range.end.text_anchor.to_point(&snapshot)
3221 })
3222 .collect()
3223 });
3224 self.update_restoration_data(cx, |data| {
3225 data.folds = inmemory_folds;
3226 });
3227
3228 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3229 return;
3230 };
3231 let background_executor = cx.background_executor().clone();
3232 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3233 let db_folds = self.display_map.update(cx, |display_map, cx| {
3234 display_map
3235 .snapshot(cx)
3236 .folds_in_range(0..snapshot.len())
3237 .map(|fold| {
3238 (
3239 fold.range.start.text_anchor.to_offset(&snapshot),
3240 fold.range.end.text_anchor.to_offset(&snapshot),
3241 )
3242 })
3243 .collect()
3244 });
3245 self.serialize_folds = cx.background_spawn(async move {
3246 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3247 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3248 .await
3249 .with_context(|| {
3250 format!(
3251 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3252 )
3253 })
3254 .log_err();
3255 });
3256 }
3257
3258 pub fn sync_selections(
3259 &mut self,
3260 other: Entity<Editor>,
3261 cx: &mut Context<Self>,
3262 ) -> gpui::Subscription {
3263 let other_selections = other.read(cx).selections.disjoint.to_vec();
3264 self.selections.change_with(cx, |selections| {
3265 selections.select_anchors(other_selections);
3266 });
3267
3268 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3269 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3270 let other_selections = other.read(cx).selections.disjoint.to_vec();
3271 if other_selections.is_empty() {
3272 return;
3273 }
3274 this.selections.change_with(cx, |selections| {
3275 selections.select_anchors(other_selections);
3276 });
3277 }
3278 });
3279
3280 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3281 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3282 let these_selections = this.selections.disjoint.to_vec();
3283 if these_selections.is_empty() {
3284 return;
3285 }
3286 other.update(cx, |other_editor, cx| {
3287 other_editor.selections.change_with(cx, |selections| {
3288 selections.select_anchors(these_selections);
3289 })
3290 });
3291 }
3292 });
3293
3294 Subscription::join(other_subscription, this_subscription)
3295 }
3296
3297 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3298 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3299 /// effects of selection change occur at the end of the transaction.
3300 pub fn change_selections<R>(
3301 &mut self,
3302 effects: SelectionEffects,
3303 window: &mut Window,
3304 cx: &mut Context<Self>,
3305 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3306 ) -> R {
3307 if let Some(state) = &mut self.deferred_selection_effects_state {
3308 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3309 state.effects.completions = effects.completions;
3310 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3311 let (changed, result) = self.selections.change_with(cx, change);
3312 state.changed |= changed;
3313 return result;
3314 }
3315 let mut state = DeferredSelectionEffectsState {
3316 changed: false,
3317 effects,
3318 old_cursor_position: self.selections.newest_anchor().head(),
3319 history_entry: SelectionHistoryEntry {
3320 selections: self.selections.disjoint_anchors(),
3321 select_next_state: self.select_next_state.clone(),
3322 select_prev_state: self.select_prev_state.clone(),
3323 add_selections_state: self.add_selections_state.clone(),
3324 },
3325 };
3326 let (changed, result) = self.selections.change_with(cx, change);
3327 state.changed = state.changed || changed;
3328 if self.defer_selection_effects {
3329 self.deferred_selection_effects_state = Some(state);
3330 } else {
3331 self.apply_selection_effects(state, window, cx);
3332 }
3333 result
3334 }
3335
3336 /// Defers the effects of selection change, so that the effects of multiple calls to
3337 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3338 /// to selection history and the state of popovers based on selection position aren't
3339 /// erroneously updated.
3340 pub fn with_selection_effects_deferred<R>(
3341 &mut self,
3342 window: &mut Window,
3343 cx: &mut Context<Self>,
3344 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3345 ) -> R {
3346 let already_deferred = self.defer_selection_effects;
3347 self.defer_selection_effects = true;
3348 let result = update(self, window, cx);
3349 if !already_deferred {
3350 self.defer_selection_effects = false;
3351 if let Some(state) = self.deferred_selection_effects_state.take() {
3352 self.apply_selection_effects(state, window, cx);
3353 }
3354 }
3355 result
3356 }
3357
3358 fn apply_selection_effects(
3359 &mut self,
3360 state: DeferredSelectionEffectsState,
3361 window: &mut Window,
3362 cx: &mut Context<Self>,
3363 ) {
3364 if state.changed {
3365 self.selection_history.push(state.history_entry);
3366
3367 if let Some(autoscroll) = state.effects.scroll {
3368 self.request_autoscroll(autoscroll, cx);
3369 }
3370
3371 let old_cursor_position = &state.old_cursor_position;
3372
3373 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3374
3375 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3376 self.show_signature_help(&ShowSignatureHelp, window, cx);
3377 }
3378 }
3379 }
3380
3381 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3382 where
3383 I: IntoIterator<Item = (Range<S>, T)>,
3384 S: ToOffset,
3385 T: Into<Arc<str>>,
3386 {
3387 if self.read_only(cx) {
3388 return;
3389 }
3390
3391 self.buffer
3392 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3393 }
3394
3395 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3396 where
3397 I: IntoIterator<Item = (Range<S>, T)>,
3398 S: ToOffset,
3399 T: Into<Arc<str>>,
3400 {
3401 if self.read_only(cx) {
3402 return;
3403 }
3404
3405 self.buffer.update(cx, |buffer, cx| {
3406 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3407 });
3408 }
3409
3410 pub fn edit_with_block_indent<I, S, T>(
3411 &mut self,
3412 edits: I,
3413 original_indent_columns: Vec<Option<u32>>,
3414 cx: &mut Context<Self>,
3415 ) where
3416 I: IntoIterator<Item = (Range<S>, T)>,
3417 S: ToOffset,
3418 T: Into<Arc<str>>,
3419 {
3420 if self.read_only(cx) {
3421 return;
3422 }
3423
3424 self.buffer.update(cx, |buffer, cx| {
3425 buffer.edit(
3426 edits,
3427 Some(AutoindentMode::Block {
3428 original_indent_columns,
3429 }),
3430 cx,
3431 )
3432 });
3433 }
3434
3435 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3436 self.hide_context_menu(window, cx);
3437
3438 match phase {
3439 SelectPhase::Begin {
3440 position,
3441 add,
3442 click_count,
3443 } => self.begin_selection(position, add, click_count, window, cx),
3444 SelectPhase::BeginColumnar {
3445 position,
3446 goal_column,
3447 reset,
3448 mode,
3449 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3450 SelectPhase::Extend {
3451 position,
3452 click_count,
3453 } => self.extend_selection(position, click_count, window, cx),
3454 SelectPhase::Update {
3455 position,
3456 goal_column,
3457 scroll_delta,
3458 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3459 SelectPhase::End => self.end_selection(window, cx),
3460 }
3461 }
3462
3463 fn extend_selection(
3464 &mut self,
3465 position: DisplayPoint,
3466 click_count: usize,
3467 window: &mut Window,
3468 cx: &mut Context<Self>,
3469 ) {
3470 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3471 let tail = self.selections.newest::<usize>(cx).tail();
3472 self.begin_selection(position, false, click_count, window, cx);
3473
3474 let position = position.to_offset(&display_map, Bias::Left);
3475 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3476
3477 let mut pending_selection = self
3478 .selections
3479 .pending_anchor()
3480 .expect("extend_selection not called with pending selection");
3481 if position >= tail {
3482 pending_selection.start = tail_anchor;
3483 } else {
3484 pending_selection.end = tail_anchor;
3485 pending_selection.reversed = true;
3486 }
3487
3488 let mut pending_mode = self.selections.pending_mode().unwrap();
3489 match &mut pending_mode {
3490 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3491 _ => {}
3492 }
3493
3494 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3495 SelectionEffects::scroll(Autoscroll::fit())
3496 } else {
3497 SelectionEffects::no_scroll()
3498 };
3499
3500 self.change_selections(effects, window, cx, |s| {
3501 s.set_pending(pending_selection, pending_mode)
3502 });
3503 }
3504
3505 fn begin_selection(
3506 &mut self,
3507 position: DisplayPoint,
3508 add: bool,
3509 click_count: usize,
3510 window: &mut Window,
3511 cx: &mut Context<Self>,
3512 ) {
3513 if !self.focus_handle.is_focused(window) {
3514 self.last_focused_descendant = None;
3515 window.focus(&self.focus_handle);
3516 }
3517
3518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3519 let buffer = &display_map.buffer_snapshot;
3520 let position = display_map.clip_point(position, Bias::Left);
3521
3522 let start;
3523 let end;
3524 let mode;
3525 let mut auto_scroll;
3526 match click_count {
3527 1 => {
3528 start = buffer.anchor_before(position.to_point(&display_map));
3529 end = start;
3530 mode = SelectMode::Character;
3531 auto_scroll = true;
3532 }
3533 2 => {
3534 let position = display_map
3535 .clip_point(position, Bias::Left)
3536 .to_offset(&display_map, Bias::Left);
3537 let (range, _) = buffer.surrounding_word(position, false);
3538 start = buffer.anchor_before(range.start);
3539 end = buffer.anchor_before(range.end);
3540 mode = SelectMode::Word(start..end);
3541 auto_scroll = true;
3542 }
3543 3 => {
3544 let position = display_map
3545 .clip_point(position, Bias::Left)
3546 .to_point(&display_map);
3547 let line_start = display_map.prev_line_boundary(position).0;
3548 let next_line_start = buffer.clip_point(
3549 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3550 Bias::Left,
3551 );
3552 start = buffer.anchor_before(line_start);
3553 end = buffer.anchor_before(next_line_start);
3554 mode = SelectMode::Line(start..end);
3555 auto_scroll = true;
3556 }
3557 _ => {
3558 start = buffer.anchor_before(0);
3559 end = buffer.anchor_before(buffer.len());
3560 mode = SelectMode::All;
3561 auto_scroll = false;
3562 }
3563 }
3564 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3565
3566 let point_to_delete: Option<usize> = {
3567 let selected_points: Vec<Selection<Point>> =
3568 self.selections.disjoint_in_range(start..end, cx);
3569
3570 if !add || click_count > 1 {
3571 None
3572 } else if !selected_points.is_empty() {
3573 Some(selected_points[0].id)
3574 } else {
3575 let clicked_point_already_selected =
3576 self.selections.disjoint.iter().find(|selection| {
3577 selection.start.to_point(buffer) == start.to_point(buffer)
3578 || selection.end.to_point(buffer) == end.to_point(buffer)
3579 });
3580
3581 clicked_point_already_selected.map(|selection| selection.id)
3582 }
3583 };
3584
3585 let selections_count = self.selections.count();
3586 let effects = if auto_scroll {
3587 SelectionEffects::default()
3588 } else {
3589 SelectionEffects::no_scroll()
3590 };
3591
3592 self.change_selections(effects, window, cx, |s| {
3593 if let Some(point_to_delete) = point_to_delete {
3594 s.delete(point_to_delete);
3595
3596 if selections_count == 1 {
3597 s.set_pending_anchor_range(start..end, mode);
3598 }
3599 } else {
3600 if !add {
3601 s.clear_disjoint();
3602 }
3603
3604 s.set_pending_anchor_range(start..end, mode);
3605 }
3606 });
3607 }
3608
3609 fn begin_columnar_selection(
3610 &mut self,
3611 position: DisplayPoint,
3612 goal_column: u32,
3613 reset: bool,
3614 mode: ColumnarMode,
3615 window: &mut Window,
3616 cx: &mut Context<Self>,
3617 ) {
3618 if !self.focus_handle.is_focused(window) {
3619 self.last_focused_descendant = None;
3620 window.focus(&self.focus_handle);
3621 }
3622
3623 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3624
3625 if reset {
3626 let pointer_position = display_map
3627 .buffer_snapshot
3628 .anchor_before(position.to_point(&display_map));
3629
3630 self.change_selections(
3631 SelectionEffects::scroll(Autoscroll::newest()),
3632 window,
3633 cx,
3634 |s| {
3635 s.clear_disjoint();
3636 s.set_pending_anchor_range(
3637 pointer_position..pointer_position,
3638 SelectMode::Character,
3639 );
3640 },
3641 );
3642 };
3643
3644 let tail = self.selections.newest::<Point>(cx).tail();
3645 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3646 self.columnar_selection_state = match mode {
3647 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3648 selection_tail: selection_anchor,
3649 display_point: if reset {
3650 if position.column() != goal_column {
3651 Some(DisplayPoint::new(position.row(), goal_column))
3652 } else {
3653 None
3654 }
3655 } else {
3656 None
3657 },
3658 }),
3659 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3660 selection_tail: selection_anchor,
3661 }),
3662 };
3663
3664 if !reset {
3665 self.select_columns(position, goal_column, &display_map, window, cx);
3666 }
3667 }
3668
3669 fn update_selection(
3670 &mut self,
3671 position: DisplayPoint,
3672 goal_column: u32,
3673 scroll_delta: gpui::Point<f32>,
3674 window: &mut Window,
3675 cx: &mut Context<Self>,
3676 ) {
3677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3678
3679 if self.columnar_selection_state.is_some() {
3680 self.select_columns(position, goal_column, &display_map, window, cx);
3681 } else if let Some(mut pending) = self.selections.pending_anchor() {
3682 let buffer = &display_map.buffer_snapshot;
3683 let head;
3684 let tail;
3685 let mode = self.selections.pending_mode().unwrap();
3686 match &mode {
3687 SelectMode::Character => {
3688 head = position.to_point(&display_map);
3689 tail = pending.tail().to_point(buffer);
3690 }
3691 SelectMode::Word(original_range) => {
3692 let offset = display_map
3693 .clip_point(position, Bias::Left)
3694 .to_offset(&display_map, Bias::Left);
3695 let original_range = original_range.to_offset(buffer);
3696
3697 let head_offset = if buffer.is_inside_word(offset, false)
3698 || original_range.contains(&offset)
3699 {
3700 let (word_range, _) = buffer.surrounding_word(offset, false);
3701 if word_range.start < original_range.start {
3702 word_range.start
3703 } else {
3704 word_range.end
3705 }
3706 } else {
3707 offset
3708 };
3709
3710 head = head_offset.to_point(buffer);
3711 if head_offset <= original_range.start {
3712 tail = original_range.end.to_point(buffer);
3713 } else {
3714 tail = original_range.start.to_point(buffer);
3715 }
3716 }
3717 SelectMode::Line(original_range) => {
3718 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3719
3720 let position = display_map
3721 .clip_point(position, Bias::Left)
3722 .to_point(&display_map);
3723 let line_start = display_map.prev_line_boundary(position).0;
3724 let next_line_start = buffer.clip_point(
3725 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3726 Bias::Left,
3727 );
3728
3729 if line_start < original_range.start {
3730 head = line_start
3731 } else {
3732 head = next_line_start
3733 }
3734
3735 if head <= original_range.start {
3736 tail = original_range.end;
3737 } else {
3738 tail = original_range.start;
3739 }
3740 }
3741 SelectMode::All => {
3742 return;
3743 }
3744 };
3745
3746 if head < tail {
3747 pending.start = buffer.anchor_before(head);
3748 pending.end = buffer.anchor_before(tail);
3749 pending.reversed = true;
3750 } else {
3751 pending.start = buffer.anchor_before(tail);
3752 pending.end = buffer.anchor_before(head);
3753 pending.reversed = false;
3754 }
3755
3756 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3757 s.set_pending(pending, mode);
3758 });
3759 } else {
3760 log::error!("update_selection dispatched with no pending selection");
3761 return;
3762 }
3763
3764 self.apply_scroll_delta(scroll_delta, window, cx);
3765 cx.notify();
3766 }
3767
3768 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3769 self.columnar_selection_state.take();
3770 if self.selections.pending_anchor().is_some() {
3771 let selections = self.selections.all::<usize>(cx);
3772 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3773 s.select(selections);
3774 s.clear_pending();
3775 });
3776 }
3777 }
3778
3779 fn select_columns(
3780 &mut self,
3781 head: DisplayPoint,
3782 goal_column: u32,
3783 display_map: &DisplaySnapshot,
3784 window: &mut Window,
3785 cx: &mut Context<Self>,
3786 ) {
3787 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3788 return;
3789 };
3790
3791 let tail = match columnar_state {
3792 ColumnarSelectionState::FromMouse {
3793 selection_tail,
3794 display_point,
3795 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3796 ColumnarSelectionState::FromSelection { selection_tail } => {
3797 selection_tail.to_display_point(display_map)
3798 }
3799 };
3800
3801 let start_row = cmp::min(tail.row(), head.row());
3802 let end_row = cmp::max(tail.row(), head.row());
3803 let start_column = cmp::min(tail.column(), goal_column);
3804 let end_column = cmp::max(tail.column(), goal_column);
3805 let reversed = start_column < tail.column();
3806
3807 let selection_ranges = (start_row.0..=end_row.0)
3808 .map(DisplayRow)
3809 .filter_map(|row| {
3810 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3811 || start_column <= display_map.line_len(row))
3812 && !display_map.is_block_line(row)
3813 {
3814 let start = display_map
3815 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3816 .to_point(display_map);
3817 let end = display_map
3818 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3819 .to_point(display_map);
3820 if reversed {
3821 Some(end..start)
3822 } else {
3823 Some(start..end)
3824 }
3825 } else {
3826 None
3827 }
3828 })
3829 .collect::<Vec<_>>();
3830
3831 let ranges = match columnar_state {
3832 ColumnarSelectionState::FromMouse { .. } => {
3833 let mut non_empty_ranges = selection_ranges
3834 .iter()
3835 .filter(|selection_range| selection_range.start != selection_range.end)
3836 .peekable();
3837 if non_empty_ranges.peek().is_some() {
3838 non_empty_ranges.cloned().collect()
3839 } else {
3840 selection_ranges
3841 }
3842 }
3843 _ => selection_ranges,
3844 };
3845
3846 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3847 s.select_ranges(ranges);
3848 });
3849 cx.notify();
3850 }
3851
3852 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3853 self.selections
3854 .all_adjusted(cx)
3855 .iter()
3856 .any(|selection| !selection.is_empty())
3857 }
3858
3859 pub fn has_pending_nonempty_selection(&self) -> bool {
3860 let pending_nonempty_selection = match self.selections.pending_anchor() {
3861 Some(Selection { start, end, .. }) => start != end,
3862 None => false,
3863 };
3864
3865 pending_nonempty_selection
3866 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3867 }
3868
3869 pub fn has_pending_selection(&self) -> bool {
3870 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3871 }
3872
3873 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3874 self.selection_mark_mode = false;
3875 self.selection_drag_state = SelectionDragState::None;
3876
3877 if self.clear_expanded_diff_hunks(cx) {
3878 cx.notify();
3879 return;
3880 }
3881 if self.dismiss_menus_and_popups(true, window, cx) {
3882 return;
3883 }
3884
3885 if self.mode.is_full()
3886 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3887 {
3888 return;
3889 }
3890
3891 cx.propagate();
3892 }
3893
3894 pub fn dismiss_menus_and_popups(
3895 &mut self,
3896 is_user_requested: bool,
3897 window: &mut Window,
3898 cx: &mut Context<Self>,
3899 ) -> bool {
3900 if self.take_rename(false, window, cx).is_some() {
3901 return true;
3902 }
3903
3904 if hide_hover(self, cx) {
3905 return true;
3906 }
3907
3908 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3909 return true;
3910 }
3911
3912 if self.hide_context_menu(window, cx).is_some() {
3913 return true;
3914 }
3915
3916 if self.mouse_context_menu.take().is_some() {
3917 return true;
3918 }
3919
3920 if is_user_requested && self.discard_edit_prediction(true, cx) {
3921 return true;
3922 }
3923
3924 if self.snippet_stack.pop().is_some() {
3925 return true;
3926 }
3927
3928 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3929 self.dismiss_diagnostics(cx);
3930 return true;
3931 }
3932
3933 false
3934 }
3935
3936 fn linked_editing_ranges_for(
3937 &self,
3938 selection: Range<text::Anchor>,
3939 cx: &App,
3940 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3941 if self.linked_edit_ranges.is_empty() {
3942 return None;
3943 }
3944 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3945 selection.end.buffer_id.and_then(|end_buffer_id| {
3946 if selection.start.buffer_id != Some(end_buffer_id) {
3947 return None;
3948 }
3949 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3950 let snapshot = buffer.read(cx).snapshot();
3951 self.linked_edit_ranges
3952 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3953 .map(|ranges| (ranges, snapshot, buffer))
3954 })?;
3955 use text::ToOffset as TO;
3956 // find offset from the start of current range to current cursor position
3957 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3958
3959 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3960 let start_difference = start_offset - start_byte_offset;
3961 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3962 let end_difference = end_offset - start_byte_offset;
3963 // Current range has associated linked ranges.
3964 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3965 for range in linked_ranges.iter() {
3966 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3967 let end_offset = start_offset + end_difference;
3968 let start_offset = start_offset + start_difference;
3969 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3970 continue;
3971 }
3972 if self.selections.disjoint_anchor_ranges().any(|s| {
3973 if s.start.buffer_id != selection.start.buffer_id
3974 || s.end.buffer_id != selection.end.buffer_id
3975 {
3976 return false;
3977 }
3978 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3979 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3980 }) {
3981 continue;
3982 }
3983 let start = buffer_snapshot.anchor_after(start_offset);
3984 let end = buffer_snapshot.anchor_after(end_offset);
3985 linked_edits
3986 .entry(buffer.clone())
3987 .or_default()
3988 .push(start..end);
3989 }
3990 Some(linked_edits)
3991 }
3992
3993 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3994 let text: Arc<str> = text.into();
3995
3996 if self.read_only(cx) {
3997 return;
3998 }
3999
4000 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4001
4002 let selections = self.selections.all_adjusted(cx);
4003 let mut bracket_inserted = false;
4004 let mut edits = Vec::new();
4005 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4006 let mut new_selections = Vec::with_capacity(selections.len());
4007 let mut new_autoclose_regions = Vec::new();
4008 let snapshot = self.buffer.read(cx).read(cx);
4009 let mut clear_linked_edit_ranges = false;
4010
4011 for (selection, autoclose_region) in
4012 self.selections_with_autoclose_regions(selections, &snapshot)
4013 {
4014 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4015 // Determine if the inserted text matches the opening or closing
4016 // bracket of any of this language's bracket pairs.
4017 let mut bracket_pair = None;
4018 let mut is_bracket_pair_start = false;
4019 let mut is_bracket_pair_end = false;
4020 if !text.is_empty() {
4021 let mut bracket_pair_matching_end = None;
4022 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4023 // and they are removing the character that triggered IME popup.
4024 for (pair, enabled) in scope.brackets() {
4025 if !pair.close && !pair.surround {
4026 continue;
4027 }
4028
4029 if enabled && pair.start.ends_with(text.as_ref()) {
4030 let prefix_len = pair.start.len() - text.len();
4031 let preceding_text_matches_prefix = prefix_len == 0
4032 || (selection.start.column >= (prefix_len as u32)
4033 && snapshot.contains_str_at(
4034 Point::new(
4035 selection.start.row,
4036 selection.start.column - (prefix_len as u32),
4037 ),
4038 &pair.start[..prefix_len],
4039 ));
4040 if preceding_text_matches_prefix {
4041 bracket_pair = Some(pair.clone());
4042 is_bracket_pair_start = true;
4043 break;
4044 }
4045 }
4046 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4047 {
4048 // take first bracket pair matching end, but don't break in case a later bracket
4049 // pair matches start
4050 bracket_pair_matching_end = Some(pair.clone());
4051 }
4052 }
4053 if let Some(end) = bracket_pair_matching_end
4054 && bracket_pair.is_none()
4055 {
4056 bracket_pair = Some(end);
4057 is_bracket_pair_end = true;
4058 }
4059 }
4060
4061 if let Some(bracket_pair) = bracket_pair {
4062 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4063 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4064 let auto_surround =
4065 self.use_auto_surround && snapshot_settings.use_auto_surround;
4066 if selection.is_empty() {
4067 if is_bracket_pair_start {
4068 // If the inserted text is a suffix of an opening bracket and the
4069 // selection is preceded by the rest of the opening bracket, then
4070 // insert the closing bracket.
4071 let following_text_allows_autoclose = snapshot
4072 .chars_at(selection.start)
4073 .next()
4074 .is_none_or(|c| scope.should_autoclose_before(c));
4075
4076 let preceding_text_allows_autoclose = selection.start.column == 0
4077 || snapshot
4078 .reversed_chars_at(selection.start)
4079 .next()
4080 .is_none_or(|c| {
4081 bracket_pair.start != bracket_pair.end
4082 || !snapshot
4083 .char_classifier_at(selection.start)
4084 .is_word(c)
4085 });
4086
4087 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4088 && bracket_pair.start.len() == 1
4089 {
4090 let target = bracket_pair.start.chars().next().unwrap();
4091 let current_line_count = snapshot
4092 .reversed_chars_at(selection.start)
4093 .take_while(|&c| c != '\n')
4094 .filter(|&c| c == target)
4095 .count();
4096 current_line_count % 2 == 1
4097 } else {
4098 false
4099 };
4100
4101 if autoclose
4102 && bracket_pair.close
4103 && following_text_allows_autoclose
4104 && preceding_text_allows_autoclose
4105 && !is_closing_quote
4106 {
4107 let anchor = snapshot.anchor_before(selection.end);
4108 new_selections.push((selection.map(|_| anchor), text.len()));
4109 new_autoclose_regions.push((
4110 anchor,
4111 text.len(),
4112 selection.id,
4113 bracket_pair.clone(),
4114 ));
4115 edits.push((
4116 selection.range(),
4117 format!("{}{}", text, bracket_pair.end).into(),
4118 ));
4119 bracket_inserted = true;
4120 continue;
4121 }
4122 }
4123
4124 if let Some(region) = autoclose_region {
4125 // If the selection is followed by an auto-inserted closing bracket,
4126 // then don't insert that closing bracket again; just move the selection
4127 // past the closing bracket.
4128 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4129 && text.as_ref() == region.pair.end.as_str()
4130 && snapshot.contains_str_at(region.range.end, text.as_ref());
4131 if should_skip {
4132 let anchor = snapshot.anchor_after(selection.end);
4133 new_selections
4134 .push((selection.map(|_| anchor), region.pair.end.len()));
4135 continue;
4136 }
4137 }
4138
4139 let always_treat_brackets_as_autoclosed = snapshot
4140 .language_settings_at(selection.start, cx)
4141 .always_treat_brackets_as_autoclosed;
4142 if always_treat_brackets_as_autoclosed
4143 && is_bracket_pair_end
4144 && snapshot.contains_str_at(selection.end, text.as_ref())
4145 {
4146 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4147 // and the inserted text is a closing bracket and the selection is followed
4148 // by the closing bracket then move the selection past the closing bracket.
4149 let anchor = snapshot.anchor_after(selection.end);
4150 new_selections.push((selection.map(|_| anchor), text.len()));
4151 continue;
4152 }
4153 }
4154 // If an opening bracket is 1 character long and is typed while
4155 // text is selected, then surround that text with the bracket pair.
4156 else if auto_surround
4157 && bracket_pair.surround
4158 && is_bracket_pair_start
4159 && bracket_pair.start.chars().count() == 1
4160 {
4161 edits.push((selection.start..selection.start, text.clone()));
4162 edits.push((
4163 selection.end..selection.end,
4164 bracket_pair.end.as_str().into(),
4165 ));
4166 bracket_inserted = true;
4167 new_selections.push((
4168 Selection {
4169 id: selection.id,
4170 start: snapshot.anchor_after(selection.start),
4171 end: snapshot.anchor_before(selection.end),
4172 reversed: selection.reversed,
4173 goal: selection.goal,
4174 },
4175 0,
4176 ));
4177 continue;
4178 }
4179 }
4180 }
4181
4182 if self.auto_replace_emoji_shortcode
4183 && selection.is_empty()
4184 && text.as_ref().ends_with(':')
4185 && let Some(possible_emoji_short_code) =
4186 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4187 && !possible_emoji_short_code.is_empty()
4188 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4189 {
4190 let emoji_shortcode_start = Point::new(
4191 selection.start.row,
4192 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4193 );
4194
4195 // Remove shortcode from buffer
4196 edits.push((
4197 emoji_shortcode_start..selection.start,
4198 "".to_string().into(),
4199 ));
4200 new_selections.push((
4201 Selection {
4202 id: selection.id,
4203 start: snapshot.anchor_after(emoji_shortcode_start),
4204 end: snapshot.anchor_before(selection.start),
4205 reversed: selection.reversed,
4206 goal: selection.goal,
4207 },
4208 0,
4209 ));
4210
4211 // Insert emoji
4212 let selection_start_anchor = snapshot.anchor_after(selection.start);
4213 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4214 edits.push((selection.start..selection.end, emoji.to_string().into()));
4215
4216 continue;
4217 }
4218
4219 // If not handling any auto-close operation, then just replace the selected
4220 // text with the given input and move the selection to the end of the
4221 // newly inserted text.
4222 let anchor = snapshot.anchor_after(selection.end);
4223 if !self.linked_edit_ranges.is_empty() {
4224 let start_anchor = snapshot.anchor_before(selection.start);
4225
4226 let is_word_char = text.chars().next().is_none_or(|char| {
4227 let classifier = snapshot
4228 .char_classifier_at(start_anchor.to_offset(&snapshot))
4229 .ignore_punctuation(true);
4230 classifier.is_word(char)
4231 });
4232
4233 if is_word_char {
4234 if let Some(ranges) = self
4235 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4236 {
4237 for (buffer, edits) in ranges {
4238 linked_edits
4239 .entry(buffer.clone())
4240 .or_default()
4241 .extend(edits.into_iter().map(|range| (range, text.clone())));
4242 }
4243 }
4244 } else {
4245 clear_linked_edit_ranges = true;
4246 }
4247 }
4248
4249 new_selections.push((selection.map(|_| anchor), 0));
4250 edits.push((selection.start..selection.end, text.clone()));
4251 }
4252
4253 drop(snapshot);
4254
4255 self.transact(window, cx, |this, window, cx| {
4256 if clear_linked_edit_ranges {
4257 this.linked_edit_ranges.clear();
4258 }
4259 let initial_buffer_versions =
4260 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4261
4262 this.buffer.update(cx, |buffer, cx| {
4263 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4264 });
4265 for (buffer, edits) in linked_edits {
4266 buffer.update(cx, |buffer, cx| {
4267 let snapshot = buffer.snapshot();
4268 let edits = edits
4269 .into_iter()
4270 .map(|(range, text)| {
4271 use text::ToPoint as TP;
4272 let end_point = TP::to_point(&range.end, &snapshot);
4273 let start_point = TP::to_point(&range.start, &snapshot);
4274 (start_point..end_point, text)
4275 })
4276 .sorted_by_key(|(range, _)| range.start);
4277 buffer.edit(edits, None, cx);
4278 })
4279 }
4280 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4281 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4282 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4283 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4284 .zip(new_selection_deltas)
4285 .map(|(selection, delta)| Selection {
4286 id: selection.id,
4287 start: selection.start + delta,
4288 end: selection.end + delta,
4289 reversed: selection.reversed,
4290 goal: SelectionGoal::None,
4291 })
4292 .collect::<Vec<_>>();
4293
4294 let mut i = 0;
4295 for (position, delta, selection_id, pair) in new_autoclose_regions {
4296 let position = position.to_offset(&map.buffer_snapshot) + delta;
4297 let start = map.buffer_snapshot.anchor_before(position);
4298 let end = map.buffer_snapshot.anchor_after(position);
4299 while let Some(existing_state) = this.autoclose_regions.get(i) {
4300 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4301 Ordering::Less => i += 1,
4302 Ordering::Greater => break,
4303 Ordering::Equal => {
4304 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4305 Ordering::Less => i += 1,
4306 Ordering::Equal => break,
4307 Ordering::Greater => break,
4308 }
4309 }
4310 }
4311 }
4312 this.autoclose_regions.insert(
4313 i,
4314 AutocloseRegion {
4315 selection_id,
4316 range: start..end,
4317 pair,
4318 },
4319 );
4320 }
4321
4322 let had_active_edit_prediction = this.has_active_edit_prediction();
4323 this.change_selections(
4324 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4325 window,
4326 cx,
4327 |s| s.select(new_selections),
4328 );
4329
4330 if !bracket_inserted
4331 && let Some(on_type_format_task) =
4332 this.trigger_on_type_formatting(text.to_string(), window, cx)
4333 {
4334 on_type_format_task.detach_and_log_err(cx);
4335 }
4336
4337 let editor_settings = EditorSettings::get_global(cx);
4338 if bracket_inserted
4339 && (editor_settings.auto_signature_help
4340 || editor_settings.show_signature_help_after_edits)
4341 {
4342 this.show_signature_help(&ShowSignatureHelp, window, cx);
4343 }
4344
4345 let trigger_in_words =
4346 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4347 if this.hard_wrap.is_some() {
4348 let latest: Range<Point> = this.selections.newest(cx).range();
4349 if latest.is_empty()
4350 && this
4351 .buffer()
4352 .read(cx)
4353 .snapshot(cx)
4354 .line_len(MultiBufferRow(latest.start.row))
4355 == latest.start.column
4356 {
4357 this.rewrap_impl(
4358 RewrapOptions {
4359 override_language_settings: true,
4360 preserve_existing_whitespace: true,
4361 },
4362 cx,
4363 )
4364 }
4365 }
4366 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4367 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4368 this.refresh_edit_prediction(true, false, window, cx);
4369 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4370 });
4371 }
4372
4373 fn find_possible_emoji_shortcode_at_position(
4374 snapshot: &MultiBufferSnapshot,
4375 position: Point,
4376 ) -> Option<String> {
4377 let mut chars = Vec::new();
4378 let mut found_colon = false;
4379 for char in snapshot.reversed_chars_at(position).take(100) {
4380 // Found a possible emoji shortcode in the middle of the buffer
4381 if found_colon {
4382 if char.is_whitespace() {
4383 chars.reverse();
4384 return Some(chars.iter().collect());
4385 }
4386 // If the previous character is not a whitespace, we are in the middle of a word
4387 // and we only want to complete the shortcode if the word is made up of other emojis
4388 let mut containing_word = String::new();
4389 for ch in snapshot
4390 .reversed_chars_at(position)
4391 .skip(chars.len() + 1)
4392 .take(100)
4393 {
4394 if ch.is_whitespace() {
4395 break;
4396 }
4397 containing_word.push(ch);
4398 }
4399 let containing_word = containing_word.chars().rev().collect::<String>();
4400 if util::word_consists_of_emojis(containing_word.as_str()) {
4401 chars.reverse();
4402 return Some(chars.iter().collect());
4403 }
4404 }
4405
4406 if char.is_whitespace() || !char.is_ascii() {
4407 return None;
4408 }
4409 if char == ':' {
4410 found_colon = true;
4411 } else {
4412 chars.push(char);
4413 }
4414 }
4415 // Found a possible emoji shortcode at the beginning of the buffer
4416 chars.reverse();
4417 Some(chars.iter().collect())
4418 }
4419
4420 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4421 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4422 self.transact(window, cx, |this, window, cx| {
4423 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4424 let selections = this.selections.all::<usize>(cx);
4425 let multi_buffer = this.buffer.read(cx);
4426 let buffer = multi_buffer.snapshot(cx);
4427 selections
4428 .iter()
4429 .map(|selection| {
4430 let start_point = selection.start.to_point(&buffer);
4431 let mut existing_indent =
4432 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4433 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4434 let start = selection.start;
4435 let end = selection.end;
4436 let selection_is_empty = start == end;
4437 let language_scope = buffer.language_scope_at(start);
4438 let (
4439 comment_delimiter,
4440 doc_delimiter,
4441 insert_extra_newline,
4442 indent_on_newline,
4443 indent_on_extra_newline,
4444 ) = if let Some(language) = &language_scope {
4445 let mut insert_extra_newline =
4446 insert_extra_newline_brackets(&buffer, start..end, language)
4447 || insert_extra_newline_tree_sitter(&buffer, start..end);
4448
4449 // Comment extension on newline is allowed only for cursor selections
4450 let comment_delimiter = maybe!({
4451 if !selection_is_empty {
4452 return None;
4453 }
4454
4455 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4456 return None;
4457 }
4458
4459 let delimiters = language.line_comment_prefixes();
4460 let max_len_of_delimiter =
4461 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4462 let (snapshot, range) =
4463 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4464
4465 let num_of_whitespaces = snapshot
4466 .chars_for_range(range.clone())
4467 .take_while(|c| c.is_whitespace())
4468 .count();
4469 let comment_candidate = snapshot
4470 .chars_for_range(range.clone())
4471 .skip(num_of_whitespaces)
4472 .take(max_len_of_delimiter)
4473 .collect::<String>();
4474 let (delimiter, trimmed_len) = delimiters
4475 .iter()
4476 .filter_map(|delimiter| {
4477 let prefix = delimiter.trim_end();
4478 if comment_candidate.starts_with(prefix) {
4479 Some((delimiter, prefix.len()))
4480 } else {
4481 None
4482 }
4483 })
4484 .max_by_key(|(_, len)| *len)?;
4485
4486 if let Some(BlockCommentConfig {
4487 start: block_start, ..
4488 }) = language.block_comment()
4489 {
4490 let block_start_trimmed = block_start.trim_end();
4491 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4492 let line_content = snapshot
4493 .chars_for_range(range)
4494 .skip(num_of_whitespaces)
4495 .take(block_start_trimmed.len())
4496 .collect::<String>();
4497
4498 if line_content.starts_with(block_start_trimmed) {
4499 return None;
4500 }
4501 }
4502 }
4503
4504 let cursor_is_placed_after_comment_marker =
4505 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4506 if cursor_is_placed_after_comment_marker {
4507 Some(delimiter.clone())
4508 } else {
4509 None
4510 }
4511 });
4512
4513 let mut indent_on_newline = IndentSize::spaces(0);
4514 let mut indent_on_extra_newline = IndentSize::spaces(0);
4515
4516 let doc_delimiter = maybe!({
4517 if !selection_is_empty {
4518 return None;
4519 }
4520
4521 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4522 return None;
4523 }
4524
4525 let BlockCommentConfig {
4526 start: start_tag,
4527 end: end_tag,
4528 prefix: delimiter,
4529 tab_size: len,
4530 } = language.documentation_comment()?;
4531 let is_within_block_comment = buffer
4532 .language_scope_at(start_point)
4533 .is_some_and(|scope| scope.override_name() == Some("comment"));
4534 if !is_within_block_comment {
4535 return None;
4536 }
4537
4538 let (snapshot, range) =
4539 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4540
4541 let num_of_whitespaces = snapshot
4542 .chars_for_range(range.clone())
4543 .take_while(|c| c.is_whitespace())
4544 .count();
4545
4546 // 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.
4547 let column = start_point.column;
4548 let cursor_is_after_start_tag = {
4549 let start_tag_len = start_tag.len();
4550 let start_tag_line = snapshot
4551 .chars_for_range(range.clone())
4552 .skip(num_of_whitespaces)
4553 .take(start_tag_len)
4554 .collect::<String>();
4555 if start_tag_line.starts_with(start_tag.as_ref()) {
4556 num_of_whitespaces + start_tag_len <= column as usize
4557 } else {
4558 false
4559 }
4560 };
4561
4562 let cursor_is_after_delimiter = {
4563 let delimiter_trim = delimiter.trim_end();
4564 let delimiter_line = snapshot
4565 .chars_for_range(range.clone())
4566 .skip(num_of_whitespaces)
4567 .take(delimiter_trim.len())
4568 .collect::<String>();
4569 if delimiter_line.starts_with(delimiter_trim) {
4570 num_of_whitespaces + delimiter_trim.len() <= column as usize
4571 } else {
4572 false
4573 }
4574 };
4575
4576 let cursor_is_before_end_tag_if_exists = {
4577 let mut char_position = 0u32;
4578 let mut end_tag_offset = None;
4579
4580 'outer: for chunk in snapshot.text_for_range(range) {
4581 if let Some(byte_pos) = chunk.find(&**end_tag) {
4582 let chars_before_match =
4583 chunk[..byte_pos].chars().count() as u32;
4584 end_tag_offset =
4585 Some(char_position + chars_before_match);
4586 break 'outer;
4587 }
4588 char_position += chunk.chars().count() as u32;
4589 }
4590
4591 if let Some(end_tag_offset) = end_tag_offset {
4592 let cursor_is_before_end_tag = column <= end_tag_offset;
4593 if cursor_is_after_start_tag {
4594 if cursor_is_before_end_tag {
4595 insert_extra_newline = true;
4596 }
4597 let cursor_is_at_start_of_end_tag =
4598 column == end_tag_offset;
4599 if cursor_is_at_start_of_end_tag {
4600 indent_on_extra_newline.len = *len;
4601 }
4602 }
4603 cursor_is_before_end_tag
4604 } else {
4605 true
4606 }
4607 };
4608
4609 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4610 && cursor_is_before_end_tag_if_exists
4611 {
4612 if cursor_is_after_start_tag {
4613 indent_on_newline.len = *len;
4614 }
4615 Some(delimiter.clone())
4616 } else {
4617 None
4618 }
4619 });
4620
4621 (
4622 comment_delimiter,
4623 doc_delimiter,
4624 insert_extra_newline,
4625 indent_on_newline,
4626 indent_on_extra_newline,
4627 )
4628 } else {
4629 (
4630 None,
4631 None,
4632 false,
4633 IndentSize::default(),
4634 IndentSize::default(),
4635 )
4636 };
4637
4638 let prevent_auto_indent = doc_delimiter.is_some();
4639 let delimiter = comment_delimiter.or(doc_delimiter);
4640
4641 let capacity_for_delimiter =
4642 delimiter.as_deref().map(str::len).unwrap_or_default();
4643 let mut new_text = String::with_capacity(
4644 1 + capacity_for_delimiter
4645 + existing_indent.len as usize
4646 + indent_on_newline.len as usize
4647 + indent_on_extra_newline.len as usize,
4648 );
4649 new_text.push('\n');
4650 new_text.extend(existing_indent.chars());
4651 new_text.extend(indent_on_newline.chars());
4652
4653 if let Some(delimiter) = &delimiter {
4654 new_text.push_str(delimiter);
4655 }
4656
4657 if insert_extra_newline {
4658 new_text.push('\n');
4659 new_text.extend(existing_indent.chars());
4660 new_text.extend(indent_on_extra_newline.chars());
4661 }
4662
4663 let anchor = buffer.anchor_after(end);
4664 let new_selection = selection.map(|_| anchor);
4665 (
4666 ((start..end, new_text), prevent_auto_indent),
4667 (insert_extra_newline, new_selection),
4668 )
4669 })
4670 .unzip()
4671 };
4672
4673 let mut auto_indent_edits = Vec::new();
4674 let mut edits = Vec::new();
4675 for (edit, prevent_auto_indent) in edits_with_flags {
4676 if prevent_auto_indent {
4677 edits.push(edit);
4678 } else {
4679 auto_indent_edits.push(edit);
4680 }
4681 }
4682 if !edits.is_empty() {
4683 this.edit(edits, cx);
4684 }
4685 if !auto_indent_edits.is_empty() {
4686 this.edit_with_autoindent(auto_indent_edits, cx);
4687 }
4688
4689 let buffer = this.buffer.read(cx).snapshot(cx);
4690 let new_selections = selection_info
4691 .into_iter()
4692 .map(|(extra_newline_inserted, new_selection)| {
4693 let mut cursor = new_selection.end.to_point(&buffer);
4694 if extra_newline_inserted {
4695 cursor.row -= 1;
4696 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4697 }
4698 new_selection.map(|_| cursor)
4699 })
4700 .collect();
4701
4702 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4703 this.refresh_edit_prediction(true, false, window, cx);
4704 });
4705 }
4706
4707 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4708 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4709
4710 let buffer = self.buffer.read(cx);
4711 let snapshot = buffer.snapshot(cx);
4712
4713 let mut edits = Vec::new();
4714 let mut rows = Vec::new();
4715
4716 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4717 let cursor = selection.head();
4718 let row = cursor.row;
4719
4720 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4721
4722 let newline = "\n".to_string();
4723 edits.push((start_of_line..start_of_line, newline));
4724
4725 rows.push(row + rows_inserted as u32);
4726 }
4727
4728 self.transact(window, cx, |editor, window, cx| {
4729 editor.edit(edits, cx);
4730
4731 editor.change_selections(Default::default(), window, cx, |s| {
4732 let mut index = 0;
4733 s.move_cursors_with(|map, _, _| {
4734 let row = rows[index];
4735 index += 1;
4736
4737 let point = Point::new(row, 0);
4738 let boundary = map.next_line_boundary(point).1;
4739 let clipped = map.clip_point(boundary, Bias::Left);
4740
4741 (clipped, SelectionGoal::None)
4742 });
4743 });
4744
4745 let mut indent_edits = Vec::new();
4746 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4747 for row in rows {
4748 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4749 for (row, indent) in indents {
4750 if indent.len == 0 {
4751 continue;
4752 }
4753
4754 let text = match indent.kind {
4755 IndentKind::Space => " ".repeat(indent.len as usize),
4756 IndentKind::Tab => "\t".repeat(indent.len as usize),
4757 };
4758 let point = Point::new(row.0, 0);
4759 indent_edits.push((point..point, text));
4760 }
4761 }
4762 editor.edit(indent_edits, cx);
4763 });
4764 }
4765
4766 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4768
4769 let buffer = self.buffer.read(cx);
4770 let snapshot = buffer.snapshot(cx);
4771
4772 let mut edits = Vec::new();
4773 let mut rows = Vec::new();
4774 let mut rows_inserted = 0;
4775
4776 for selection in self.selections.all_adjusted(cx) {
4777 let cursor = selection.head();
4778 let row = cursor.row;
4779
4780 let point = Point::new(row + 1, 0);
4781 let start_of_line = snapshot.clip_point(point, Bias::Left);
4782
4783 let newline = "\n".to_string();
4784 edits.push((start_of_line..start_of_line, newline));
4785
4786 rows_inserted += 1;
4787 rows.push(row + rows_inserted);
4788 }
4789
4790 self.transact(window, cx, |editor, window, cx| {
4791 editor.edit(edits, cx);
4792
4793 editor.change_selections(Default::default(), window, cx, |s| {
4794 let mut index = 0;
4795 s.move_cursors_with(|map, _, _| {
4796 let row = rows[index];
4797 index += 1;
4798
4799 let point = Point::new(row, 0);
4800 let boundary = map.next_line_boundary(point).1;
4801 let clipped = map.clip_point(boundary, Bias::Left);
4802
4803 (clipped, SelectionGoal::None)
4804 });
4805 });
4806
4807 let mut indent_edits = Vec::new();
4808 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4809 for row in rows {
4810 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4811 for (row, indent) in indents {
4812 if indent.len == 0 {
4813 continue;
4814 }
4815
4816 let text = match indent.kind {
4817 IndentKind::Space => " ".repeat(indent.len as usize),
4818 IndentKind::Tab => "\t".repeat(indent.len as usize),
4819 };
4820 let point = Point::new(row.0, 0);
4821 indent_edits.push((point..point, text));
4822 }
4823 }
4824 editor.edit(indent_edits, cx);
4825 });
4826 }
4827
4828 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4829 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4830 original_indent_columns: Vec::new(),
4831 });
4832 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4833 }
4834
4835 fn insert_with_autoindent_mode(
4836 &mut self,
4837 text: &str,
4838 autoindent_mode: Option<AutoindentMode>,
4839 window: &mut Window,
4840 cx: &mut Context<Self>,
4841 ) {
4842 if self.read_only(cx) {
4843 return;
4844 }
4845
4846 let text: Arc<str> = text.into();
4847 self.transact(window, cx, |this, window, cx| {
4848 let old_selections = this.selections.all_adjusted(cx);
4849 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4850 let anchors = {
4851 let snapshot = buffer.read(cx);
4852 old_selections
4853 .iter()
4854 .map(|s| {
4855 let anchor = snapshot.anchor_after(s.head());
4856 s.map(|_| anchor)
4857 })
4858 .collect::<Vec<_>>()
4859 };
4860 buffer.edit(
4861 old_selections
4862 .iter()
4863 .map(|s| (s.start..s.end, text.clone())),
4864 autoindent_mode,
4865 cx,
4866 );
4867 anchors
4868 });
4869
4870 this.change_selections(Default::default(), window, cx, |s| {
4871 s.select_anchors(selection_anchors);
4872 });
4873
4874 cx.notify();
4875 });
4876 }
4877
4878 fn trigger_completion_on_input(
4879 &mut self,
4880 text: &str,
4881 trigger_in_words: bool,
4882 window: &mut Window,
4883 cx: &mut Context<Self>,
4884 ) {
4885 let completions_source = self
4886 .context_menu
4887 .borrow()
4888 .as_ref()
4889 .and_then(|menu| match menu {
4890 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4891 CodeContextMenu::CodeActions(_) => None,
4892 });
4893
4894 match completions_source {
4895 Some(CompletionsMenuSource::Words) => {
4896 self.show_word_completions(&ShowWordCompletions, window, cx)
4897 }
4898 Some(CompletionsMenuSource::Normal)
4899 | Some(CompletionsMenuSource::SnippetChoices)
4900 | None
4901 if self.is_completion_trigger(
4902 text,
4903 trigger_in_words,
4904 completions_source.is_some(),
4905 cx,
4906 ) =>
4907 {
4908 self.show_completions(
4909 &ShowCompletions {
4910 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4911 },
4912 window,
4913 cx,
4914 )
4915 }
4916 _ => {
4917 self.hide_context_menu(window, cx);
4918 }
4919 }
4920 }
4921
4922 fn is_completion_trigger(
4923 &self,
4924 text: &str,
4925 trigger_in_words: bool,
4926 menu_is_open: bool,
4927 cx: &mut Context<Self>,
4928 ) -> bool {
4929 let position = self.selections.newest_anchor().head();
4930 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4931 return false;
4932 };
4933
4934 if let Some(completion_provider) = &self.completion_provider {
4935 completion_provider.is_completion_trigger(
4936 &buffer,
4937 position.text_anchor,
4938 text,
4939 trigger_in_words,
4940 menu_is_open,
4941 cx,
4942 )
4943 } else {
4944 false
4945 }
4946 }
4947
4948 /// If any empty selections is touching the start of its innermost containing autoclose
4949 /// region, expand it to select the brackets.
4950 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4951 let selections = self.selections.all::<usize>(cx);
4952 let buffer = self.buffer.read(cx).read(cx);
4953 let new_selections = self
4954 .selections_with_autoclose_regions(selections, &buffer)
4955 .map(|(mut selection, region)| {
4956 if !selection.is_empty() {
4957 return selection;
4958 }
4959
4960 if let Some(region) = region {
4961 let mut range = region.range.to_offset(&buffer);
4962 if selection.start == range.start && range.start >= region.pair.start.len() {
4963 range.start -= region.pair.start.len();
4964 if buffer.contains_str_at(range.start, ®ion.pair.start)
4965 && buffer.contains_str_at(range.end, ®ion.pair.end)
4966 {
4967 range.end += region.pair.end.len();
4968 selection.start = range.start;
4969 selection.end = range.end;
4970
4971 return selection;
4972 }
4973 }
4974 }
4975
4976 let always_treat_brackets_as_autoclosed = buffer
4977 .language_settings_at(selection.start, cx)
4978 .always_treat_brackets_as_autoclosed;
4979
4980 if !always_treat_brackets_as_autoclosed {
4981 return selection;
4982 }
4983
4984 if let Some(scope) = buffer.language_scope_at(selection.start) {
4985 for (pair, enabled) in scope.brackets() {
4986 if !enabled || !pair.close {
4987 continue;
4988 }
4989
4990 if buffer.contains_str_at(selection.start, &pair.end) {
4991 let pair_start_len = pair.start.len();
4992 if buffer.contains_str_at(
4993 selection.start.saturating_sub(pair_start_len),
4994 &pair.start,
4995 ) {
4996 selection.start -= pair_start_len;
4997 selection.end += pair.end.len();
4998
4999 return selection;
5000 }
5001 }
5002 }
5003 }
5004
5005 selection
5006 })
5007 .collect();
5008
5009 drop(buffer);
5010 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5011 selections.select(new_selections)
5012 });
5013 }
5014
5015 /// Iterate the given selections, and for each one, find the smallest surrounding
5016 /// autoclose region. This uses the ordering of the selections and the autoclose
5017 /// regions to avoid repeated comparisons.
5018 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5019 &'a self,
5020 selections: impl IntoIterator<Item = Selection<D>>,
5021 buffer: &'a MultiBufferSnapshot,
5022 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5023 let mut i = 0;
5024 let mut regions = self.autoclose_regions.as_slice();
5025 selections.into_iter().map(move |selection| {
5026 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5027
5028 let mut enclosing = None;
5029 while let Some(pair_state) = regions.get(i) {
5030 if pair_state.range.end.to_offset(buffer) < range.start {
5031 regions = ®ions[i + 1..];
5032 i = 0;
5033 } else if pair_state.range.start.to_offset(buffer) > range.end {
5034 break;
5035 } else {
5036 if pair_state.selection_id == selection.id {
5037 enclosing = Some(pair_state);
5038 }
5039 i += 1;
5040 }
5041 }
5042
5043 (selection, enclosing)
5044 })
5045 }
5046
5047 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5048 fn invalidate_autoclose_regions(
5049 &mut self,
5050 mut selections: &[Selection<Anchor>],
5051 buffer: &MultiBufferSnapshot,
5052 ) {
5053 self.autoclose_regions.retain(|state| {
5054 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5055 return false;
5056 }
5057
5058 let mut i = 0;
5059 while let Some(selection) = selections.get(i) {
5060 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5061 selections = &selections[1..];
5062 continue;
5063 }
5064 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5065 break;
5066 }
5067 if selection.id == state.selection_id {
5068 return true;
5069 } else {
5070 i += 1;
5071 }
5072 }
5073 false
5074 });
5075 }
5076
5077 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5078 let offset = position.to_offset(buffer);
5079 let (word_range, kind) = buffer.surrounding_word(offset, true);
5080 if offset > word_range.start && kind == Some(CharKind::Word) {
5081 Some(
5082 buffer
5083 .text_for_range(word_range.start..offset)
5084 .collect::<String>(),
5085 )
5086 } else {
5087 None
5088 }
5089 }
5090
5091 pub fn toggle_inline_values(
5092 &mut self,
5093 _: &ToggleInlineValues,
5094 _: &mut Window,
5095 cx: &mut Context<Self>,
5096 ) {
5097 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5098
5099 self.refresh_inline_values(cx);
5100 }
5101
5102 pub fn toggle_inlay_hints(
5103 &mut self,
5104 _: &ToggleInlayHints,
5105 _: &mut Window,
5106 cx: &mut Context<Self>,
5107 ) {
5108 self.refresh_inlay_hints(
5109 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5110 cx,
5111 );
5112 }
5113
5114 pub fn inlay_hints_enabled(&self) -> bool {
5115 self.inlay_hint_cache.enabled
5116 }
5117
5118 pub fn inline_values_enabled(&self) -> bool {
5119 self.inline_value_cache.enabled
5120 }
5121
5122 #[cfg(any(test, feature = "test-support"))]
5123 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5124 self.display_map
5125 .read(cx)
5126 .current_inlays()
5127 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5128 .cloned()
5129 .collect()
5130 }
5131
5132 #[cfg(any(test, feature = "test-support"))]
5133 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5134 self.display_map
5135 .read(cx)
5136 .current_inlays()
5137 .cloned()
5138 .collect()
5139 }
5140
5141 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5142 if self.semantics_provider.is_none() || !self.mode.is_full() {
5143 return;
5144 }
5145
5146 let reason_description = reason.description();
5147 let ignore_debounce = matches!(
5148 reason,
5149 InlayHintRefreshReason::SettingsChange(_)
5150 | InlayHintRefreshReason::Toggle(_)
5151 | InlayHintRefreshReason::ExcerptsRemoved(_)
5152 | InlayHintRefreshReason::ModifiersChanged(_)
5153 );
5154 let (invalidate_cache, required_languages) = match reason {
5155 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5156 match self.inlay_hint_cache.modifiers_override(enabled) {
5157 Some(enabled) => {
5158 if enabled {
5159 (InvalidationStrategy::RefreshRequested, None)
5160 } else {
5161 self.splice_inlays(
5162 &self
5163 .visible_inlay_hints(cx)
5164 .iter()
5165 .map(|inlay| inlay.id)
5166 .collect::<Vec<InlayId>>(),
5167 Vec::new(),
5168 cx,
5169 );
5170 return;
5171 }
5172 }
5173 None => return,
5174 }
5175 }
5176 InlayHintRefreshReason::Toggle(enabled) => {
5177 if self.inlay_hint_cache.toggle(enabled) {
5178 if enabled {
5179 (InvalidationStrategy::RefreshRequested, None)
5180 } else {
5181 self.splice_inlays(
5182 &self
5183 .visible_inlay_hints(cx)
5184 .iter()
5185 .map(|inlay| inlay.id)
5186 .collect::<Vec<InlayId>>(),
5187 Vec::new(),
5188 cx,
5189 );
5190 return;
5191 }
5192 } else {
5193 return;
5194 }
5195 }
5196 InlayHintRefreshReason::SettingsChange(new_settings) => {
5197 match self.inlay_hint_cache.update_settings(
5198 &self.buffer,
5199 new_settings,
5200 self.visible_inlay_hints(cx),
5201 cx,
5202 ) {
5203 ControlFlow::Break(Some(InlaySplice {
5204 to_remove,
5205 to_insert,
5206 })) => {
5207 self.splice_inlays(&to_remove, to_insert, cx);
5208 return;
5209 }
5210 ControlFlow::Break(None) => return,
5211 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5212 }
5213 }
5214 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5215 if let Some(InlaySplice {
5216 to_remove,
5217 to_insert,
5218 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5219 {
5220 self.splice_inlays(&to_remove, to_insert, cx);
5221 }
5222 self.display_map.update(cx, |display_map, _| {
5223 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5224 });
5225 return;
5226 }
5227 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5228 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5229 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5230 }
5231 InlayHintRefreshReason::RefreshRequested => {
5232 (InvalidationStrategy::RefreshRequested, None)
5233 }
5234 };
5235
5236 if let Some(InlaySplice {
5237 to_remove,
5238 to_insert,
5239 }) = self.inlay_hint_cache.spawn_hint_refresh(
5240 reason_description,
5241 self.visible_excerpts(required_languages.as_ref(), cx),
5242 invalidate_cache,
5243 ignore_debounce,
5244 cx,
5245 ) {
5246 self.splice_inlays(&to_remove, to_insert, cx);
5247 }
5248 }
5249
5250 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5251 self.display_map
5252 .read(cx)
5253 .current_inlays()
5254 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5255 .cloned()
5256 .collect()
5257 }
5258
5259 pub fn visible_excerpts(
5260 &self,
5261 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5262 cx: &mut Context<Editor>,
5263 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5264 let Some(project) = self.project() else {
5265 return HashMap::default();
5266 };
5267 let project = project.read(cx);
5268 let multi_buffer = self.buffer().read(cx);
5269 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5270 let multi_buffer_visible_start = self
5271 .scroll_manager
5272 .anchor()
5273 .anchor
5274 .to_point(&multi_buffer_snapshot);
5275 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5276 multi_buffer_visible_start
5277 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5278 Bias::Left,
5279 );
5280 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5281 multi_buffer_snapshot
5282 .range_to_buffer_ranges(multi_buffer_visible_range)
5283 .into_iter()
5284 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5285 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5286 let buffer_file = project::File::from_dyn(buffer.file())?;
5287 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5288 let worktree_entry = buffer_worktree
5289 .read(cx)
5290 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5291 if worktree_entry.is_ignored {
5292 return None;
5293 }
5294
5295 let language = buffer.language()?;
5296 if let Some(restrict_to_languages) = restrict_to_languages
5297 && !restrict_to_languages.contains(language)
5298 {
5299 return None;
5300 }
5301 Some((
5302 excerpt_id,
5303 (
5304 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5305 buffer.version().clone(),
5306 excerpt_visible_range,
5307 ),
5308 ))
5309 })
5310 .collect()
5311 }
5312
5313 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5314 TextLayoutDetails {
5315 text_system: window.text_system().clone(),
5316 editor_style: self.style.clone().unwrap(),
5317 rem_size: window.rem_size(),
5318 scroll_anchor: self.scroll_manager.anchor(),
5319 visible_rows: self.visible_line_count(),
5320 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5321 }
5322 }
5323
5324 pub fn splice_inlays(
5325 &self,
5326 to_remove: &[InlayId],
5327 to_insert: Vec<Inlay>,
5328 cx: &mut Context<Self>,
5329 ) {
5330 self.display_map.update(cx, |display_map, cx| {
5331 display_map.splice_inlays(to_remove, to_insert, cx)
5332 });
5333 cx.notify();
5334 }
5335
5336 fn trigger_on_type_formatting(
5337 &self,
5338 input: String,
5339 window: &mut Window,
5340 cx: &mut Context<Self>,
5341 ) -> Option<Task<Result<()>>> {
5342 if input.len() != 1 {
5343 return None;
5344 }
5345
5346 let project = self.project()?;
5347 let position = self.selections.newest_anchor().head();
5348 let (buffer, buffer_position) = self
5349 .buffer
5350 .read(cx)
5351 .text_anchor_for_position(position, cx)?;
5352
5353 let settings = language_settings::language_settings(
5354 buffer
5355 .read(cx)
5356 .language_at(buffer_position)
5357 .map(|l| l.name()),
5358 buffer.read(cx).file(),
5359 cx,
5360 );
5361 if !settings.use_on_type_format {
5362 return None;
5363 }
5364
5365 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5366 // hence we do LSP request & edit on host side only — add formats to host's history.
5367 let push_to_lsp_host_history = true;
5368 // If this is not the host, append its history with new edits.
5369 let push_to_client_history = project.read(cx).is_via_collab();
5370
5371 let on_type_formatting = project.update(cx, |project, cx| {
5372 project.on_type_format(
5373 buffer.clone(),
5374 buffer_position,
5375 input,
5376 push_to_lsp_host_history,
5377 cx,
5378 )
5379 });
5380 Some(cx.spawn_in(window, async move |editor, cx| {
5381 if let Some(transaction) = on_type_formatting.await? {
5382 if push_to_client_history {
5383 buffer
5384 .update(cx, |buffer, _| {
5385 buffer.push_transaction(transaction, Instant::now());
5386 buffer.finalize_last_transaction();
5387 })
5388 .ok();
5389 }
5390 editor.update(cx, |editor, cx| {
5391 editor.refresh_document_highlights(cx);
5392 })?;
5393 }
5394 Ok(())
5395 }))
5396 }
5397
5398 pub fn show_word_completions(
5399 &mut self,
5400 _: &ShowWordCompletions,
5401 window: &mut Window,
5402 cx: &mut Context<Self>,
5403 ) {
5404 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5405 }
5406
5407 pub fn show_completions(
5408 &mut self,
5409 options: &ShowCompletions,
5410 window: &mut Window,
5411 cx: &mut Context<Self>,
5412 ) {
5413 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5414 }
5415
5416 fn open_or_update_completions_menu(
5417 &mut self,
5418 requested_source: Option<CompletionsMenuSource>,
5419 trigger: Option<&str>,
5420 window: &mut Window,
5421 cx: &mut Context<Self>,
5422 ) {
5423 if self.pending_rename.is_some() {
5424 return;
5425 }
5426
5427 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5428
5429 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5430 // inserted and selected. To handle that case, the start of the selection is used so that
5431 // the menu starts with all choices.
5432 let position = self
5433 .selections
5434 .newest_anchor()
5435 .start
5436 .bias_right(&multibuffer_snapshot);
5437 if position.diff_base_anchor.is_some() {
5438 return;
5439 }
5440 let (buffer, buffer_position) =
5441 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5442 output
5443 } else {
5444 return;
5445 };
5446 let buffer_snapshot = buffer.read(cx).snapshot();
5447
5448 let query: Option<Arc<String>> =
5449 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5450
5451 drop(multibuffer_snapshot);
5452
5453 let provider = match requested_source {
5454 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5455 Some(CompletionsMenuSource::Words) => None,
5456 Some(CompletionsMenuSource::SnippetChoices) => {
5457 log::error!("bug: SnippetChoices requested_source is not handled");
5458 None
5459 }
5460 };
5461
5462 let sort_completions = provider
5463 .as_ref()
5464 .is_some_and(|provider| provider.sort_completions());
5465
5466 let filter_completions = provider
5467 .as_ref()
5468 .is_none_or(|provider| provider.filter_completions());
5469
5470 let trigger_kind = match trigger {
5471 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5472 CompletionTriggerKind::TRIGGER_CHARACTER
5473 }
5474 _ => CompletionTriggerKind::INVOKED,
5475 };
5476 let completion_context = CompletionContext {
5477 trigger_character: trigger.and_then(|trigger| {
5478 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5479 Some(String::from(trigger))
5480 } else {
5481 None
5482 }
5483 }),
5484 trigger_kind,
5485 };
5486
5487 // Hide the current completions menu when a trigger char is typed. Without this, cached
5488 // completions from before the trigger char may be reused (#32774). Snippet choices could
5489 // involve trigger chars, so this is skipped in that case.
5490 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5491 {
5492 let menu_is_open = matches!(
5493 self.context_menu.borrow().as_ref(),
5494 Some(CodeContextMenu::Completions(_))
5495 );
5496 if menu_is_open {
5497 self.hide_context_menu(window, cx);
5498 }
5499 }
5500
5501 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5502 if filter_completions {
5503 menu.filter(query.clone(), provider.clone(), window, cx);
5504 }
5505 // When `is_incomplete` is false, no need to re-query completions when the current query
5506 // is a suffix of the initial query.
5507 if !menu.is_incomplete {
5508 // If the new query is a suffix of the old query (typing more characters) and
5509 // the previous result was complete, the existing completions can be filtered.
5510 //
5511 // Note that this is always true for snippet completions.
5512 let query_matches = match (&menu.initial_query, &query) {
5513 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5514 (None, _) => true,
5515 _ => false,
5516 };
5517 if query_matches {
5518 let position_matches = if menu.initial_position == position {
5519 true
5520 } else {
5521 let snapshot = self.buffer.read(cx).read(cx);
5522 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5523 };
5524 if position_matches {
5525 return;
5526 }
5527 }
5528 }
5529 };
5530
5531 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5532 buffer_snapshot.surrounding_word(buffer_position, false)
5533 {
5534 let word_to_exclude = buffer_snapshot
5535 .text_for_range(word_range.clone())
5536 .collect::<String>();
5537 (
5538 buffer_snapshot.anchor_before(word_range.start)
5539 ..buffer_snapshot.anchor_after(buffer_position),
5540 Some(word_to_exclude),
5541 )
5542 } else {
5543 (buffer_position..buffer_position, None)
5544 };
5545
5546 let language = buffer_snapshot
5547 .language_at(buffer_position)
5548 .map(|language| language.name());
5549
5550 let completion_settings =
5551 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5552
5553 let show_completion_documentation = buffer_snapshot
5554 .settings_at(buffer_position, cx)
5555 .show_completion_documentation;
5556
5557 // The document can be large, so stay in reasonable bounds when searching for words,
5558 // otherwise completion pop-up might be slow to appear.
5559 const WORD_LOOKUP_ROWS: u32 = 5_000;
5560 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5561 let min_word_search = buffer_snapshot.clip_point(
5562 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5563 Bias::Left,
5564 );
5565 let max_word_search = buffer_snapshot.clip_point(
5566 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5567 Bias::Right,
5568 );
5569 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5570 ..buffer_snapshot.point_to_offset(max_word_search);
5571
5572 let skip_digits = query
5573 .as_ref()
5574 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5575
5576 let omit_word_completions = match &query {
5577 Some(query) => query.chars().count() < completion_settings.words_min_length,
5578 None => completion_settings.words_min_length != 0,
5579 };
5580
5581 let (mut words, provider_responses) = match &provider {
5582 Some(provider) => {
5583 let provider_responses = provider.completions(
5584 position.excerpt_id,
5585 &buffer,
5586 buffer_position,
5587 completion_context,
5588 window,
5589 cx,
5590 );
5591
5592 let words = match (omit_word_completions, completion_settings.words) {
5593 (true, _) | (_, WordsCompletionMode::Disabled) => {
5594 Task::ready(BTreeMap::default())
5595 }
5596 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5597 .background_spawn(async move {
5598 buffer_snapshot.words_in_range(WordsQuery {
5599 fuzzy_contents: None,
5600 range: word_search_range,
5601 skip_digits,
5602 })
5603 }),
5604 };
5605
5606 (words, provider_responses)
5607 }
5608 None => {
5609 let words = if omit_word_completions {
5610 Task::ready(BTreeMap::default())
5611 } else {
5612 cx.background_spawn(async move {
5613 buffer_snapshot.words_in_range(WordsQuery {
5614 fuzzy_contents: None,
5615 range: word_search_range,
5616 skip_digits,
5617 })
5618 })
5619 };
5620 (words, Task::ready(Ok(Vec::new())))
5621 }
5622 };
5623
5624 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5625
5626 let id = post_inc(&mut self.next_completion_id);
5627 let task = cx.spawn_in(window, async move |editor, cx| {
5628 let Ok(()) = editor.update(cx, |this, _| {
5629 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5630 }) else {
5631 return;
5632 };
5633
5634 // TODO: Ideally completions from different sources would be selectively re-queried, so
5635 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5636 let mut completions = Vec::new();
5637 let mut is_incomplete = false;
5638 if let Some(provider_responses) = provider_responses.await.log_err()
5639 && !provider_responses.is_empty()
5640 {
5641 for response in provider_responses {
5642 completions.extend(response.completions);
5643 is_incomplete = is_incomplete || response.is_incomplete;
5644 }
5645 if completion_settings.words == WordsCompletionMode::Fallback {
5646 words = Task::ready(BTreeMap::default());
5647 }
5648 }
5649
5650 let mut words = words.await;
5651 if let Some(word_to_exclude) = &word_to_exclude {
5652 words.remove(word_to_exclude);
5653 }
5654 for lsp_completion in &completions {
5655 words.remove(&lsp_completion.new_text);
5656 }
5657 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5658 replace_range: word_replace_range.clone(),
5659 new_text: word.clone(),
5660 label: CodeLabel::plain(word, None),
5661 icon_path: None,
5662 documentation: None,
5663 source: CompletionSource::BufferWord {
5664 word_range,
5665 resolved: false,
5666 },
5667 insert_text_mode: Some(InsertTextMode::AS_IS),
5668 confirm: None,
5669 }));
5670
5671 let menu = if completions.is_empty() {
5672 None
5673 } else {
5674 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5675 let languages = editor
5676 .workspace
5677 .as_ref()
5678 .and_then(|(workspace, _)| workspace.upgrade())
5679 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5680 let menu = CompletionsMenu::new(
5681 id,
5682 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5683 sort_completions,
5684 show_completion_documentation,
5685 position,
5686 query.clone(),
5687 is_incomplete,
5688 buffer.clone(),
5689 completions.into(),
5690 snippet_sort_order,
5691 languages,
5692 language,
5693 cx,
5694 );
5695
5696 let query = if filter_completions { query } else { None };
5697 let matches_task = if let Some(query) = query {
5698 menu.do_async_filtering(query, cx)
5699 } else {
5700 Task::ready(menu.unfiltered_matches())
5701 };
5702 (menu, matches_task)
5703 }) else {
5704 return;
5705 };
5706
5707 let matches = matches_task.await;
5708
5709 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5710 // Newer menu already set, so exit.
5711 if let Some(CodeContextMenu::Completions(prev_menu)) =
5712 editor.context_menu.borrow().as_ref()
5713 && prev_menu.id > id
5714 {
5715 return;
5716 };
5717
5718 // Only valid to take prev_menu because it the new menu is immediately set
5719 // below, or the menu is hidden.
5720 if let Some(CodeContextMenu::Completions(prev_menu)) =
5721 editor.context_menu.borrow_mut().take()
5722 {
5723 let position_matches =
5724 if prev_menu.initial_position == menu.initial_position {
5725 true
5726 } else {
5727 let snapshot = editor.buffer.read(cx).read(cx);
5728 prev_menu.initial_position.to_offset(&snapshot)
5729 == menu.initial_position.to_offset(&snapshot)
5730 };
5731 if position_matches {
5732 // Preserve markdown cache before `set_filter_results` because it will
5733 // try to populate the documentation cache.
5734 menu.preserve_markdown_cache(prev_menu);
5735 }
5736 };
5737
5738 menu.set_filter_results(matches, provider, window, cx);
5739 }) else {
5740 return;
5741 };
5742
5743 menu.visible().then_some(menu)
5744 };
5745
5746 editor
5747 .update_in(cx, |editor, window, cx| {
5748 if editor.focus_handle.is_focused(window)
5749 && let Some(menu) = menu
5750 {
5751 *editor.context_menu.borrow_mut() =
5752 Some(CodeContextMenu::Completions(menu));
5753
5754 crate::hover_popover::hide_hover(editor, cx);
5755 if editor.show_edit_predictions_in_menu() {
5756 editor.update_visible_edit_prediction(window, cx);
5757 } else {
5758 editor.discard_edit_prediction(false, cx);
5759 }
5760
5761 cx.notify();
5762 return;
5763 }
5764
5765 if editor.completion_tasks.len() <= 1 {
5766 // If there are no more completion tasks and the last menu was empty, we should hide it.
5767 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5768 // If it was already hidden and we don't show edit predictions in the menu,
5769 // we should also show the edit prediction when available.
5770 if was_hidden && editor.show_edit_predictions_in_menu() {
5771 editor.update_visible_edit_prediction(window, cx);
5772 }
5773 }
5774 })
5775 .ok();
5776 });
5777
5778 self.completion_tasks.push((id, task));
5779 }
5780
5781 #[cfg(feature = "test-support")]
5782 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5783 let menu = self.context_menu.borrow();
5784 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5785 let completions = menu.completions.borrow();
5786 Some(completions.to_vec())
5787 } else {
5788 None
5789 }
5790 }
5791
5792 pub fn with_completions_menu_matching_id<R>(
5793 &self,
5794 id: CompletionId,
5795 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5796 ) -> R {
5797 let mut context_menu = self.context_menu.borrow_mut();
5798 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5799 return f(None);
5800 };
5801 if completions_menu.id != id {
5802 return f(None);
5803 }
5804 f(Some(completions_menu))
5805 }
5806
5807 pub fn confirm_completion(
5808 &mut self,
5809 action: &ConfirmCompletion,
5810 window: &mut Window,
5811 cx: &mut Context<Self>,
5812 ) -> Option<Task<Result<()>>> {
5813 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5814 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5815 }
5816
5817 pub fn confirm_completion_insert(
5818 &mut self,
5819 _: &ConfirmCompletionInsert,
5820 window: &mut Window,
5821 cx: &mut Context<Self>,
5822 ) -> Option<Task<Result<()>>> {
5823 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5824 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5825 }
5826
5827 pub fn confirm_completion_replace(
5828 &mut self,
5829 _: &ConfirmCompletionReplace,
5830 window: &mut Window,
5831 cx: &mut Context<Self>,
5832 ) -> Option<Task<Result<()>>> {
5833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5834 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5835 }
5836
5837 pub fn compose_completion(
5838 &mut self,
5839 action: &ComposeCompletion,
5840 window: &mut Window,
5841 cx: &mut Context<Self>,
5842 ) -> Option<Task<Result<()>>> {
5843 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5844 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5845 }
5846
5847 fn do_completion(
5848 &mut self,
5849 item_ix: Option<usize>,
5850 intent: CompletionIntent,
5851 window: &mut Window,
5852 cx: &mut Context<Editor>,
5853 ) -> Option<Task<Result<()>>> {
5854 use language::ToOffset as _;
5855
5856 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5857 else {
5858 return None;
5859 };
5860
5861 let candidate_id = {
5862 let entries = completions_menu.entries.borrow();
5863 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5864 if self.show_edit_predictions_in_menu() {
5865 self.discard_edit_prediction(true, cx);
5866 }
5867 mat.candidate_id
5868 };
5869
5870 let completion = completions_menu
5871 .completions
5872 .borrow()
5873 .get(candidate_id)?
5874 .clone();
5875 cx.stop_propagation();
5876
5877 let buffer_handle = completions_menu.buffer.clone();
5878
5879 let CompletionEdit {
5880 new_text,
5881 snippet,
5882 replace_range,
5883 } = process_completion_for_edit(
5884 &completion,
5885 intent,
5886 &buffer_handle,
5887 &completions_menu.initial_position.text_anchor,
5888 cx,
5889 );
5890
5891 let buffer = buffer_handle.read(cx);
5892 let snapshot = self.buffer.read(cx).snapshot(cx);
5893 let newest_anchor = self.selections.newest_anchor();
5894 let replace_range_multibuffer = {
5895 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5896 let multibuffer_anchor = snapshot
5897 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5898 .unwrap()
5899 ..snapshot
5900 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5901 .unwrap();
5902 multibuffer_anchor.start.to_offset(&snapshot)
5903 ..multibuffer_anchor.end.to_offset(&snapshot)
5904 };
5905 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5906 return None;
5907 }
5908
5909 let old_text = buffer
5910 .text_for_range(replace_range.clone())
5911 .collect::<String>();
5912 let lookbehind = newest_anchor
5913 .start
5914 .text_anchor
5915 .to_offset(buffer)
5916 .saturating_sub(replace_range.start);
5917 let lookahead = replace_range
5918 .end
5919 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5920 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5921 let suffix = &old_text[lookbehind.min(old_text.len())..];
5922
5923 let selections = self.selections.all::<usize>(cx);
5924 let mut ranges = Vec::new();
5925 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5926
5927 for selection in &selections {
5928 let range = if selection.id == newest_anchor.id {
5929 replace_range_multibuffer.clone()
5930 } else {
5931 let mut range = selection.range();
5932
5933 // if prefix is present, don't duplicate it
5934 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5935 range.start = range.start.saturating_sub(lookbehind);
5936
5937 // if suffix is also present, mimic the newest cursor and replace it
5938 if selection.id != newest_anchor.id
5939 && snapshot.contains_str_at(range.end, suffix)
5940 {
5941 range.end += lookahead;
5942 }
5943 }
5944 range
5945 };
5946
5947 ranges.push(range.clone());
5948
5949 if !self.linked_edit_ranges.is_empty() {
5950 let start_anchor = snapshot.anchor_before(range.start);
5951 let end_anchor = snapshot.anchor_after(range.end);
5952 if let Some(ranges) = self
5953 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5954 {
5955 for (buffer, edits) in ranges {
5956 linked_edits
5957 .entry(buffer.clone())
5958 .or_default()
5959 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5960 }
5961 }
5962 }
5963 }
5964
5965 let common_prefix_len = old_text
5966 .chars()
5967 .zip(new_text.chars())
5968 .take_while(|(a, b)| a == b)
5969 .map(|(a, _)| a.len_utf8())
5970 .sum::<usize>();
5971
5972 cx.emit(EditorEvent::InputHandled {
5973 utf16_range_to_replace: None,
5974 text: new_text[common_prefix_len..].into(),
5975 });
5976
5977 self.transact(window, cx, |editor, window, cx| {
5978 if let Some(mut snippet) = snippet {
5979 snippet.text = new_text.to_string();
5980 editor
5981 .insert_snippet(&ranges, snippet, window, cx)
5982 .log_err();
5983 } else {
5984 editor.buffer.update(cx, |multi_buffer, cx| {
5985 let auto_indent = match completion.insert_text_mode {
5986 Some(InsertTextMode::AS_IS) => None,
5987 _ => editor.autoindent_mode.clone(),
5988 };
5989 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5990 multi_buffer.edit(edits, auto_indent, cx);
5991 });
5992 }
5993 for (buffer, edits) in linked_edits {
5994 buffer.update(cx, |buffer, cx| {
5995 let snapshot = buffer.snapshot();
5996 let edits = edits
5997 .into_iter()
5998 .map(|(range, text)| {
5999 use text::ToPoint as TP;
6000 let end_point = TP::to_point(&range.end, &snapshot);
6001 let start_point = TP::to_point(&range.start, &snapshot);
6002 (start_point..end_point, text)
6003 })
6004 .sorted_by_key(|(range, _)| range.start);
6005 buffer.edit(edits, None, cx);
6006 })
6007 }
6008
6009 editor.refresh_edit_prediction(true, false, window, cx);
6010 });
6011 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6012
6013 let show_new_completions_on_confirm = completion
6014 .confirm
6015 .as_ref()
6016 .is_some_and(|confirm| confirm(intent, window, cx));
6017 if show_new_completions_on_confirm {
6018 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6019 }
6020
6021 let provider = self.completion_provider.as_ref()?;
6022 drop(completion);
6023 let apply_edits = provider.apply_additional_edits_for_completion(
6024 buffer_handle,
6025 completions_menu.completions.clone(),
6026 candidate_id,
6027 true,
6028 cx,
6029 );
6030
6031 let editor_settings = EditorSettings::get_global(cx);
6032 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6033 // After the code completion is finished, users often want to know what signatures are needed.
6034 // so we should automatically call signature_help
6035 self.show_signature_help(&ShowSignatureHelp, window, cx);
6036 }
6037
6038 Some(cx.foreground_executor().spawn(async move {
6039 apply_edits.await?;
6040 Ok(())
6041 }))
6042 }
6043
6044 pub fn toggle_code_actions(
6045 &mut self,
6046 action: &ToggleCodeActions,
6047 window: &mut Window,
6048 cx: &mut Context<Self>,
6049 ) {
6050 let quick_launch = action.quick_launch;
6051 let mut context_menu = self.context_menu.borrow_mut();
6052 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6053 if code_actions.deployed_from == action.deployed_from {
6054 // Toggle if we're selecting the same one
6055 *context_menu = None;
6056 cx.notify();
6057 return;
6058 } else {
6059 // Otherwise, clear it and start a new one
6060 *context_menu = None;
6061 cx.notify();
6062 }
6063 }
6064 drop(context_menu);
6065 let snapshot = self.snapshot(window, cx);
6066 let deployed_from = action.deployed_from.clone();
6067 let action = action.clone();
6068 self.completion_tasks.clear();
6069 self.discard_edit_prediction(false, cx);
6070
6071 let multibuffer_point = match &action.deployed_from {
6072 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6073 DisplayPoint::new(*row, 0).to_point(&snapshot)
6074 }
6075 _ => self.selections.newest::<Point>(cx).head(),
6076 };
6077 let Some((buffer, buffer_row)) = snapshot
6078 .buffer_snapshot
6079 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6080 .and_then(|(buffer_snapshot, range)| {
6081 self.buffer()
6082 .read(cx)
6083 .buffer(buffer_snapshot.remote_id())
6084 .map(|buffer| (buffer, range.start.row))
6085 })
6086 else {
6087 return;
6088 };
6089 let buffer_id = buffer.read(cx).remote_id();
6090 let tasks = self
6091 .tasks
6092 .get(&(buffer_id, buffer_row))
6093 .map(|t| Arc::new(t.to_owned()));
6094
6095 if !self.focus_handle.is_focused(window) {
6096 return;
6097 }
6098 let project = self.project.clone();
6099
6100 let code_actions_task = match deployed_from {
6101 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6102 _ => self.code_actions(buffer_row, window, cx),
6103 };
6104
6105 let runnable_task = match deployed_from {
6106 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6107 _ => {
6108 let mut task_context_task = Task::ready(None);
6109 if let Some(tasks) = &tasks
6110 && let Some(project) = project
6111 {
6112 task_context_task =
6113 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6114 }
6115
6116 cx.spawn_in(window, {
6117 let buffer = buffer.clone();
6118 async move |editor, cx| {
6119 let task_context = task_context_task.await;
6120
6121 let resolved_tasks =
6122 tasks
6123 .zip(task_context.clone())
6124 .map(|(tasks, task_context)| ResolvedTasks {
6125 templates: tasks.resolve(&task_context).collect(),
6126 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6127 multibuffer_point.row,
6128 tasks.column,
6129 )),
6130 });
6131 let debug_scenarios = editor
6132 .update(cx, |editor, cx| {
6133 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6134 })?
6135 .await;
6136 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6137 }
6138 })
6139 }
6140 };
6141
6142 cx.spawn_in(window, async move |editor, cx| {
6143 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6144 let code_actions = code_actions_task.await;
6145 let spawn_straight_away = quick_launch
6146 && resolved_tasks
6147 .as_ref()
6148 .is_some_and(|tasks| tasks.templates.len() == 1)
6149 && code_actions
6150 .as_ref()
6151 .is_none_or(|actions| actions.is_empty())
6152 && debug_scenarios.is_empty();
6153
6154 editor.update_in(cx, |editor, window, cx| {
6155 crate::hover_popover::hide_hover(editor, cx);
6156 let actions = CodeActionContents::new(
6157 resolved_tasks,
6158 code_actions,
6159 debug_scenarios,
6160 task_context.unwrap_or_default(),
6161 );
6162
6163 // Don't show the menu if there are no actions available
6164 if actions.is_empty() {
6165 cx.notify();
6166 return Task::ready(Ok(()));
6167 }
6168
6169 *editor.context_menu.borrow_mut() =
6170 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6171 buffer,
6172 actions,
6173 selected_item: Default::default(),
6174 scroll_handle: UniformListScrollHandle::default(),
6175 deployed_from,
6176 }));
6177 cx.notify();
6178 if spawn_straight_away
6179 && let Some(task) = editor.confirm_code_action(
6180 &ConfirmCodeAction { item_ix: Some(0) },
6181 window,
6182 cx,
6183 )
6184 {
6185 return task;
6186 }
6187
6188 Task::ready(Ok(()))
6189 })
6190 })
6191 .detach_and_log_err(cx);
6192 }
6193
6194 fn debug_scenarios(
6195 &mut self,
6196 resolved_tasks: &Option<ResolvedTasks>,
6197 buffer: &Entity<Buffer>,
6198 cx: &mut App,
6199 ) -> Task<Vec<task::DebugScenario>> {
6200 maybe!({
6201 let project = self.project()?;
6202 let dap_store = project.read(cx).dap_store();
6203 let mut scenarios = vec![];
6204 let resolved_tasks = resolved_tasks.as_ref()?;
6205 let buffer = buffer.read(cx);
6206 let language = buffer.language()?;
6207 let file = buffer.file();
6208 let debug_adapter = language_settings(language.name().into(), file, cx)
6209 .debuggers
6210 .first()
6211 .map(SharedString::from)
6212 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6213
6214 dap_store.update(cx, |dap_store, cx| {
6215 for (_, task) in &resolved_tasks.templates {
6216 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6217 task.original_task().clone(),
6218 debug_adapter.clone().into(),
6219 task.display_label().to_owned().into(),
6220 cx,
6221 );
6222 scenarios.push(maybe_scenario);
6223 }
6224 });
6225 Some(cx.background_spawn(async move {
6226 futures::future::join_all(scenarios)
6227 .await
6228 .into_iter()
6229 .flatten()
6230 .collect::<Vec<_>>()
6231 }))
6232 })
6233 .unwrap_or_else(|| Task::ready(vec![]))
6234 }
6235
6236 fn code_actions(
6237 &mut self,
6238 buffer_row: u32,
6239 window: &mut Window,
6240 cx: &mut Context<Self>,
6241 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6242 let mut task = self.code_actions_task.take();
6243 cx.spawn_in(window, async move |editor, cx| {
6244 while let Some(prev_task) = task {
6245 prev_task.await.log_err();
6246 task = editor
6247 .update(cx, |this, _| this.code_actions_task.take())
6248 .ok()?;
6249 }
6250
6251 editor
6252 .update(cx, |editor, cx| {
6253 editor
6254 .available_code_actions
6255 .clone()
6256 .and_then(|(location, code_actions)| {
6257 let snapshot = location.buffer.read(cx).snapshot();
6258 let point_range = location.range.to_point(&snapshot);
6259 let point_range = point_range.start.row..=point_range.end.row;
6260 if point_range.contains(&buffer_row) {
6261 Some(code_actions)
6262 } else {
6263 None
6264 }
6265 })
6266 })
6267 .ok()
6268 .flatten()
6269 })
6270 }
6271
6272 pub fn confirm_code_action(
6273 &mut self,
6274 action: &ConfirmCodeAction,
6275 window: &mut Window,
6276 cx: &mut Context<Self>,
6277 ) -> Option<Task<Result<()>>> {
6278 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6279
6280 let actions_menu =
6281 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6282 menu
6283 } else {
6284 return None;
6285 };
6286
6287 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6288 let action = actions_menu.actions.get(action_ix)?;
6289 let title = action.label();
6290 let buffer = actions_menu.buffer;
6291 let workspace = self.workspace()?;
6292
6293 match action {
6294 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6295 workspace.update(cx, |workspace, cx| {
6296 workspace.schedule_resolved_task(
6297 task_source_kind,
6298 resolved_task,
6299 false,
6300 window,
6301 cx,
6302 );
6303
6304 Some(Task::ready(Ok(())))
6305 })
6306 }
6307 CodeActionsItem::CodeAction {
6308 excerpt_id,
6309 action,
6310 provider,
6311 } => {
6312 let apply_code_action =
6313 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6314 let workspace = workspace.downgrade();
6315 Some(cx.spawn_in(window, async move |editor, cx| {
6316 let project_transaction = apply_code_action.await?;
6317 Self::open_project_transaction(
6318 &editor,
6319 workspace,
6320 project_transaction,
6321 title,
6322 cx,
6323 )
6324 .await
6325 }))
6326 }
6327 CodeActionsItem::DebugScenario(scenario) => {
6328 let context = actions_menu.actions.context;
6329
6330 workspace.update(cx, |workspace, cx| {
6331 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6332 workspace.start_debug_session(
6333 scenario,
6334 context,
6335 Some(buffer),
6336 None,
6337 window,
6338 cx,
6339 );
6340 });
6341 Some(Task::ready(Ok(())))
6342 }
6343 }
6344 }
6345
6346 pub async fn open_project_transaction(
6347 editor: &WeakEntity<Editor>,
6348 workspace: WeakEntity<Workspace>,
6349 transaction: ProjectTransaction,
6350 title: String,
6351 cx: &mut AsyncWindowContext,
6352 ) -> Result<()> {
6353 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6354 cx.update(|_, cx| {
6355 entries.sort_unstable_by_key(|(buffer, _)| {
6356 buffer.read(cx).file().map(|f| f.path().clone())
6357 });
6358 })?;
6359
6360 // If the project transaction's edits are all contained within this editor, then
6361 // avoid opening a new editor to display them.
6362
6363 if let Some((buffer, transaction)) = entries.first() {
6364 if entries.len() == 1 {
6365 let excerpt = editor.update(cx, |editor, cx| {
6366 editor
6367 .buffer()
6368 .read(cx)
6369 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6370 })?;
6371 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6372 && excerpted_buffer == *buffer
6373 {
6374 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6375 let excerpt_range = excerpt_range.to_offset(buffer);
6376 buffer
6377 .edited_ranges_for_transaction::<usize>(transaction)
6378 .all(|range| {
6379 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6380 })
6381 })?;
6382
6383 if all_edits_within_excerpt {
6384 return Ok(());
6385 }
6386 }
6387 }
6388 } else {
6389 return Ok(());
6390 }
6391
6392 let mut ranges_to_highlight = Vec::new();
6393 let excerpt_buffer = cx.new(|cx| {
6394 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6395 for (buffer_handle, transaction) in &entries {
6396 let edited_ranges = buffer_handle
6397 .read(cx)
6398 .edited_ranges_for_transaction::<Point>(transaction)
6399 .collect::<Vec<_>>();
6400 let (ranges, _) = multibuffer.set_excerpts_for_path(
6401 PathKey::for_buffer(buffer_handle, cx),
6402 buffer_handle.clone(),
6403 edited_ranges,
6404 multibuffer_context_lines(cx),
6405 cx,
6406 );
6407
6408 ranges_to_highlight.extend(ranges);
6409 }
6410 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6411 multibuffer
6412 })?;
6413
6414 workspace.update_in(cx, |workspace, window, cx| {
6415 let project = workspace.project().clone();
6416 let editor =
6417 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6418 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6419 editor.update(cx, |editor, cx| {
6420 editor.highlight_background::<Self>(
6421 &ranges_to_highlight,
6422 |theme| theme.colors().editor_highlighted_line_background,
6423 cx,
6424 );
6425 });
6426 })?;
6427
6428 Ok(())
6429 }
6430
6431 pub fn clear_code_action_providers(&mut self) {
6432 self.code_action_providers.clear();
6433 self.available_code_actions.take();
6434 }
6435
6436 pub fn add_code_action_provider(
6437 &mut self,
6438 provider: Rc<dyn CodeActionProvider>,
6439 window: &mut Window,
6440 cx: &mut Context<Self>,
6441 ) {
6442 if self
6443 .code_action_providers
6444 .iter()
6445 .any(|existing_provider| existing_provider.id() == provider.id())
6446 {
6447 return;
6448 }
6449
6450 self.code_action_providers.push(provider);
6451 self.refresh_code_actions(window, cx);
6452 }
6453
6454 pub fn remove_code_action_provider(
6455 &mut self,
6456 id: Arc<str>,
6457 window: &mut Window,
6458 cx: &mut Context<Self>,
6459 ) {
6460 self.code_action_providers
6461 .retain(|provider| provider.id() != id);
6462 self.refresh_code_actions(window, cx);
6463 }
6464
6465 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6466 !self.code_action_providers.is_empty()
6467 && EditorSettings::get_global(cx).toolbar.code_actions
6468 }
6469
6470 pub fn has_available_code_actions(&self) -> bool {
6471 self.available_code_actions
6472 .as_ref()
6473 .is_some_and(|(_, actions)| !actions.is_empty())
6474 }
6475
6476 fn render_inline_code_actions(
6477 &self,
6478 icon_size: ui::IconSize,
6479 display_row: DisplayRow,
6480 is_active: bool,
6481 cx: &mut Context<Self>,
6482 ) -> AnyElement {
6483 let show_tooltip = !self.context_menu_visible();
6484 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6485 .icon_size(icon_size)
6486 .shape(ui::IconButtonShape::Square)
6487 .icon_color(ui::Color::Hidden)
6488 .toggle_state(is_active)
6489 .when(show_tooltip, |this| {
6490 this.tooltip({
6491 let focus_handle = self.focus_handle.clone();
6492 move |window, cx| {
6493 Tooltip::for_action_in(
6494 "Toggle Code Actions",
6495 &ToggleCodeActions {
6496 deployed_from: None,
6497 quick_launch: false,
6498 },
6499 &focus_handle,
6500 window,
6501 cx,
6502 )
6503 }
6504 })
6505 })
6506 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6507 window.focus(&editor.focus_handle(cx));
6508 editor.toggle_code_actions(
6509 &crate::actions::ToggleCodeActions {
6510 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6511 display_row,
6512 )),
6513 quick_launch: false,
6514 },
6515 window,
6516 cx,
6517 );
6518 }))
6519 .into_any_element()
6520 }
6521
6522 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6523 &self.context_menu
6524 }
6525
6526 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6527 let newest_selection = self.selections.newest_anchor().clone();
6528 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6529 let buffer = self.buffer.read(cx);
6530 if newest_selection.head().diff_base_anchor.is_some() {
6531 return None;
6532 }
6533 let (start_buffer, start) =
6534 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6535 let (end_buffer, end) =
6536 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6537 if start_buffer != end_buffer {
6538 return None;
6539 }
6540
6541 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6542 cx.background_executor()
6543 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6544 .await;
6545
6546 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6547 let providers = this.code_action_providers.clone();
6548 let tasks = this
6549 .code_action_providers
6550 .iter()
6551 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6552 .collect::<Vec<_>>();
6553 (providers, tasks)
6554 })?;
6555
6556 let mut actions = Vec::new();
6557 for (provider, provider_actions) in
6558 providers.into_iter().zip(future::join_all(tasks).await)
6559 {
6560 if let Some(provider_actions) = provider_actions.log_err() {
6561 actions.extend(provider_actions.into_iter().map(|action| {
6562 AvailableCodeAction {
6563 excerpt_id: newest_selection.start.excerpt_id,
6564 action,
6565 provider: provider.clone(),
6566 }
6567 }));
6568 }
6569 }
6570
6571 this.update(cx, |this, cx| {
6572 this.available_code_actions = if actions.is_empty() {
6573 None
6574 } else {
6575 Some((
6576 Location {
6577 buffer: start_buffer,
6578 range: start..end,
6579 },
6580 actions.into(),
6581 ))
6582 };
6583 cx.notify();
6584 })
6585 }));
6586 None
6587 }
6588
6589 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6590 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6591 self.show_git_blame_inline = false;
6592
6593 self.show_git_blame_inline_delay_task =
6594 Some(cx.spawn_in(window, async move |this, cx| {
6595 cx.background_executor().timer(delay).await;
6596
6597 this.update(cx, |this, cx| {
6598 this.show_git_blame_inline = true;
6599 cx.notify();
6600 })
6601 .log_err();
6602 }));
6603 }
6604 }
6605
6606 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6607 let snapshot = self.snapshot(window, cx);
6608 let cursor = self.selections.newest::<Point>(cx).head();
6609 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6610 else {
6611 return;
6612 };
6613
6614 let Some(blame) = self.blame.as_ref() else {
6615 return;
6616 };
6617
6618 let row_info = RowInfo {
6619 buffer_id: Some(buffer.remote_id()),
6620 buffer_row: Some(point.row),
6621 ..Default::default()
6622 };
6623 let Some(blame_entry) = blame
6624 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6625 .flatten()
6626 else {
6627 return;
6628 };
6629
6630 let anchor = self.selections.newest_anchor().head();
6631 let position = self.to_pixel_point(anchor, &snapshot, window);
6632 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6633 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6634 };
6635 }
6636
6637 fn show_blame_popover(
6638 &mut self,
6639 blame_entry: &BlameEntry,
6640 position: gpui::Point<Pixels>,
6641 ignore_timeout: bool,
6642 cx: &mut Context<Self>,
6643 ) {
6644 if let Some(state) = &mut self.inline_blame_popover {
6645 state.hide_task.take();
6646 } else {
6647 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6648 let blame_entry = blame_entry.clone();
6649 let show_task = cx.spawn(async move |editor, cx| {
6650 if !ignore_timeout {
6651 cx.background_executor()
6652 .timer(std::time::Duration::from_millis(blame_popover_delay))
6653 .await;
6654 }
6655 editor
6656 .update(cx, |editor, cx| {
6657 editor.inline_blame_popover_show_task.take();
6658 let Some(blame) = editor.blame.as_ref() else {
6659 return;
6660 };
6661 let blame = blame.read(cx);
6662 let details = blame.details_for_entry(&blame_entry);
6663 let markdown = cx.new(|cx| {
6664 Markdown::new(
6665 details
6666 .as_ref()
6667 .map(|message| message.message.clone())
6668 .unwrap_or_default(),
6669 None,
6670 None,
6671 cx,
6672 )
6673 });
6674 editor.inline_blame_popover = Some(InlineBlamePopover {
6675 position,
6676 hide_task: None,
6677 popover_bounds: None,
6678 popover_state: InlineBlamePopoverState {
6679 scroll_handle: ScrollHandle::new(),
6680 commit_message: details,
6681 markdown,
6682 },
6683 keyboard_grace: ignore_timeout,
6684 });
6685 cx.notify();
6686 })
6687 .ok();
6688 });
6689 self.inline_blame_popover_show_task = Some(show_task);
6690 }
6691 }
6692
6693 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6694 self.inline_blame_popover_show_task.take();
6695 if let Some(state) = &mut self.inline_blame_popover {
6696 let hide_task = cx.spawn(async move |editor, cx| {
6697 cx.background_executor()
6698 .timer(std::time::Duration::from_millis(100))
6699 .await;
6700 editor
6701 .update(cx, |editor, cx| {
6702 editor.inline_blame_popover.take();
6703 cx.notify();
6704 })
6705 .ok();
6706 });
6707 state.hide_task = Some(hide_task);
6708 }
6709 }
6710
6711 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6712 if self.pending_rename.is_some() {
6713 return None;
6714 }
6715
6716 let provider = self.semantics_provider.clone()?;
6717 let buffer = self.buffer.read(cx);
6718 let newest_selection = self.selections.newest_anchor().clone();
6719 let cursor_position = newest_selection.head();
6720 let (cursor_buffer, cursor_buffer_position) =
6721 buffer.text_anchor_for_position(cursor_position, cx)?;
6722 let (tail_buffer, tail_buffer_position) =
6723 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6724 if cursor_buffer != tail_buffer {
6725 return None;
6726 }
6727
6728 let snapshot = cursor_buffer.read(cx).snapshot();
6729 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6730 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6731 if start_word_range != end_word_range {
6732 self.document_highlights_task.take();
6733 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6734 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6735 return None;
6736 }
6737
6738 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6739 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6740 cx.background_executor()
6741 .timer(Duration::from_millis(debounce))
6742 .await;
6743
6744 let highlights = if let Some(highlights) = cx
6745 .update(|cx| {
6746 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6747 })
6748 .ok()
6749 .flatten()
6750 {
6751 highlights.await.log_err()
6752 } else {
6753 None
6754 };
6755
6756 if let Some(highlights) = highlights {
6757 this.update(cx, |this, cx| {
6758 if this.pending_rename.is_some() {
6759 return;
6760 }
6761
6762 let buffer = this.buffer.read(cx);
6763 if buffer
6764 .text_anchor_for_position(cursor_position, cx)
6765 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6766 {
6767 return;
6768 }
6769
6770 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6771 let mut write_ranges = Vec::new();
6772 let mut read_ranges = Vec::new();
6773 for highlight in highlights {
6774 let buffer_id = cursor_buffer.read(cx).remote_id();
6775 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6776 {
6777 let start = highlight
6778 .range
6779 .start
6780 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6781 let end = highlight
6782 .range
6783 .end
6784 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6785 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6786 continue;
6787 }
6788
6789 let range = Anchor {
6790 buffer_id: Some(buffer_id),
6791 excerpt_id,
6792 text_anchor: start,
6793 diff_base_anchor: None,
6794 }..Anchor {
6795 buffer_id: Some(buffer_id),
6796 excerpt_id,
6797 text_anchor: end,
6798 diff_base_anchor: None,
6799 };
6800 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6801 write_ranges.push(range);
6802 } else {
6803 read_ranges.push(range);
6804 }
6805 }
6806 }
6807
6808 this.highlight_background::<DocumentHighlightRead>(
6809 &read_ranges,
6810 |theme| theme.colors().editor_document_highlight_read_background,
6811 cx,
6812 );
6813 this.highlight_background::<DocumentHighlightWrite>(
6814 &write_ranges,
6815 |theme| theme.colors().editor_document_highlight_write_background,
6816 cx,
6817 );
6818 cx.notify();
6819 })
6820 .log_err();
6821 }
6822 }));
6823 None
6824 }
6825
6826 fn prepare_highlight_query_from_selection(
6827 &mut self,
6828 cx: &mut Context<Editor>,
6829 ) -> Option<(String, Range<Anchor>)> {
6830 if matches!(self.mode, EditorMode::SingleLine) {
6831 return None;
6832 }
6833 if !EditorSettings::get_global(cx).selection_highlight {
6834 return None;
6835 }
6836 if self.selections.count() != 1 || self.selections.line_mode {
6837 return None;
6838 }
6839 let selection = self.selections.newest::<Point>(cx);
6840 if selection.is_empty() || selection.start.row != selection.end.row {
6841 return None;
6842 }
6843 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6844 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6845 let query = multi_buffer_snapshot
6846 .text_for_range(selection_anchor_range.clone())
6847 .collect::<String>();
6848 if query.trim().is_empty() {
6849 return None;
6850 }
6851 Some((query, selection_anchor_range))
6852 }
6853
6854 fn update_selection_occurrence_highlights(
6855 &mut self,
6856 query_text: String,
6857 query_range: Range<Anchor>,
6858 multi_buffer_range_to_query: Range<Point>,
6859 use_debounce: bool,
6860 window: &mut Window,
6861 cx: &mut Context<Editor>,
6862 ) -> Task<()> {
6863 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6864 cx.spawn_in(window, async move |editor, cx| {
6865 if use_debounce {
6866 cx.background_executor()
6867 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6868 .await;
6869 }
6870 let match_task = cx.background_spawn(async move {
6871 let buffer_ranges = multi_buffer_snapshot
6872 .range_to_buffer_ranges(multi_buffer_range_to_query)
6873 .into_iter()
6874 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6875 let mut match_ranges = Vec::new();
6876 let Ok(regex) = project::search::SearchQuery::text(
6877 query_text.clone(),
6878 false,
6879 false,
6880 false,
6881 Default::default(),
6882 Default::default(),
6883 false,
6884 None,
6885 ) else {
6886 return Vec::default();
6887 };
6888 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6889 match_ranges.extend(
6890 regex
6891 .search(buffer_snapshot, Some(search_range.clone()))
6892 .await
6893 .into_iter()
6894 .filter_map(|match_range| {
6895 let match_start = buffer_snapshot
6896 .anchor_after(search_range.start + match_range.start);
6897 let match_end = buffer_snapshot
6898 .anchor_before(search_range.start + match_range.end);
6899 let match_anchor_range = Anchor::range_in_buffer(
6900 excerpt_id,
6901 buffer_snapshot.remote_id(),
6902 match_start..match_end,
6903 );
6904 (match_anchor_range != query_range).then_some(match_anchor_range)
6905 }),
6906 );
6907 }
6908 match_ranges
6909 });
6910 let match_ranges = match_task.await;
6911 editor
6912 .update_in(cx, |editor, _, cx| {
6913 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6914 if !match_ranges.is_empty() {
6915 editor.highlight_background::<SelectedTextHighlight>(
6916 &match_ranges,
6917 |theme| theme.colors().editor_document_highlight_bracket_background,
6918 cx,
6919 )
6920 }
6921 })
6922 .log_err();
6923 })
6924 }
6925
6926 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6927 struct NewlineFold;
6928 let type_id = std::any::TypeId::of::<NewlineFold>();
6929 if !self.mode.is_single_line() {
6930 return;
6931 }
6932 let snapshot = self.snapshot(window, cx);
6933 if snapshot.buffer_snapshot.max_point().row == 0 {
6934 return;
6935 }
6936 let task = cx.background_spawn(async move {
6937 let new_newlines = snapshot
6938 .buffer_chars_at(0)
6939 .filter_map(|(c, i)| {
6940 if c == '\n' {
6941 Some(
6942 snapshot.buffer_snapshot.anchor_after(i)
6943 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6944 )
6945 } else {
6946 None
6947 }
6948 })
6949 .collect::<Vec<_>>();
6950 let existing_newlines = snapshot
6951 .folds_in_range(0..snapshot.buffer_snapshot.len())
6952 .filter_map(|fold| {
6953 if fold.placeholder.type_tag == Some(type_id) {
6954 Some(fold.range.start..fold.range.end)
6955 } else {
6956 None
6957 }
6958 })
6959 .collect::<Vec<_>>();
6960
6961 (new_newlines, existing_newlines)
6962 });
6963 self.folding_newlines = cx.spawn(async move |this, cx| {
6964 let (new_newlines, existing_newlines) = task.await;
6965 if new_newlines == existing_newlines {
6966 return;
6967 }
6968 let placeholder = FoldPlaceholder {
6969 render: Arc::new(move |_, _, cx| {
6970 div()
6971 .bg(cx.theme().status().hint_background)
6972 .border_b_1()
6973 .size_full()
6974 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6975 .border_color(cx.theme().status().hint)
6976 .child("\\n")
6977 .into_any()
6978 }),
6979 constrain_width: false,
6980 merge_adjacent: false,
6981 type_tag: Some(type_id),
6982 };
6983 let creases = new_newlines
6984 .into_iter()
6985 .map(|range| Crease::simple(range, placeholder.clone()))
6986 .collect();
6987 this.update(cx, |this, cx| {
6988 this.display_map.update(cx, |display_map, cx| {
6989 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6990 display_map.fold(creases, cx);
6991 });
6992 })
6993 .ok();
6994 });
6995 }
6996
6997 fn refresh_selected_text_highlights(
6998 &mut self,
6999 on_buffer_edit: bool,
7000 window: &mut Window,
7001 cx: &mut Context<Editor>,
7002 ) {
7003 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7004 else {
7005 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7006 self.quick_selection_highlight_task.take();
7007 self.debounced_selection_highlight_task.take();
7008 return;
7009 };
7010 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7011 if on_buffer_edit
7012 || self
7013 .quick_selection_highlight_task
7014 .as_ref()
7015 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7016 {
7017 let multi_buffer_visible_start = self
7018 .scroll_manager
7019 .anchor()
7020 .anchor
7021 .to_point(&multi_buffer_snapshot);
7022 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7023 multi_buffer_visible_start
7024 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7025 Bias::Left,
7026 );
7027 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7028 self.quick_selection_highlight_task = Some((
7029 query_range.clone(),
7030 self.update_selection_occurrence_highlights(
7031 query_text.clone(),
7032 query_range.clone(),
7033 multi_buffer_visible_range,
7034 false,
7035 window,
7036 cx,
7037 ),
7038 ));
7039 }
7040 if on_buffer_edit
7041 || self
7042 .debounced_selection_highlight_task
7043 .as_ref()
7044 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7045 {
7046 let multi_buffer_start = multi_buffer_snapshot
7047 .anchor_before(0)
7048 .to_point(&multi_buffer_snapshot);
7049 let multi_buffer_end = multi_buffer_snapshot
7050 .anchor_after(multi_buffer_snapshot.len())
7051 .to_point(&multi_buffer_snapshot);
7052 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7053 self.debounced_selection_highlight_task = Some((
7054 query_range.clone(),
7055 self.update_selection_occurrence_highlights(
7056 query_text,
7057 query_range,
7058 multi_buffer_full_range,
7059 true,
7060 window,
7061 cx,
7062 ),
7063 ));
7064 }
7065 }
7066
7067 pub fn refresh_edit_prediction(
7068 &mut self,
7069 debounce: bool,
7070 user_requested: bool,
7071 window: &mut Window,
7072 cx: &mut Context<Self>,
7073 ) -> Option<()> {
7074 if DisableAiSettings::get_global(cx).disable_ai {
7075 return None;
7076 }
7077
7078 let provider = self.edit_prediction_provider()?;
7079 let cursor = self.selections.newest_anchor().head();
7080 let (buffer, cursor_buffer_position) =
7081 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7082
7083 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7084 self.discard_edit_prediction(false, cx);
7085 return None;
7086 }
7087
7088 if !user_requested
7089 && (!self.should_show_edit_predictions()
7090 || !self.is_focused(window)
7091 || buffer.read(cx).is_empty())
7092 {
7093 self.discard_edit_prediction(false, cx);
7094 return None;
7095 }
7096
7097 self.update_visible_edit_prediction(window, cx);
7098 provider.refresh(
7099 self.project.clone(),
7100 buffer,
7101 cursor_buffer_position,
7102 debounce,
7103 cx,
7104 );
7105 Some(())
7106 }
7107
7108 fn show_edit_predictions_in_menu(&self) -> bool {
7109 match self.edit_prediction_settings {
7110 EditPredictionSettings::Disabled => false,
7111 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7112 }
7113 }
7114
7115 pub fn edit_predictions_enabled(&self) -> bool {
7116 match self.edit_prediction_settings {
7117 EditPredictionSettings::Disabled => false,
7118 EditPredictionSettings::Enabled { .. } => true,
7119 }
7120 }
7121
7122 fn edit_prediction_requires_modifier(&self) -> bool {
7123 match self.edit_prediction_settings {
7124 EditPredictionSettings::Disabled => false,
7125 EditPredictionSettings::Enabled {
7126 preview_requires_modifier,
7127 ..
7128 } => preview_requires_modifier,
7129 }
7130 }
7131
7132 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7133 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7134 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7135 self.discard_edit_prediction(false, cx);
7136 } else {
7137 let selection = self.selections.newest_anchor();
7138 let cursor = selection.head();
7139
7140 if let Some((buffer, cursor_buffer_position)) =
7141 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7142 {
7143 self.edit_prediction_settings =
7144 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7145 }
7146 }
7147 }
7148
7149 fn edit_prediction_settings_at_position(
7150 &self,
7151 buffer: &Entity<Buffer>,
7152 buffer_position: language::Anchor,
7153 cx: &App,
7154 ) -> EditPredictionSettings {
7155 if !self.mode.is_full()
7156 || !self.show_edit_predictions_override.unwrap_or(true)
7157 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7158 {
7159 return EditPredictionSettings::Disabled;
7160 }
7161
7162 let buffer = buffer.read(cx);
7163
7164 let file = buffer.file();
7165
7166 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7167 return EditPredictionSettings::Disabled;
7168 };
7169
7170 let by_provider = matches!(
7171 self.menu_edit_predictions_policy,
7172 MenuEditPredictionsPolicy::ByProvider
7173 );
7174
7175 let show_in_menu = by_provider
7176 && self
7177 .edit_prediction_provider
7178 .as_ref()
7179 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7180
7181 let preview_requires_modifier =
7182 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7183
7184 EditPredictionSettings::Enabled {
7185 show_in_menu,
7186 preview_requires_modifier,
7187 }
7188 }
7189
7190 fn should_show_edit_predictions(&self) -> bool {
7191 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7192 }
7193
7194 pub fn edit_prediction_preview_is_active(&self) -> bool {
7195 matches!(
7196 self.edit_prediction_preview,
7197 EditPredictionPreview::Active { .. }
7198 )
7199 }
7200
7201 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7202 let cursor = self.selections.newest_anchor().head();
7203 if let Some((buffer, cursor_position)) =
7204 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7205 {
7206 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7207 } else {
7208 false
7209 }
7210 }
7211
7212 pub fn supports_minimap(&self, cx: &App) -> bool {
7213 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7214 }
7215
7216 fn edit_predictions_enabled_in_buffer(
7217 &self,
7218 buffer: &Entity<Buffer>,
7219 buffer_position: language::Anchor,
7220 cx: &App,
7221 ) -> bool {
7222 maybe!({
7223 if self.read_only(cx) {
7224 return Some(false);
7225 }
7226 let provider = self.edit_prediction_provider()?;
7227 if !provider.is_enabled(buffer, buffer_position, cx) {
7228 return Some(false);
7229 }
7230 let buffer = buffer.read(cx);
7231 let Some(file) = buffer.file() else {
7232 return Some(true);
7233 };
7234 let settings = all_language_settings(Some(file), cx);
7235 Some(settings.edit_predictions_enabled_for_file(file, cx))
7236 })
7237 .unwrap_or(false)
7238 }
7239
7240 fn cycle_edit_prediction(
7241 &mut self,
7242 direction: Direction,
7243 window: &mut Window,
7244 cx: &mut Context<Self>,
7245 ) -> Option<()> {
7246 let provider = self.edit_prediction_provider()?;
7247 let cursor = self.selections.newest_anchor().head();
7248 let (buffer, cursor_buffer_position) =
7249 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7250 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7251 return None;
7252 }
7253
7254 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7255 self.update_visible_edit_prediction(window, cx);
7256
7257 Some(())
7258 }
7259
7260 pub fn show_edit_prediction(
7261 &mut self,
7262 _: &ShowEditPrediction,
7263 window: &mut Window,
7264 cx: &mut Context<Self>,
7265 ) {
7266 if !self.has_active_edit_prediction() {
7267 self.refresh_edit_prediction(false, true, window, cx);
7268 return;
7269 }
7270
7271 self.update_visible_edit_prediction(window, cx);
7272 }
7273
7274 pub fn display_cursor_names(
7275 &mut self,
7276 _: &DisplayCursorNames,
7277 window: &mut Window,
7278 cx: &mut Context<Self>,
7279 ) {
7280 self.show_cursor_names(window, cx);
7281 }
7282
7283 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7284 self.show_cursor_names = true;
7285 cx.notify();
7286 cx.spawn_in(window, async move |this, cx| {
7287 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7288 this.update(cx, |this, cx| {
7289 this.show_cursor_names = false;
7290 cx.notify()
7291 })
7292 .ok()
7293 })
7294 .detach();
7295 }
7296
7297 pub fn next_edit_prediction(
7298 &mut self,
7299 _: &NextEditPrediction,
7300 window: &mut Window,
7301 cx: &mut Context<Self>,
7302 ) {
7303 if self.has_active_edit_prediction() {
7304 self.cycle_edit_prediction(Direction::Next, window, cx);
7305 } else {
7306 let is_copilot_disabled = self
7307 .refresh_edit_prediction(false, true, window, cx)
7308 .is_none();
7309 if is_copilot_disabled {
7310 cx.propagate();
7311 }
7312 }
7313 }
7314
7315 pub fn previous_edit_prediction(
7316 &mut self,
7317 _: &PreviousEditPrediction,
7318 window: &mut Window,
7319 cx: &mut Context<Self>,
7320 ) {
7321 if self.has_active_edit_prediction() {
7322 self.cycle_edit_prediction(Direction::Prev, window, cx);
7323 } else {
7324 let is_copilot_disabled = self
7325 .refresh_edit_prediction(false, true, window, cx)
7326 .is_none();
7327 if is_copilot_disabled {
7328 cx.propagate();
7329 }
7330 }
7331 }
7332
7333 pub fn accept_edit_prediction(
7334 &mut self,
7335 _: &AcceptEditPrediction,
7336 window: &mut Window,
7337 cx: &mut Context<Self>,
7338 ) {
7339 if self.show_edit_predictions_in_menu() {
7340 self.hide_context_menu(window, cx);
7341 }
7342
7343 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7344 return;
7345 };
7346
7347 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7348
7349 match &active_edit_prediction.completion {
7350 EditPrediction::Move { target, .. } => {
7351 let target = *target;
7352
7353 if let Some(position_map) = &self.last_position_map {
7354 if position_map
7355 .visible_row_range
7356 .contains(&target.to_display_point(&position_map.snapshot).row())
7357 || !self.edit_prediction_requires_modifier()
7358 {
7359 self.unfold_ranges(&[target..target], true, false, cx);
7360 // Note that this is also done in vim's handler of the Tab action.
7361 self.change_selections(
7362 SelectionEffects::scroll(Autoscroll::newest()),
7363 window,
7364 cx,
7365 |selections| {
7366 selections.select_anchor_ranges([target..target]);
7367 },
7368 );
7369 self.clear_row_highlights::<EditPredictionPreview>();
7370
7371 self.edit_prediction_preview
7372 .set_previous_scroll_position(None);
7373 } else {
7374 self.edit_prediction_preview
7375 .set_previous_scroll_position(Some(
7376 position_map.snapshot.scroll_anchor,
7377 ));
7378
7379 self.highlight_rows::<EditPredictionPreview>(
7380 target..target,
7381 cx.theme().colors().editor_highlighted_line_background,
7382 RowHighlightOptions {
7383 autoscroll: true,
7384 ..Default::default()
7385 },
7386 cx,
7387 );
7388 self.request_autoscroll(Autoscroll::fit(), cx);
7389 }
7390 }
7391 }
7392 EditPrediction::Edit { edits, .. } => {
7393 if let Some(provider) = self.edit_prediction_provider() {
7394 provider.accept(cx);
7395 }
7396
7397 // Store the transaction ID and selections before applying the edit
7398 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7399
7400 let snapshot = self.buffer.read(cx).snapshot(cx);
7401 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7402
7403 self.buffer.update(cx, |buffer, cx| {
7404 buffer.edit(edits.iter().cloned(), None, cx)
7405 });
7406
7407 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7408 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7409 });
7410
7411 let selections = self.selections.disjoint_anchors();
7412 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7413 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7414 if has_new_transaction {
7415 self.selection_history
7416 .insert_transaction(transaction_id_now, selections);
7417 }
7418 }
7419
7420 self.update_visible_edit_prediction(window, cx);
7421 if self.active_edit_prediction.is_none() {
7422 self.refresh_edit_prediction(true, true, window, cx);
7423 }
7424
7425 cx.notify();
7426 }
7427 }
7428
7429 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7430 }
7431
7432 pub fn accept_partial_edit_prediction(
7433 &mut self,
7434 _: &AcceptPartialEditPrediction,
7435 window: &mut Window,
7436 cx: &mut Context<Self>,
7437 ) {
7438 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7439 return;
7440 };
7441 if self.selections.count() != 1 {
7442 return;
7443 }
7444
7445 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7446
7447 match &active_edit_prediction.completion {
7448 EditPrediction::Move { target, .. } => {
7449 let target = *target;
7450 self.change_selections(
7451 SelectionEffects::scroll(Autoscroll::newest()),
7452 window,
7453 cx,
7454 |selections| {
7455 selections.select_anchor_ranges([target..target]);
7456 },
7457 );
7458 }
7459 EditPrediction::Edit { edits, .. } => {
7460 // Find an insertion that starts at the cursor position.
7461 let snapshot = self.buffer.read(cx).snapshot(cx);
7462 let cursor_offset = self.selections.newest::<usize>(cx).head();
7463 let insertion = edits.iter().find_map(|(range, text)| {
7464 let range = range.to_offset(&snapshot);
7465 if range.is_empty() && range.start == cursor_offset {
7466 Some(text)
7467 } else {
7468 None
7469 }
7470 });
7471
7472 if let Some(text) = insertion {
7473 let mut partial_completion = text
7474 .chars()
7475 .by_ref()
7476 .take_while(|c| c.is_alphabetic())
7477 .collect::<String>();
7478 if partial_completion.is_empty() {
7479 partial_completion = text
7480 .chars()
7481 .by_ref()
7482 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7483 .collect::<String>();
7484 }
7485
7486 cx.emit(EditorEvent::InputHandled {
7487 utf16_range_to_replace: None,
7488 text: partial_completion.clone().into(),
7489 });
7490
7491 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7492
7493 self.refresh_edit_prediction(true, true, window, cx);
7494 cx.notify();
7495 } else {
7496 self.accept_edit_prediction(&Default::default(), window, cx);
7497 }
7498 }
7499 }
7500 }
7501
7502 fn discard_edit_prediction(
7503 &mut self,
7504 should_report_edit_prediction_event: bool,
7505 cx: &mut Context<Self>,
7506 ) -> bool {
7507 if should_report_edit_prediction_event {
7508 let completion_id = self
7509 .active_edit_prediction
7510 .as_ref()
7511 .and_then(|active_completion| active_completion.completion_id.clone());
7512
7513 self.report_edit_prediction_event(completion_id, false, cx);
7514 }
7515
7516 if let Some(provider) = self.edit_prediction_provider() {
7517 provider.discard(cx);
7518 }
7519
7520 self.take_active_edit_prediction(cx)
7521 }
7522
7523 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7524 let Some(provider) = self.edit_prediction_provider() else {
7525 return;
7526 };
7527
7528 let Some((_, buffer, _)) = self
7529 .buffer
7530 .read(cx)
7531 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7532 else {
7533 return;
7534 };
7535
7536 let extension = buffer
7537 .read(cx)
7538 .file()
7539 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7540
7541 let event_type = match accepted {
7542 true => "Edit Prediction Accepted",
7543 false => "Edit Prediction Discarded",
7544 };
7545 telemetry::event!(
7546 event_type,
7547 provider = provider.name(),
7548 prediction_id = id,
7549 suggestion_accepted = accepted,
7550 file_extension = extension,
7551 );
7552 }
7553
7554 pub fn has_active_edit_prediction(&self) -> bool {
7555 self.active_edit_prediction.is_some()
7556 }
7557
7558 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7559 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7560 return false;
7561 };
7562
7563 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7564 self.clear_highlights::<EditPredictionHighlight>(cx);
7565 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7566 true
7567 }
7568
7569 /// Returns true when we're displaying the edit prediction popover below the cursor
7570 /// like we are not previewing and the LSP autocomplete menu is visible
7571 /// or we are in `when_holding_modifier` mode.
7572 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7573 if self.edit_prediction_preview_is_active()
7574 || !self.show_edit_predictions_in_menu()
7575 || !self.edit_predictions_enabled()
7576 {
7577 return false;
7578 }
7579
7580 if self.has_visible_completions_menu() {
7581 return true;
7582 }
7583
7584 has_completion && self.edit_prediction_requires_modifier()
7585 }
7586
7587 fn handle_modifiers_changed(
7588 &mut self,
7589 modifiers: Modifiers,
7590 position_map: &PositionMap,
7591 window: &mut Window,
7592 cx: &mut Context<Self>,
7593 ) {
7594 if self.show_edit_predictions_in_menu() {
7595 self.update_edit_prediction_preview(&modifiers, window, cx);
7596 }
7597
7598 self.update_selection_mode(&modifiers, position_map, window, cx);
7599
7600 let mouse_position = window.mouse_position();
7601 if !position_map.text_hitbox.is_hovered(window) {
7602 return;
7603 }
7604
7605 self.update_hovered_link(
7606 position_map.point_for_position(mouse_position),
7607 &position_map.snapshot,
7608 modifiers,
7609 window,
7610 cx,
7611 )
7612 }
7613
7614 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7615 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7616 if invert {
7617 match multi_cursor_setting {
7618 MultiCursorModifier::Alt => modifiers.alt,
7619 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7620 }
7621 } else {
7622 match multi_cursor_setting {
7623 MultiCursorModifier::Alt => modifiers.secondary(),
7624 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7625 }
7626 }
7627 }
7628
7629 fn columnar_selection_mode(
7630 modifiers: &Modifiers,
7631 cx: &mut Context<Self>,
7632 ) -> Option<ColumnarMode> {
7633 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7634 if Self::multi_cursor_modifier(false, modifiers, cx) {
7635 Some(ColumnarMode::FromMouse)
7636 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7637 Some(ColumnarMode::FromSelection)
7638 } else {
7639 None
7640 }
7641 } else {
7642 None
7643 }
7644 }
7645
7646 fn update_selection_mode(
7647 &mut self,
7648 modifiers: &Modifiers,
7649 position_map: &PositionMap,
7650 window: &mut Window,
7651 cx: &mut Context<Self>,
7652 ) {
7653 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7654 return;
7655 };
7656 if self.selections.pending.is_none() {
7657 return;
7658 }
7659
7660 let mouse_position = window.mouse_position();
7661 let point_for_position = position_map.point_for_position(mouse_position);
7662 let position = point_for_position.previous_valid;
7663
7664 self.select(
7665 SelectPhase::BeginColumnar {
7666 position,
7667 reset: false,
7668 mode,
7669 goal_column: point_for_position.exact_unclipped.column(),
7670 },
7671 window,
7672 cx,
7673 );
7674 }
7675
7676 fn update_edit_prediction_preview(
7677 &mut self,
7678 modifiers: &Modifiers,
7679 window: &mut Window,
7680 cx: &mut Context<Self>,
7681 ) {
7682 let mut modifiers_held = false;
7683 if let Some(accept_keystroke) = self
7684 .accept_edit_prediction_keybind(false, window, cx)
7685 .keystroke()
7686 {
7687 modifiers_held = modifiers_held
7688 || (accept_keystroke.modifiers() == modifiers
7689 && accept_keystroke.modifiers().modified());
7690 };
7691 if let Some(accept_partial_keystroke) = self
7692 .accept_edit_prediction_keybind(true, window, cx)
7693 .keystroke()
7694 {
7695 modifiers_held = modifiers_held
7696 || (accept_partial_keystroke.modifiers() == modifiers
7697 && accept_partial_keystroke.modifiers().modified());
7698 }
7699
7700 if modifiers_held {
7701 if matches!(
7702 self.edit_prediction_preview,
7703 EditPredictionPreview::Inactive { .. }
7704 ) {
7705 self.edit_prediction_preview = EditPredictionPreview::Active {
7706 previous_scroll_position: None,
7707 since: Instant::now(),
7708 };
7709
7710 self.update_visible_edit_prediction(window, cx);
7711 cx.notify();
7712 }
7713 } else if let EditPredictionPreview::Active {
7714 previous_scroll_position,
7715 since,
7716 } = self.edit_prediction_preview
7717 {
7718 if let (Some(previous_scroll_position), Some(position_map)) =
7719 (previous_scroll_position, self.last_position_map.as_ref())
7720 {
7721 self.set_scroll_position(
7722 previous_scroll_position
7723 .scroll_position(&position_map.snapshot.display_snapshot),
7724 window,
7725 cx,
7726 );
7727 }
7728
7729 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7730 released_too_fast: since.elapsed() < Duration::from_millis(200),
7731 };
7732 self.clear_row_highlights::<EditPredictionPreview>();
7733 self.update_visible_edit_prediction(window, cx);
7734 cx.notify();
7735 }
7736 }
7737
7738 fn update_visible_edit_prediction(
7739 &mut self,
7740 _window: &mut Window,
7741 cx: &mut Context<Self>,
7742 ) -> Option<()> {
7743 if DisableAiSettings::get_global(cx).disable_ai {
7744 return None;
7745 }
7746
7747 let selection = self.selections.newest_anchor();
7748 let cursor = selection.head();
7749 let multibuffer = self.buffer.read(cx).snapshot(cx);
7750 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7751 let excerpt_id = cursor.excerpt_id;
7752
7753 let show_in_menu = self.show_edit_predictions_in_menu();
7754 let completions_menu_has_precedence = !show_in_menu
7755 && (self.context_menu.borrow().is_some()
7756 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7757
7758 if completions_menu_has_precedence
7759 || !offset_selection.is_empty()
7760 || self
7761 .active_edit_prediction
7762 .as_ref()
7763 .is_some_and(|completion| {
7764 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7765 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7766 !invalidation_range.contains(&offset_selection.head())
7767 })
7768 {
7769 self.discard_edit_prediction(false, cx);
7770 return None;
7771 }
7772
7773 self.take_active_edit_prediction(cx);
7774 let Some(provider) = self.edit_prediction_provider() else {
7775 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7776 return None;
7777 };
7778
7779 let (buffer, cursor_buffer_position) =
7780 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7781
7782 self.edit_prediction_settings =
7783 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7784
7785 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7786 self.discard_edit_prediction(false, cx);
7787 return None;
7788 };
7789
7790 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7791
7792 if self.edit_prediction_indent_conflict {
7793 let cursor_point = cursor.to_point(&multibuffer);
7794
7795 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7796
7797 if let Some((_, indent)) = indents.iter().next()
7798 && indent.len == cursor_point.column
7799 {
7800 self.edit_prediction_indent_conflict = false;
7801 }
7802 }
7803
7804 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7805 let edits = edit_prediction
7806 .edits
7807 .into_iter()
7808 .flat_map(|(range, new_text)| {
7809 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7810 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7811 Some((start..end, new_text))
7812 })
7813 .collect::<Vec<_>>();
7814 if edits.is_empty() {
7815 return None;
7816 }
7817
7818 let first_edit_start = edits.first().unwrap().0.start;
7819 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7820 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7821
7822 let last_edit_end = edits.last().unwrap().0.end;
7823 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7824 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7825
7826 let cursor_row = cursor.to_point(&multibuffer).row;
7827
7828 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7829
7830 let mut inlay_ids = Vec::new();
7831 let invalidation_row_range;
7832 let move_invalidation_row_range = if cursor_row < edit_start_row {
7833 Some(cursor_row..edit_end_row)
7834 } else if cursor_row > edit_end_row {
7835 Some(edit_start_row..cursor_row)
7836 } else {
7837 None
7838 };
7839 let supports_jump = self
7840 .edit_prediction_provider
7841 .as_ref()
7842 .map(|provider| provider.provider.supports_jump_to_edit())
7843 .unwrap_or(true);
7844
7845 let is_move = supports_jump
7846 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7847 let completion = if is_move {
7848 invalidation_row_range =
7849 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7850 let target = first_edit_start;
7851 EditPrediction::Move { target, snapshot }
7852 } else {
7853 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7854 && !self.edit_predictions_hidden_for_vim_mode;
7855
7856 if show_completions_in_buffer {
7857 if edits
7858 .iter()
7859 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7860 {
7861 let mut inlays = Vec::new();
7862 for (range, new_text) in &edits {
7863 let inlay = Inlay::edit_prediction(
7864 post_inc(&mut self.next_inlay_id),
7865 range.start,
7866 new_text.as_str(),
7867 );
7868 inlay_ids.push(inlay.id);
7869 inlays.push(inlay);
7870 }
7871
7872 self.splice_inlays(&[], inlays, cx);
7873 } else {
7874 let background_color = cx.theme().status().deleted_background;
7875 self.highlight_text::<EditPredictionHighlight>(
7876 edits.iter().map(|(range, _)| range.clone()).collect(),
7877 HighlightStyle {
7878 background_color: Some(background_color),
7879 ..Default::default()
7880 },
7881 cx,
7882 );
7883 }
7884 }
7885
7886 invalidation_row_range = edit_start_row..edit_end_row;
7887
7888 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7889 if provider.show_tab_accept_marker() {
7890 EditDisplayMode::TabAccept
7891 } else {
7892 EditDisplayMode::Inline
7893 }
7894 } else {
7895 EditDisplayMode::DiffPopover
7896 };
7897
7898 EditPrediction::Edit {
7899 edits,
7900 edit_preview: edit_prediction.edit_preview,
7901 display_mode,
7902 snapshot,
7903 }
7904 };
7905
7906 let invalidation_range = multibuffer
7907 .anchor_before(Point::new(invalidation_row_range.start, 0))
7908 ..multibuffer.anchor_after(Point::new(
7909 invalidation_row_range.end,
7910 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7911 ));
7912
7913 self.stale_edit_prediction_in_menu = None;
7914 self.active_edit_prediction = Some(EditPredictionState {
7915 inlay_ids,
7916 completion,
7917 completion_id: edit_prediction.id,
7918 invalidation_range,
7919 });
7920
7921 cx.notify();
7922
7923 Some(())
7924 }
7925
7926 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7927 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7928 }
7929
7930 fn clear_tasks(&mut self) {
7931 self.tasks.clear()
7932 }
7933
7934 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7935 if self.tasks.insert(key, value).is_some() {
7936 // This case should hopefully be rare, but just in case...
7937 log::error!(
7938 "multiple different run targets found on a single line, only the last target will be rendered"
7939 )
7940 }
7941 }
7942
7943 /// Get all display points of breakpoints that will be rendered within editor
7944 ///
7945 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7946 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7947 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7948 fn active_breakpoints(
7949 &self,
7950 range: Range<DisplayRow>,
7951 window: &mut Window,
7952 cx: &mut Context<Self>,
7953 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7954 let mut breakpoint_display_points = HashMap::default();
7955
7956 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7957 return breakpoint_display_points;
7958 };
7959
7960 let snapshot = self.snapshot(window, cx);
7961
7962 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7963 let Some(project) = self.project() else {
7964 return breakpoint_display_points;
7965 };
7966
7967 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7968 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7969
7970 for (buffer_snapshot, range, excerpt_id) in
7971 multi_buffer_snapshot.range_to_buffer_ranges(range)
7972 {
7973 let Some(buffer) = project
7974 .read(cx)
7975 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7976 else {
7977 continue;
7978 };
7979 let breakpoints = breakpoint_store.read(cx).breakpoints(
7980 &buffer,
7981 Some(
7982 buffer_snapshot.anchor_before(range.start)
7983 ..buffer_snapshot.anchor_after(range.end),
7984 ),
7985 buffer_snapshot,
7986 cx,
7987 );
7988 for (breakpoint, state) in breakpoints {
7989 let multi_buffer_anchor =
7990 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7991 let position = multi_buffer_anchor
7992 .to_point(multi_buffer_snapshot)
7993 .to_display_point(&snapshot);
7994
7995 breakpoint_display_points.insert(
7996 position.row(),
7997 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7998 );
7999 }
8000 }
8001
8002 breakpoint_display_points
8003 }
8004
8005 fn breakpoint_context_menu(
8006 &self,
8007 anchor: Anchor,
8008 window: &mut Window,
8009 cx: &mut Context<Self>,
8010 ) -> Entity<ui::ContextMenu> {
8011 let weak_editor = cx.weak_entity();
8012 let focus_handle = self.focus_handle(cx);
8013
8014 let row = self
8015 .buffer
8016 .read(cx)
8017 .snapshot(cx)
8018 .summary_for_anchor::<Point>(&anchor)
8019 .row;
8020
8021 let breakpoint = self
8022 .breakpoint_at_row(row, window, cx)
8023 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8024
8025 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8026 "Edit Log Breakpoint"
8027 } else {
8028 "Set Log Breakpoint"
8029 };
8030
8031 let condition_breakpoint_msg = if breakpoint
8032 .as_ref()
8033 .is_some_and(|bp| bp.1.condition.is_some())
8034 {
8035 "Edit Condition Breakpoint"
8036 } else {
8037 "Set Condition Breakpoint"
8038 };
8039
8040 let hit_condition_breakpoint_msg = if breakpoint
8041 .as_ref()
8042 .is_some_and(|bp| bp.1.hit_condition.is_some())
8043 {
8044 "Edit Hit Condition Breakpoint"
8045 } else {
8046 "Set Hit Condition Breakpoint"
8047 };
8048
8049 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8050 "Unset Breakpoint"
8051 } else {
8052 "Set Breakpoint"
8053 };
8054
8055 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8056
8057 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8058 BreakpointState::Enabled => Some("Disable"),
8059 BreakpointState::Disabled => Some("Enable"),
8060 });
8061
8062 let (anchor, breakpoint) =
8063 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8064
8065 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8066 menu.on_blur_subscription(Subscription::new(|| {}))
8067 .context(focus_handle)
8068 .when(run_to_cursor, |this| {
8069 let weak_editor = weak_editor.clone();
8070 this.entry("Run to cursor", None, move |window, cx| {
8071 weak_editor
8072 .update(cx, |editor, cx| {
8073 editor.change_selections(
8074 SelectionEffects::no_scroll(),
8075 window,
8076 cx,
8077 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8078 );
8079 })
8080 .ok();
8081
8082 window.dispatch_action(Box::new(RunToCursor), cx);
8083 })
8084 .separator()
8085 })
8086 .when_some(toggle_state_msg, |this, msg| {
8087 this.entry(msg, None, {
8088 let weak_editor = weak_editor.clone();
8089 let breakpoint = breakpoint.clone();
8090 move |_window, cx| {
8091 weak_editor
8092 .update(cx, |this, cx| {
8093 this.edit_breakpoint_at_anchor(
8094 anchor,
8095 breakpoint.as_ref().clone(),
8096 BreakpointEditAction::InvertState,
8097 cx,
8098 );
8099 })
8100 .log_err();
8101 }
8102 })
8103 })
8104 .entry(set_breakpoint_msg, None, {
8105 let weak_editor = weak_editor.clone();
8106 let breakpoint = breakpoint.clone();
8107 move |_window, cx| {
8108 weak_editor
8109 .update(cx, |this, cx| {
8110 this.edit_breakpoint_at_anchor(
8111 anchor,
8112 breakpoint.as_ref().clone(),
8113 BreakpointEditAction::Toggle,
8114 cx,
8115 );
8116 })
8117 .log_err();
8118 }
8119 })
8120 .entry(log_breakpoint_msg, None, {
8121 let breakpoint = breakpoint.clone();
8122 let weak_editor = weak_editor.clone();
8123 move |window, cx| {
8124 weak_editor
8125 .update(cx, |this, cx| {
8126 this.add_edit_breakpoint_block(
8127 anchor,
8128 breakpoint.as_ref(),
8129 BreakpointPromptEditAction::Log,
8130 window,
8131 cx,
8132 );
8133 })
8134 .log_err();
8135 }
8136 })
8137 .entry(condition_breakpoint_msg, None, {
8138 let breakpoint = breakpoint.clone();
8139 let weak_editor = weak_editor.clone();
8140 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::Condition,
8147 window,
8148 cx,
8149 );
8150 })
8151 .log_err();
8152 }
8153 })
8154 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8155 weak_editor
8156 .update(cx, |this, cx| {
8157 this.add_edit_breakpoint_block(
8158 anchor,
8159 breakpoint.as_ref(),
8160 BreakpointPromptEditAction::HitCondition,
8161 window,
8162 cx,
8163 );
8164 })
8165 .log_err();
8166 })
8167 })
8168 }
8169
8170 fn render_breakpoint(
8171 &self,
8172 position: Anchor,
8173 row: DisplayRow,
8174 breakpoint: &Breakpoint,
8175 state: Option<BreakpointSessionState>,
8176 cx: &mut Context<Self>,
8177 ) -> IconButton {
8178 let is_rejected = state.is_some_and(|s| !s.verified);
8179 // Is it a breakpoint that shows up when hovering over gutter?
8180 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8181 (false, false),
8182 |PhantomBreakpointIndicator {
8183 is_active,
8184 display_row,
8185 collides_with_existing_breakpoint,
8186 }| {
8187 (
8188 is_active && display_row == row,
8189 collides_with_existing_breakpoint,
8190 )
8191 },
8192 );
8193
8194 let (color, icon) = {
8195 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8196 (false, false) => ui::IconName::DebugBreakpoint,
8197 (true, false) => ui::IconName::DebugLogBreakpoint,
8198 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8199 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8200 };
8201
8202 let color = if is_phantom {
8203 Color::Hint
8204 } else if is_rejected {
8205 Color::Disabled
8206 } else {
8207 Color::Debugger
8208 };
8209
8210 (color, icon)
8211 };
8212
8213 let breakpoint = Arc::from(breakpoint.clone());
8214
8215 let alt_as_text = gpui::Keystroke {
8216 modifiers: Modifiers::secondary_key(),
8217 ..Default::default()
8218 };
8219 let primary_action_text = if breakpoint.is_disabled() {
8220 "Enable breakpoint"
8221 } else if is_phantom && !collides_with_existing {
8222 "Set breakpoint"
8223 } else {
8224 "Unset breakpoint"
8225 };
8226 let focus_handle = self.focus_handle.clone();
8227
8228 let meta = if is_rejected {
8229 SharedString::from("No executable code is associated with this line.")
8230 } else if collides_with_existing && !breakpoint.is_disabled() {
8231 SharedString::from(format!(
8232 "{alt_as_text}-click to disable,\nright-click for more options."
8233 ))
8234 } else {
8235 SharedString::from("Right-click for more options.")
8236 };
8237 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8238 .icon_size(IconSize::XSmall)
8239 .size(ui::ButtonSize::None)
8240 .when(is_rejected, |this| {
8241 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8242 })
8243 .icon_color(color)
8244 .style(ButtonStyle::Transparent)
8245 .on_click(cx.listener({
8246 move |editor, event: &ClickEvent, window, cx| {
8247 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8248 BreakpointEditAction::InvertState
8249 } else {
8250 BreakpointEditAction::Toggle
8251 };
8252
8253 window.focus(&editor.focus_handle(cx));
8254 editor.edit_breakpoint_at_anchor(
8255 position,
8256 breakpoint.as_ref().clone(),
8257 edit_action,
8258 cx,
8259 );
8260 }
8261 }))
8262 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8263 editor.set_breakpoint_context_menu(
8264 row,
8265 Some(position),
8266 event.position(),
8267 window,
8268 cx,
8269 );
8270 }))
8271 .tooltip(move |window, cx| {
8272 Tooltip::with_meta_in(
8273 primary_action_text,
8274 Some(&ToggleBreakpoint),
8275 meta.clone(),
8276 &focus_handle,
8277 window,
8278 cx,
8279 )
8280 })
8281 }
8282
8283 fn build_tasks_context(
8284 project: &Entity<Project>,
8285 buffer: &Entity<Buffer>,
8286 buffer_row: u32,
8287 tasks: &Arc<RunnableTasks>,
8288 cx: &mut Context<Self>,
8289 ) -> Task<Option<task::TaskContext>> {
8290 let position = Point::new(buffer_row, tasks.column);
8291 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8292 let location = Location {
8293 buffer: buffer.clone(),
8294 range: range_start..range_start,
8295 };
8296 // Fill in the environmental variables from the tree-sitter captures
8297 let mut captured_task_variables = TaskVariables::default();
8298 for (capture_name, value) in tasks.extra_variables.clone() {
8299 captured_task_variables.insert(
8300 task::VariableName::Custom(capture_name.into()),
8301 value.clone(),
8302 );
8303 }
8304 project.update(cx, |project, cx| {
8305 project.task_store().update(cx, |task_store, cx| {
8306 task_store.task_context_for_location(captured_task_variables, location, cx)
8307 })
8308 })
8309 }
8310
8311 pub fn spawn_nearest_task(
8312 &mut self,
8313 action: &SpawnNearestTask,
8314 window: &mut Window,
8315 cx: &mut Context<Self>,
8316 ) {
8317 let Some((workspace, _)) = self.workspace.clone() else {
8318 return;
8319 };
8320 let Some(project) = self.project.clone() else {
8321 return;
8322 };
8323
8324 // Try to find a closest, enclosing node using tree-sitter that has a task
8325 let Some((buffer, buffer_row, tasks)) = self
8326 .find_enclosing_node_task(cx)
8327 // Or find the task that's closest in row-distance.
8328 .or_else(|| self.find_closest_task(cx))
8329 else {
8330 return;
8331 };
8332
8333 let reveal_strategy = action.reveal;
8334 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8335 cx.spawn_in(window, async move |_, cx| {
8336 let context = task_context.await?;
8337 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8338
8339 let resolved = &mut resolved_task.resolved;
8340 resolved.reveal = reveal_strategy;
8341
8342 workspace
8343 .update_in(cx, |workspace, window, cx| {
8344 workspace.schedule_resolved_task(
8345 task_source_kind,
8346 resolved_task,
8347 false,
8348 window,
8349 cx,
8350 );
8351 })
8352 .ok()
8353 })
8354 .detach();
8355 }
8356
8357 fn find_closest_task(
8358 &mut self,
8359 cx: &mut Context<Self>,
8360 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8361 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8362
8363 let ((buffer_id, row), tasks) = self
8364 .tasks
8365 .iter()
8366 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8367
8368 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8369 let tasks = Arc::new(tasks.to_owned());
8370 Some((buffer, *row, tasks))
8371 }
8372
8373 fn find_enclosing_node_task(
8374 &mut self,
8375 cx: &mut Context<Self>,
8376 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8377 let snapshot = self.buffer.read(cx).snapshot(cx);
8378 let offset = self.selections.newest::<usize>(cx).head();
8379 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8380 let buffer_id = excerpt.buffer().remote_id();
8381
8382 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8383 let mut cursor = layer.node().walk();
8384
8385 while cursor.goto_first_child_for_byte(offset).is_some() {
8386 if cursor.node().end_byte() == offset {
8387 cursor.goto_next_sibling();
8388 }
8389 }
8390
8391 // Ascend to the smallest ancestor that contains the range and has a task.
8392 loop {
8393 let node = cursor.node();
8394 let node_range = node.byte_range();
8395 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8396
8397 // Check if this node contains our offset
8398 if node_range.start <= offset && node_range.end >= offset {
8399 // If it contains offset, check for task
8400 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8401 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8402 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8403 }
8404 }
8405
8406 if !cursor.goto_parent() {
8407 break;
8408 }
8409 }
8410 None
8411 }
8412
8413 fn render_run_indicator(
8414 &self,
8415 _style: &EditorStyle,
8416 is_active: bool,
8417 row: DisplayRow,
8418 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8419 cx: &mut Context<Self>,
8420 ) -> IconButton {
8421 let color = Color::Muted;
8422 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8423
8424 IconButton::new(
8425 ("run_indicator", row.0 as usize),
8426 ui::IconName::PlayOutlined,
8427 )
8428 .shape(ui::IconButtonShape::Square)
8429 .icon_size(IconSize::XSmall)
8430 .icon_color(color)
8431 .toggle_state(is_active)
8432 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8433 let quick_launch = match e {
8434 ClickEvent::Keyboard(_) => true,
8435 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8436 };
8437
8438 window.focus(&editor.focus_handle(cx));
8439 editor.toggle_code_actions(
8440 &ToggleCodeActions {
8441 deployed_from: Some(CodeActionSource::RunMenu(row)),
8442 quick_launch,
8443 },
8444 window,
8445 cx,
8446 );
8447 }))
8448 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8449 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8450 }))
8451 }
8452
8453 pub fn context_menu_visible(&self) -> bool {
8454 !self.edit_prediction_preview_is_active()
8455 && self
8456 .context_menu
8457 .borrow()
8458 .as_ref()
8459 .is_some_and(|menu| menu.visible())
8460 }
8461
8462 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8463 self.context_menu
8464 .borrow()
8465 .as_ref()
8466 .map(|menu| menu.origin())
8467 }
8468
8469 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8470 self.context_menu_options = Some(options);
8471 }
8472
8473 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8474 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8475
8476 fn render_edit_prediction_popover(
8477 &mut self,
8478 text_bounds: &Bounds<Pixels>,
8479 content_origin: gpui::Point<Pixels>,
8480 right_margin: Pixels,
8481 editor_snapshot: &EditorSnapshot,
8482 visible_row_range: Range<DisplayRow>,
8483 scroll_top: f32,
8484 scroll_bottom: f32,
8485 line_layouts: &[LineWithInvisibles],
8486 line_height: Pixels,
8487 scroll_pixel_position: gpui::Point<Pixels>,
8488 newest_selection_head: Option<DisplayPoint>,
8489 editor_width: Pixels,
8490 style: &EditorStyle,
8491 window: &mut Window,
8492 cx: &mut App,
8493 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8494 if self.mode().is_minimap() {
8495 return None;
8496 }
8497 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8498
8499 if self.edit_prediction_visible_in_cursor_popover(true) {
8500 return None;
8501 }
8502
8503 match &active_edit_prediction.completion {
8504 EditPrediction::Move { target, .. } => {
8505 let target_display_point = target.to_display_point(editor_snapshot);
8506
8507 if self.edit_prediction_requires_modifier() {
8508 if !self.edit_prediction_preview_is_active() {
8509 return None;
8510 }
8511
8512 self.render_edit_prediction_modifier_jump_popover(
8513 text_bounds,
8514 content_origin,
8515 visible_row_range,
8516 line_layouts,
8517 line_height,
8518 scroll_pixel_position,
8519 newest_selection_head,
8520 target_display_point,
8521 window,
8522 cx,
8523 )
8524 } else {
8525 self.render_edit_prediction_eager_jump_popover(
8526 text_bounds,
8527 content_origin,
8528 editor_snapshot,
8529 visible_row_range,
8530 scroll_top,
8531 scroll_bottom,
8532 line_height,
8533 scroll_pixel_position,
8534 target_display_point,
8535 editor_width,
8536 window,
8537 cx,
8538 )
8539 }
8540 }
8541 EditPrediction::Edit {
8542 display_mode: EditDisplayMode::Inline,
8543 ..
8544 } => None,
8545 EditPrediction::Edit {
8546 display_mode: EditDisplayMode::TabAccept,
8547 edits,
8548 ..
8549 } => {
8550 let range = &edits.first()?.0;
8551 let target_display_point = range.end.to_display_point(editor_snapshot);
8552
8553 self.render_edit_prediction_end_of_line_popover(
8554 "Accept",
8555 editor_snapshot,
8556 visible_row_range,
8557 target_display_point,
8558 line_height,
8559 scroll_pixel_position,
8560 content_origin,
8561 editor_width,
8562 window,
8563 cx,
8564 )
8565 }
8566 EditPrediction::Edit {
8567 edits,
8568 edit_preview,
8569 display_mode: EditDisplayMode::DiffPopover,
8570 snapshot,
8571 } => self.render_edit_prediction_diff_popover(
8572 text_bounds,
8573 content_origin,
8574 right_margin,
8575 editor_snapshot,
8576 visible_row_range,
8577 line_layouts,
8578 line_height,
8579 scroll_pixel_position,
8580 newest_selection_head,
8581 editor_width,
8582 style,
8583 edits,
8584 edit_preview,
8585 snapshot,
8586 window,
8587 cx,
8588 ),
8589 }
8590 }
8591
8592 fn render_edit_prediction_modifier_jump_popover(
8593 &mut self,
8594 text_bounds: &Bounds<Pixels>,
8595 content_origin: gpui::Point<Pixels>,
8596 visible_row_range: Range<DisplayRow>,
8597 line_layouts: &[LineWithInvisibles],
8598 line_height: Pixels,
8599 scroll_pixel_position: gpui::Point<Pixels>,
8600 newest_selection_head: Option<DisplayPoint>,
8601 target_display_point: DisplayPoint,
8602 window: &mut Window,
8603 cx: &mut App,
8604 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8605 let scrolled_content_origin =
8606 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8607
8608 const SCROLL_PADDING_Y: Pixels = px(12.);
8609
8610 if target_display_point.row() < visible_row_range.start {
8611 return self.render_edit_prediction_scroll_popover(
8612 |_| SCROLL_PADDING_Y,
8613 IconName::ArrowUp,
8614 visible_row_range,
8615 line_layouts,
8616 newest_selection_head,
8617 scrolled_content_origin,
8618 window,
8619 cx,
8620 );
8621 } else if target_display_point.row() >= visible_row_range.end {
8622 return self.render_edit_prediction_scroll_popover(
8623 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8624 IconName::ArrowDown,
8625 visible_row_range,
8626 line_layouts,
8627 newest_selection_head,
8628 scrolled_content_origin,
8629 window,
8630 cx,
8631 );
8632 }
8633
8634 const POLE_WIDTH: Pixels = px(2.);
8635
8636 let line_layout =
8637 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8638 let target_column = target_display_point.column() as usize;
8639
8640 let target_x = line_layout.x_for_index(target_column);
8641 let target_y =
8642 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8643
8644 let flag_on_right = target_x < text_bounds.size.width / 2.;
8645
8646 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8647 border_color.l += 0.001;
8648
8649 let mut element = v_flex()
8650 .items_end()
8651 .when(flag_on_right, |el| el.items_start())
8652 .child(if flag_on_right {
8653 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8654 .rounded_bl(px(0.))
8655 .rounded_tl(px(0.))
8656 .border_l_2()
8657 .border_color(border_color)
8658 } else {
8659 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8660 .rounded_br(px(0.))
8661 .rounded_tr(px(0.))
8662 .border_r_2()
8663 .border_color(border_color)
8664 })
8665 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8666 .into_any();
8667
8668 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8669
8670 let mut origin = scrolled_content_origin + point(target_x, target_y)
8671 - point(
8672 if flag_on_right {
8673 POLE_WIDTH
8674 } else {
8675 size.width - POLE_WIDTH
8676 },
8677 size.height - line_height,
8678 );
8679
8680 origin.x = origin.x.max(content_origin.x);
8681
8682 element.prepaint_at(origin, window, cx);
8683
8684 Some((element, origin))
8685 }
8686
8687 fn render_edit_prediction_scroll_popover(
8688 &mut self,
8689 to_y: impl Fn(Size<Pixels>) -> Pixels,
8690 scroll_icon: IconName,
8691 visible_row_range: Range<DisplayRow>,
8692 line_layouts: &[LineWithInvisibles],
8693 newest_selection_head: Option<DisplayPoint>,
8694 scrolled_content_origin: gpui::Point<Pixels>,
8695 window: &mut Window,
8696 cx: &mut App,
8697 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8698 let mut element = self
8699 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8700 .into_any();
8701
8702 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8703
8704 let cursor = newest_selection_head?;
8705 let cursor_row_layout =
8706 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8707 let cursor_column = cursor.column() as usize;
8708
8709 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8710
8711 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8712
8713 element.prepaint_at(origin, window, cx);
8714 Some((element, origin))
8715 }
8716
8717 fn render_edit_prediction_eager_jump_popover(
8718 &mut self,
8719 text_bounds: &Bounds<Pixels>,
8720 content_origin: gpui::Point<Pixels>,
8721 editor_snapshot: &EditorSnapshot,
8722 visible_row_range: Range<DisplayRow>,
8723 scroll_top: f32,
8724 scroll_bottom: f32,
8725 line_height: Pixels,
8726 scroll_pixel_position: gpui::Point<Pixels>,
8727 target_display_point: DisplayPoint,
8728 editor_width: Pixels,
8729 window: &mut Window,
8730 cx: &mut App,
8731 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8732 if target_display_point.row().as_f32() < scroll_top {
8733 let mut element = self
8734 .render_edit_prediction_line_popover(
8735 "Jump to Edit",
8736 Some(IconName::ArrowUp),
8737 window,
8738 cx,
8739 )?
8740 .into_any();
8741
8742 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8743 let offset = point(
8744 (text_bounds.size.width - size.width) / 2.,
8745 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8746 );
8747
8748 let origin = text_bounds.origin + offset;
8749 element.prepaint_at(origin, window, cx);
8750 Some((element, origin))
8751 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8752 let mut element = self
8753 .render_edit_prediction_line_popover(
8754 "Jump to Edit",
8755 Some(IconName::ArrowDown),
8756 window,
8757 cx,
8758 )?
8759 .into_any();
8760
8761 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8762 let offset = point(
8763 (text_bounds.size.width - size.width) / 2.,
8764 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8765 );
8766
8767 let origin = text_bounds.origin + offset;
8768 element.prepaint_at(origin, window, cx);
8769 Some((element, origin))
8770 } else {
8771 self.render_edit_prediction_end_of_line_popover(
8772 "Jump to Edit",
8773 editor_snapshot,
8774 visible_row_range,
8775 target_display_point,
8776 line_height,
8777 scroll_pixel_position,
8778 content_origin,
8779 editor_width,
8780 window,
8781 cx,
8782 )
8783 }
8784 }
8785
8786 fn render_edit_prediction_end_of_line_popover(
8787 self: &mut Editor,
8788 label: &'static str,
8789 editor_snapshot: &EditorSnapshot,
8790 visible_row_range: Range<DisplayRow>,
8791 target_display_point: DisplayPoint,
8792 line_height: Pixels,
8793 scroll_pixel_position: gpui::Point<Pixels>,
8794 content_origin: gpui::Point<Pixels>,
8795 editor_width: Pixels,
8796 window: &mut Window,
8797 cx: &mut App,
8798 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8799 let target_line_end = DisplayPoint::new(
8800 target_display_point.row(),
8801 editor_snapshot.line_len(target_display_point.row()),
8802 );
8803
8804 let mut element = self
8805 .render_edit_prediction_line_popover(label, None, window, cx)?
8806 .into_any();
8807
8808 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8809
8810 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8811
8812 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8813 let mut origin = start_point
8814 + line_origin
8815 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8816 origin.x = origin.x.max(content_origin.x);
8817
8818 let max_x = content_origin.x + editor_width - size.width;
8819
8820 if origin.x > max_x {
8821 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8822
8823 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8824 origin.y += offset;
8825 IconName::ArrowUp
8826 } else {
8827 origin.y -= offset;
8828 IconName::ArrowDown
8829 };
8830
8831 element = self
8832 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8833 .into_any();
8834
8835 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8836
8837 origin.x = content_origin.x + editor_width - size.width - px(2.);
8838 }
8839
8840 element.prepaint_at(origin, window, cx);
8841 Some((element, origin))
8842 }
8843
8844 fn render_edit_prediction_diff_popover(
8845 self: &Editor,
8846 text_bounds: &Bounds<Pixels>,
8847 content_origin: gpui::Point<Pixels>,
8848 right_margin: Pixels,
8849 editor_snapshot: &EditorSnapshot,
8850 visible_row_range: Range<DisplayRow>,
8851 line_layouts: &[LineWithInvisibles],
8852 line_height: Pixels,
8853 scroll_pixel_position: gpui::Point<Pixels>,
8854 newest_selection_head: Option<DisplayPoint>,
8855 editor_width: Pixels,
8856 style: &EditorStyle,
8857 edits: &Vec<(Range<Anchor>, String)>,
8858 edit_preview: &Option<language::EditPreview>,
8859 snapshot: &language::BufferSnapshot,
8860 window: &mut Window,
8861 cx: &mut App,
8862 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8863 let edit_start = edits
8864 .first()
8865 .unwrap()
8866 .0
8867 .start
8868 .to_display_point(editor_snapshot);
8869 let edit_end = edits
8870 .last()
8871 .unwrap()
8872 .0
8873 .end
8874 .to_display_point(editor_snapshot);
8875
8876 let is_visible = visible_row_range.contains(&edit_start.row())
8877 || visible_row_range.contains(&edit_end.row());
8878 if !is_visible {
8879 return None;
8880 }
8881
8882 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8883 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8884 } else {
8885 // Fallback for providers without edit_preview
8886 crate::edit_prediction_fallback_text(edits, cx)
8887 };
8888
8889 let styled_text = highlighted_edits.to_styled_text(&style.text);
8890 let line_count = highlighted_edits.text.lines().count();
8891
8892 const BORDER_WIDTH: Pixels = px(1.);
8893
8894 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8895 let has_keybind = keybind.is_some();
8896
8897 let mut element = h_flex()
8898 .items_start()
8899 .child(
8900 h_flex()
8901 .bg(cx.theme().colors().editor_background)
8902 .border(BORDER_WIDTH)
8903 .shadow_xs()
8904 .border_color(cx.theme().colors().border)
8905 .rounded_l_lg()
8906 .when(line_count > 1, |el| el.rounded_br_lg())
8907 .pr_1()
8908 .child(styled_text),
8909 )
8910 .child(
8911 h_flex()
8912 .h(line_height + BORDER_WIDTH * 2.)
8913 .px_1p5()
8914 .gap_1()
8915 // Workaround: For some reason, there's a gap if we don't do this
8916 .ml(-BORDER_WIDTH)
8917 .shadow(vec![gpui::BoxShadow {
8918 color: gpui::black().opacity(0.05),
8919 offset: point(px(1.), px(1.)),
8920 blur_radius: px(2.),
8921 spread_radius: px(0.),
8922 }])
8923 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8924 .border(BORDER_WIDTH)
8925 .border_color(cx.theme().colors().border)
8926 .rounded_r_lg()
8927 .id("edit_prediction_diff_popover_keybind")
8928 .when(!has_keybind, |el| {
8929 let status_colors = cx.theme().status();
8930
8931 el.bg(status_colors.error_background)
8932 .border_color(status_colors.error.opacity(0.6))
8933 .child(Icon::new(IconName::Info).color(Color::Error))
8934 .cursor_default()
8935 .hoverable_tooltip(move |_window, cx| {
8936 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8937 })
8938 })
8939 .children(keybind),
8940 )
8941 .into_any();
8942
8943 let longest_row =
8944 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8945 let longest_line_width = if visible_row_range.contains(&longest_row) {
8946 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8947 } else {
8948 layout_line(
8949 longest_row,
8950 editor_snapshot,
8951 style,
8952 editor_width,
8953 |_| false,
8954 window,
8955 cx,
8956 )
8957 .width
8958 };
8959
8960 let viewport_bounds =
8961 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8962 right: -right_margin,
8963 ..Default::default()
8964 });
8965
8966 let x_after_longest =
8967 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8968 - scroll_pixel_position.x;
8969
8970 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8971
8972 // Fully visible if it can be displayed within the window (allow overlapping other
8973 // panes). However, this is only allowed if the popover starts within text_bounds.
8974 let can_position_to_the_right = x_after_longest < text_bounds.right()
8975 && x_after_longest + element_bounds.width < viewport_bounds.right();
8976
8977 let mut origin = if can_position_to_the_right {
8978 point(
8979 x_after_longest,
8980 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8981 - scroll_pixel_position.y,
8982 )
8983 } else {
8984 let cursor_row = newest_selection_head.map(|head| head.row());
8985 let above_edit = edit_start
8986 .row()
8987 .0
8988 .checked_sub(line_count as u32)
8989 .map(DisplayRow);
8990 let below_edit = Some(edit_end.row() + 1);
8991 let above_cursor =
8992 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8993 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8994
8995 // Place the edit popover adjacent to the edit if there is a location
8996 // available that is onscreen and does not obscure the cursor. Otherwise,
8997 // place it adjacent to the cursor.
8998 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8999 .into_iter()
9000 .flatten()
9001 .find(|&start_row| {
9002 let end_row = start_row + line_count as u32;
9003 visible_row_range.contains(&start_row)
9004 && visible_row_range.contains(&end_row)
9005 && cursor_row
9006 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9007 })?;
9008
9009 content_origin
9010 + point(
9011 -scroll_pixel_position.x,
9012 row_target.as_f32() * line_height - scroll_pixel_position.y,
9013 )
9014 };
9015
9016 origin.x -= BORDER_WIDTH;
9017
9018 window.defer_draw(element, origin, 1);
9019
9020 // Do not return an element, since it will already be drawn due to defer_draw.
9021 None
9022 }
9023
9024 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9025 px(30.)
9026 }
9027
9028 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9029 if self.read_only(cx) {
9030 cx.theme().players().read_only()
9031 } else {
9032 self.style.as_ref().unwrap().local_player
9033 }
9034 }
9035
9036 fn render_edit_prediction_accept_keybind(
9037 &self,
9038 window: &mut Window,
9039 cx: &App,
9040 ) -> Option<AnyElement> {
9041 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9042 let accept_keystroke = accept_binding.keystroke()?;
9043
9044 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9045
9046 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9047 Color::Accent
9048 } else {
9049 Color::Muted
9050 };
9051
9052 h_flex()
9053 .px_0p5()
9054 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9055 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9056 .text_size(TextSize::XSmall.rems(cx))
9057 .child(h_flex().children(ui::render_modifiers(
9058 accept_keystroke.modifiers(),
9059 PlatformStyle::platform(),
9060 Some(modifiers_color),
9061 Some(IconSize::XSmall.rems().into()),
9062 true,
9063 )))
9064 .when(is_platform_style_mac, |parent| {
9065 parent.child(accept_keystroke.key().to_string())
9066 })
9067 .when(!is_platform_style_mac, |parent| {
9068 parent.child(
9069 Key::new(
9070 util::capitalize(accept_keystroke.key()),
9071 Some(Color::Default),
9072 )
9073 .size(Some(IconSize::XSmall.rems().into())),
9074 )
9075 })
9076 .into_any()
9077 .into()
9078 }
9079
9080 fn render_edit_prediction_line_popover(
9081 &self,
9082 label: impl Into<SharedString>,
9083 icon: Option<IconName>,
9084 window: &mut Window,
9085 cx: &App,
9086 ) -> Option<Stateful<Div>> {
9087 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9088
9089 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9090 let has_keybind = keybind.is_some();
9091
9092 let result = h_flex()
9093 .id("ep-line-popover")
9094 .py_0p5()
9095 .pl_1()
9096 .pr(padding_right)
9097 .gap_1()
9098 .rounded_md()
9099 .border_1()
9100 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9101 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9102 .shadow_xs()
9103 .when(!has_keybind, |el| {
9104 let status_colors = cx.theme().status();
9105
9106 el.bg(status_colors.error_background)
9107 .border_color(status_colors.error.opacity(0.6))
9108 .pl_2()
9109 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9110 .cursor_default()
9111 .hoverable_tooltip(move |_window, cx| {
9112 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9113 })
9114 })
9115 .children(keybind)
9116 .child(
9117 Label::new(label)
9118 .size(LabelSize::Small)
9119 .when(!has_keybind, |el| {
9120 el.color(cx.theme().status().error.into()).strikethrough()
9121 }),
9122 )
9123 .when(!has_keybind, |el| {
9124 el.child(
9125 h_flex().ml_1().child(
9126 Icon::new(IconName::Info)
9127 .size(IconSize::Small)
9128 .color(cx.theme().status().error.into()),
9129 ),
9130 )
9131 })
9132 .when_some(icon, |element, icon| {
9133 element.child(
9134 div()
9135 .mt(px(1.5))
9136 .child(Icon::new(icon).size(IconSize::Small)),
9137 )
9138 });
9139
9140 Some(result)
9141 }
9142
9143 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9144 let accent_color = cx.theme().colors().text_accent;
9145 let editor_bg_color = cx.theme().colors().editor_background;
9146 editor_bg_color.blend(accent_color.opacity(0.1))
9147 }
9148
9149 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9150 let accent_color = cx.theme().colors().text_accent;
9151 let editor_bg_color = cx.theme().colors().editor_background;
9152 editor_bg_color.blend(accent_color.opacity(0.6))
9153 }
9154 fn get_prediction_provider_icon_name(
9155 provider: &Option<RegisteredEditPredictionProvider>,
9156 ) -> IconName {
9157 match provider {
9158 Some(provider) => match provider.provider.name() {
9159 "copilot" => IconName::Copilot,
9160 "supermaven" => IconName::Supermaven,
9161 _ => IconName::ZedPredict,
9162 },
9163 None => IconName::ZedPredict,
9164 }
9165 }
9166
9167 fn render_edit_prediction_cursor_popover(
9168 &self,
9169 min_width: Pixels,
9170 max_width: Pixels,
9171 cursor_point: Point,
9172 style: &EditorStyle,
9173 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9174 _window: &Window,
9175 cx: &mut Context<Editor>,
9176 ) -> Option<AnyElement> {
9177 let provider = self.edit_prediction_provider.as_ref()?;
9178 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9179
9180 let is_refreshing = provider.provider.is_refreshing(cx);
9181
9182 fn pending_completion_container(icon: IconName) -> Div {
9183 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9184 }
9185
9186 let completion = match &self.active_edit_prediction {
9187 Some(prediction) => {
9188 if !self.has_visible_completions_menu() {
9189 const RADIUS: Pixels = px(6.);
9190 const BORDER_WIDTH: Pixels = px(1.);
9191
9192 return Some(
9193 h_flex()
9194 .elevation_2(cx)
9195 .border(BORDER_WIDTH)
9196 .border_color(cx.theme().colors().border)
9197 .when(accept_keystroke.is_none(), |el| {
9198 el.border_color(cx.theme().status().error)
9199 })
9200 .rounded(RADIUS)
9201 .rounded_tl(px(0.))
9202 .overflow_hidden()
9203 .child(div().px_1p5().child(match &prediction.completion {
9204 EditPrediction::Move { target, snapshot } => {
9205 use text::ToPoint as _;
9206 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9207 {
9208 Icon::new(IconName::ZedPredictDown)
9209 } else {
9210 Icon::new(IconName::ZedPredictUp)
9211 }
9212 }
9213 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9214 }))
9215 .child(
9216 h_flex()
9217 .gap_1()
9218 .py_1()
9219 .px_2()
9220 .rounded_r(RADIUS - BORDER_WIDTH)
9221 .border_l_1()
9222 .border_color(cx.theme().colors().border)
9223 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9224 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9225 el.child(
9226 Label::new("Hold")
9227 .size(LabelSize::Small)
9228 .when(accept_keystroke.is_none(), |el| {
9229 el.strikethrough()
9230 })
9231 .line_height_style(LineHeightStyle::UiLabel),
9232 )
9233 })
9234 .id("edit_prediction_cursor_popover_keybind")
9235 .when(accept_keystroke.is_none(), |el| {
9236 let status_colors = cx.theme().status();
9237
9238 el.bg(status_colors.error_background)
9239 .border_color(status_colors.error.opacity(0.6))
9240 .child(Icon::new(IconName::Info).color(Color::Error))
9241 .cursor_default()
9242 .hoverable_tooltip(move |_window, cx| {
9243 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9244 .into()
9245 })
9246 })
9247 .when_some(
9248 accept_keystroke.as_ref(),
9249 |el, accept_keystroke| {
9250 el.child(h_flex().children(ui::render_modifiers(
9251 accept_keystroke.modifiers(),
9252 PlatformStyle::platform(),
9253 Some(Color::Default),
9254 Some(IconSize::XSmall.rems().into()),
9255 false,
9256 )))
9257 },
9258 ),
9259 )
9260 .into_any(),
9261 );
9262 }
9263
9264 self.render_edit_prediction_cursor_popover_preview(
9265 prediction,
9266 cursor_point,
9267 style,
9268 cx,
9269 )?
9270 }
9271
9272 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9273 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9274 stale_completion,
9275 cursor_point,
9276 style,
9277 cx,
9278 )?,
9279
9280 None => pending_completion_container(provider_icon)
9281 .child(Label::new("...").size(LabelSize::Small)),
9282 },
9283
9284 None => pending_completion_container(provider_icon)
9285 .child(Label::new("...").size(LabelSize::Small)),
9286 };
9287
9288 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9289 completion
9290 .with_animation(
9291 "loading-completion",
9292 Animation::new(Duration::from_secs(2))
9293 .repeat()
9294 .with_easing(pulsating_between(0.4, 0.8)),
9295 |label, delta| label.opacity(delta),
9296 )
9297 .into_any_element()
9298 } else {
9299 completion.into_any_element()
9300 };
9301
9302 let has_completion = self.active_edit_prediction.is_some();
9303
9304 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9305 Some(
9306 h_flex()
9307 .min_w(min_width)
9308 .max_w(max_width)
9309 .flex_1()
9310 .elevation_2(cx)
9311 .border_color(cx.theme().colors().border)
9312 .child(
9313 div()
9314 .flex_1()
9315 .py_1()
9316 .px_2()
9317 .overflow_hidden()
9318 .child(completion),
9319 )
9320 .when_some(accept_keystroke, |el, accept_keystroke| {
9321 if !accept_keystroke.modifiers().modified() {
9322 return el;
9323 }
9324
9325 el.child(
9326 h_flex()
9327 .h_full()
9328 .border_l_1()
9329 .rounded_r_lg()
9330 .border_color(cx.theme().colors().border)
9331 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9332 .gap_1()
9333 .py_1()
9334 .px_2()
9335 .child(
9336 h_flex()
9337 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9338 .when(is_platform_style_mac, |parent| parent.gap_1())
9339 .child(h_flex().children(ui::render_modifiers(
9340 accept_keystroke.modifiers(),
9341 PlatformStyle::platform(),
9342 Some(if !has_completion {
9343 Color::Muted
9344 } else {
9345 Color::Default
9346 }),
9347 None,
9348 false,
9349 ))),
9350 )
9351 .child(Label::new("Preview").into_any_element())
9352 .opacity(if has_completion { 1.0 } else { 0.4 }),
9353 )
9354 })
9355 .into_any(),
9356 )
9357 }
9358
9359 fn render_edit_prediction_cursor_popover_preview(
9360 &self,
9361 completion: &EditPredictionState,
9362 cursor_point: Point,
9363 style: &EditorStyle,
9364 cx: &mut Context<Editor>,
9365 ) -> Option<Div> {
9366 use text::ToPoint as _;
9367
9368 fn render_relative_row_jump(
9369 prefix: impl Into<String>,
9370 current_row: u32,
9371 target_row: u32,
9372 ) -> Div {
9373 let (row_diff, arrow) = if target_row < current_row {
9374 (current_row - target_row, IconName::ArrowUp)
9375 } else {
9376 (target_row - current_row, IconName::ArrowDown)
9377 };
9378
9379 h_flex()
9380 .child(
9381 Label::new(format!("{}{}", prefix.into(), row_diff))
9382 .color(Color::Muted)
9383 .size(LabelSize::Small),
9384 )
9385 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9386 }
9387
9388 let supports_jump = self
9389 .edit_prediction_provider
9390 .as_ref()
9391 .map(|provider| provider.provider.supports_jump_to_edit())
9392 .unwrap_or(true);
9393
9394 match &completion.completion {
9395 EditPrediction::Move {
9396 target, snapshot, ..
9397 } => {
9398 if !supports_jump {
9399 return None;
9400 }
9401
9402 Some(
9403 h_flex()
9404 .px_2()
9405 .gap_2()
9406 .flex_1()
9407 .child(
9408 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9409 Icon::new(IconName::ZedPredictDown)
9410 } else {
9411 Icon::new(IconName::ZedPredictUp)
9412 },
9413 )
9414 .child(Label::new("Jump to Edit")),
9415 )
9416 }
9417
9418 EditPrediction::Edit {
9419 edits,
9420 edit_preview,
9421 snapshot,
9422 display_mode: _,
9423 } => {
9424 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9425
9426 let (highlighted_edits, has_more_lines) =
9427 if let Some(edit_preview) = edit_preview.as_ref() {
9428 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9429 .first_line_preview()
9430 } else {
9431 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9432 };
9433
9434 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9435 .with_default_highlights(&style.text, highlighted_edits.highlights);
9436
9437 let preview = h_flex()
9438 .gap_1()
9439 .min_w_16()
9440 .child(styled_text)
9441 .when(has_more_lines, |parent| parent.child("…"));
9442
9443 let left = if supports_jump && first_edit_row != cursor_point.row {
9444 render_relative_row_jump("", cursor_point.row, first_edit_row)
9445 .into_any_element()
9446 } else {
9447 let icon_name =
9448 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9449 Icon::new(icon_name).into_any_element()
9450 };
9451
9452 Some(
9453 h_flex()
9454 .h_full()
9455 .flex_1()
9456 .gap_2()
9457 .pr_1()
9458 .overflow_x_hidden()
9459 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9460 .child(left)
9461 .child(preview),
9462 )
9463 }
9464 }
9465 }
9466
9467 pub fn render_context_menu(
9468 &self,
9469 style: &EditorStyle,
9470 max_height_in_lines: u32,
9471 window: &mut Window,
9472 cx: &mut Context<Editor>,
9473 ) -> Option<AnyElement> {
9474 let menu = self.context_menu.borrow();
9475 let menu = menu.as_ref()?;
9476 if !menu.visible() {
9477 return None;
9478 };
9479 Some(menu.render(style, max_height_in_lines, window, cx))
9480 }
9481
9482 fn render_context_menu_aside(
9483 &mut self,
9484 max_size: Size<Pixels>,
9485 window: &mut Window,
9486 cx: &mut Context<Editor>,
9487 ) -> Option<AnyElement> {
9488 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9489 if menu.visible() {
9490 menu.render_aside(max_size, window, cx)
9491 } else {
9492 None
9493 }
9494 })
9495 }
9496
9497 fn hide_context_menu(
9498 &mut self,
9499 window: &mut Window,
9500 cx: &mut Context<Self>,
9501 ) -> Option<CodeContextMenu> {
9502 cx.notify();
9503 self.completion_tasks.clear();
9504 let context_menu = self.context_menu.borrow_mut().take();
9505 self.stale_edit_prediction_in_menu.take();
9506 self.update_visible_edit_prediction(window, cx);
9507 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9508 && let Some(completion_provider) = &self.completion_provider
9509 {
9510 completion_provider.selection_changed(None, window, cx);
9511 }
9512 context_menu
9513 }
9514
9515 fn show_snippet_choices(
9516 &mut self,
9517 choices: &Vec<String>,
9518 selection: Range<Anchor>,
9519 cx: &mut Context<Self>,
9520 ) {
9521 let Some((_, buffer, _)) = self
9522 .buffer()
9523 .read(cx)
9524 .excerpt_containing(selection.start, cx)
9525 else {
9526 return;
9527 };
9528 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9529 else {
9530 return;
9531 };
9532 if buffer != end_buffer {
9533 log::error!("expected anchor range to have matching buffer IDs");
9534 return;
9535 }
9536
9537 let id = post_inc(&mut self.next_completion_id);
9538 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9539 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9540 CompletionsMenu::new_snippet_choices(
9541 id,
9542 true,
9543 choices,
9544 selection,
9545 buffer,
9546 snippet_sort_order,
9547 ),
9548 ));
9549 }
9550
9551 pub fn insert_snippet(
9552 &mut self,
9553 insertion_ranges: &[Range<usize>],
9554 snippet: Snippet,
9555 window: &mut Window,
9556 cx: &mut Context<Self>,
9557 ) -> Result<()> {
9558 struct Tabstop<T> {
9559 is_end_tabstop: bool,
9560 ranges: Vec<Range<T>>,
9561 choices: Option<Vec<String>>,
9562 }
9563
9564 let tabstops = self.buffer.update(cx, |buffer, cx| {
9565 let snippet_text: Arc<str> = snippet.text.clone().into();
9566 let edits = insertion_ranges
9567 .iter()
9568 .cloned()
9569 .map(|range| (range, snippet_text.clone()));
9570 let autoindent_mode = AutoindentMode::Block {
9571 original_indent_columns: Vec::new(),
9572 };
9573 buffer.edit(edits, Some(autoindent_mode), cx);
9574
9575 let snapshot = &*buffer.read(cx);
9576 let snippet = &snippet;
9577 snippet
9578 .tabstops
9579 .iter()
9580 .map(|tabstop| {
9581 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9582 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9583 });
9584 let mut tabstop_ranges = tabstop
9585 .ranges
9586 .iter()
9587 .flat_map(|tabstop_range| {
9588 let mut delta = 0_isize;
9589 insertion_ranges.iter().map(move |insertion_range| {
9590 let insertion_start = insertion_range.start as isize + delta;
9591 delta +=
9592 snippet.text.len() as isize - insertion_range.len() as isize;
9593
9594 let start = ((insertion_start + tabstop_range.start) as usize)
9595 .min(snapshot.len());
9596 let end = ((insertion_start + tabstop_range.end) as usize)
9597 .min(snapshot.len());
9598 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9599 })
9600 })
9601 .collect::<Vec<_>>();
9602 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9603
9604 Tabstop {
9605 is_end_tabstop,
9606 ranges: tabstop_ranges,
9607 choices: tabstop.choices.clone(),
9608 }
9609 })
9610 .collect::<Vec<_>>()
9611 });
9612 if let Some(tabstop) = tabstops.first() {
9613 self.change_selections(Default::default(), window, cx, |s| {
9614 // Reverse order so that the first range is the newest created selection.
9615 // Completions will use it and autoscroll will prioritize it.
9616 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9617 });
9618
9619 if let Some(choices) = &tabstop.choices
9620 && let Some(selection) = tabstop.ranges.first()
9621 {
9622 self.show_snippet_choices(choices, selection.clone(), cx)
9623 }
9624
9625 // If we're already at the last tabstop and it's at the end of the snippet,
9626 // we're done, we don't need to keep the state around.
9627 if !tabstop.is_end_tabstop {
9628 let choices = tabstops
9629 .iter()
9630 .map(|tabstop| tabstop.choices.clone())
9631 .collect();
9632
9633 let ranges = tabstops
9634 .into_iter()
9635 .map(|tabstop| tabstop.ranges)
9636 .collect::<Vec<_>>();
9637
9638 self.snippet_stack.push(SnippetState {
9639 active_index: 0,
9640 ranges,
9641 choices,
9642 });
9643 }
9644
9645 // Check whether the just-entered snippet ends with an auto-closable bracket.
9646 if self.autoclose_regions.is_empty() {
9647 let snapshot = self.buffer.read(cx).snapshot(cx);
9648 let mut all_selections = self.selections.all::<Point>(cx);
9649 for selection in &mut all_selections {
9650 let selection_head = selection.head();
9651 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9652 continue;
9653 };
9654
9655 let mut bracket_pair = None;
9656 let max_lookup_length = scope
9657 .brackets()
9658 .map(|(pair, _)| {
9659 pair.start
9660 .as_str()
9661 .chars()
9662 .count()
9663 .max(pair.end.as_str().chars().count())
9664 })
9665 .max();
9666 if let Some(max_lookup_length) = max_lookup_length {
9667 let next_text = snapshot
9668 .chars_at(selection_head)
9669 .take(max_lookup_length)
9670 .collect::<String>();
9671 let prev_text = snapshot
9672 .reversed_chars_at(selection_head)
9673 .take(max_lookup_length)
9674 .collect::<String>();
9675
9676 for (pair, enabled) in scope.brackets() {
9677 if enabled
9678 && pair.close
9679 && prev_text.starts_with(pair.start.as_str())
9680 && next_text.starts_with(pair.end.as_str())
9681 {
9682 bracket_pair = Some(pair.clone());
9683 break;
9684 }
9685 }
9686 }
9687
9688 if let Some(pair) = bracket_pair {
9689 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9690 let autoclose_enabled =
9691 self.use_autoclose && snapshot_settings.use_autoclose;
9692 if autoclose_enabled {
9693 let start = snapshot.anchor_after(selection_head);
9694 let end = snapshot.anchor_after(selection_head);
9695 self.autoclose_regions.push(AutocloseRegion {
9696 selection_id: selection.id,
9697 range: start..end,
9698 pair,
9699 });
9700 }
9701 }
9702 }
9703 }
9704 }
9705 Ok(())
9706 }
9707
9708 pub fn move_to_next_snippet_tabstop(
9709 &mut self,
9710 window: &mut Window,
9711 cx: &mut Context<Self>,
9712 ) -> bool {
9713 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9714 }
9715
9716 pub fn move_to_prev_snippet_tabstop(
9717 &mut self,
9718 window: &mut Window,
9719 cx: &mut Context<Self>,
9720 ) -> bool {
9721 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9722 }
9723
9724 pub fn move_to_snippet_tabstop(
9725 &mut self,
9726 bias: Bias,
9727 window: &mut Window,
9728 cx: &mut Context<Self>,
9729 ) -> bool {
9730 if let Some(mut snippet) = self.snippet_stack.pop() {
9731 match bias {
9732 Bias::Left => {
9733 if snippet.active_index > 0 {
9734 snippet.active_index -= 1;
9735 } else {
9736 self.snippet_stack.push(snippet);
9737 return false;
9738 }
9739 }
9740 Bias::Right => {
9741 if snippet.active_index + 1 < snippet.ranges.len() {
9742 snippet.active_index += 1;
9743 } else {
9744 self.snippet_stack.push(snippet);
9745 return false;
9746 }
9747 }
9748 }
9749 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9750 self.change_selections(Default::default(), window, cx, |s| {
9751 // Reverse order so that the first range is the newest created selection.
9752 // Completions will use it and autoscroll will prioritize it.
9753 s.select_ranges(current_ranges.iter().rev().cloned())
9754 });
9755
9756 if let Some(choices) = &snippet.choices[snippet.active_index]
9757 && let Some(selection) = current_ranges.first()
9758 {
9759 self.show_snippet_choices(choices, selection.clone(), cx);
9760 }
9761
9762 // If snippet state is not at the last tabstop, push it back on the stack
9763 if snippet.active_index + 1 < snippet.ranges.len() {
9764 self.snippet_stack.push(snippet);
9765 }
9766 return true;
9767 }
9768 }
9769
9770 false
9771 }
9772
9773 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9774 self.transact(window, cx, |this, window, cx| {
9775 this.select_all(&SelectAll, window, cx);
9776 this.insert("", window, cx);
9777 });
9778 }
9779
9780 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9781 if self.read_only(cx) {
9782 return;
9783 }
9784 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9785 self.transact(window, cx, |this, window, cx| {
9786 this.select_autoclose_pair(window, cx);
9787 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9788 if !this.linked_edit_ranges.is_empty() {
9789 let selections = this.selections.all::<MultiBufferPoint>(cx);
9790 let snapshot = this.buffer.read(cx).snapshot(cx);
9791
9792 for selection in selections.iter() {
9793 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9794 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9795 if selection_start.buffer_id != selection_end.buffer_id {
9796 continue;
9797 }
9798 if let Some(ranges) =
9799 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9800 {
9801 for (buffer, entries) in ranges {
9802 linked_ranges.entry(buffer).or_default().extend(entries);
9803 }
9804 }
9805 }
9806 }
9807
9808 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9809 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9810 for selection in &mut selections {
9811 if selection.is_empty() {
9812 let old_head = selection.head();
9813 let mut new_head =
9814 movement::left(&display_map, old_head.to_display_point(&display_map))
9815 .to_point(&display_map);
9816 if let Some((buffer, line_buffer_range)) = display_map
9817 .buffer_snapshot
9818 .buffer_line_for_row(MultiBufferRow(old_head.row))
9819 {
9820 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9821 let indent_len = match indent_size.kind {
9822 IndentKind::Space => {
9823 buffer.settings_at(line_buffer_range.start, cx).tab_size
9824 }
9825 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9826 };
9827 if old_head.column <= indent_size.len && old_head.column > 0 {
9828 let indent_len = indent_len.get();
9829 new_head = cmp::min(
9830 new_head,
9831 MultiBufferPoint::new(
9832 old_head.row,
9833 ((old_head.column - 1) / indent_len) * indent_len,
9834 ),
9835 );
9836 }
9837 }
9838
9839 selection.set_head(new_head, SelectionGoal::None);
9840 }
9841 }
9842
9843 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9844 this.insert("", window, cx);
9845 let empty_str: Arc<str> = Arc::from("");
9846 for (buffer, edits) in linked_ranges {
9847 let snapshot = buffer.read(cx).snapshot();
9848 use text::ToPoint as TP;
9849
9850 let edits = edits
9851 .into_iter()
9852 .map(|range| {
9853 let end_point = TP::to_point(&range.end, &snapshot);
9854 let mut start_point = TP::to_point(&range.start, &snapshot);
9855
9856 if end_point == start_point {
9857 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9858 .saturating_sub(1);
9859 start_point =
9860 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9861 };
9862
9863 (start_point..end_point, empty_str.clone())
9864 })
9865 .sorted_by_key(|(range, _)| range.start)
9866 .collect::<Vec<_>>();
9867 buffer.update(cx, |this, cx| {
9868 this.edit(edits, None, cx);
9869 })
9870 }
9871 this.refresh_edit_prediction(true, false, window, cx);
9872 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9873 });
9874 }
9875
9876 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9877 if self.read_only(cx) {
9878 return;
9879 }
9880 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9881 self.transact(window, cx, |this, window, cx| {
9882 this.change_selections(Default::default(), window, cx, |s| {
9883 s.move_with(|map, selection| {
9884 if selection.is_empty() {
9885 let cursor = movement::right(map, selection.head());
9886 selection.end = cursor;
9887 selection.reversed = true;
9888 selection.goal = SelectionGoal::None;
9889 }
9890 })
9891 });
9892 this.insert("", window, cx);
9893 this.refresh_edit_prediction(true, false, window, cx);
9894 });
9895 }
9896
9897 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9898 if self.mode.is_single_line() {
9899 cx.propagate();
9900 return;
9901 }
9902
9903 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9904 if self.move_to_prev_snippet_tabstop(window, cx) {
9905 return;
9906 }
9907 self.outdent(&Outdent, window, cx);
9908 }
9909
9910 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9911 if self.mode.is_single_line() {
9912 cx.propagate();
9913 return;
9914 }
9915
9916 if self.move_to_next_snippet_tabstop(window, cx) {
9917 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9918 return;
9919 }
9920 if self.read_only(cx) {
9921 return;
9922 }
9923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9924 let mut selections = self.selections.all_adjusted(cx);
9925 let buffer = self.buffer.read(cx);
9926 let snapshot = buffer.snapshot(cx);
9927 let rows_iter = selections.iter().map(|s| s.head().row);
9928 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9929
9930 let has_some_cursor_in_whitespace = selections
9931 .iter()
9932 .filter(|selection| selection.is_empty())
9933 .any(|selection| {
9934 let cursor = selection.head();
9935 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9936 cursor.column < current_indent.len
9937 });
9938
9939 let mut edits = Vec::new();
9940 let mut prev_edited_row = 0;
9941 let mut row_delta = 0;
9942 for selection in &mut selections {
9943 if selection.start.row != prev_edited_row {
9944 row_delta = 0;
9945 }
9946 prev_edited_row = selection.end.row;
9947
9948 // If the selection is non-empty, then increase the indentation of the selected lines.
9949 if !selection.is_empty() {
9950 row_delta =
9951 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9952 continue;
9953 }
9954
9955 let cursor = selection.head();
9956 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9957 if let Some(suggested_indent) =
9958 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9959 {
9960 // Don't do anything if already at suggested indent
9961 // and there is any other cursor which is not
9962 if has_some_cursor_in_whitespace
9963 && cursor.column == current_indent.len
9964 && current_indent.len == suggested_indent.len
9965 {
9966 continue;
9967 }
9968
9969 // Adjust line and move cursor to suggested indent
9970 // if cursor is not at suggested indent
9971 if cursor.column < suggested_indent.len
9972 && cursor.column <= current_indent.len
9973 && current_indent.len <= suggested_indent.len
9974 {
9975 selection.start = Point::new(cursor.row, suggested_indent.len);
9976 selection.end = selection.start;
9977 if row_delta == 0 {
9978 edits.extend(Buffer::edit_for_indent_size_adjustment(
9979 cursor.row,
9980 current_indent,
9981 suggested_indent,
9982 ));
9983 row_delta = suggested_indent.len - current_indent.len;
9984 }
9985 continue;
9986 }
9987
9988 // If current indent is more than suggested indent
9989 // only move cursor to current indent and skip indent
9990 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9991 selection.start = Point::new(cursor.row, current_indent.len);
9992 selection.end = selection.start;
9993 continue;
9994 }
9995 }
9996
9997 // Otherwise, insert a hard or soft tab.
9998 let settings = buffer.language_settings_at(cursor, cx);
9999 let tab_size = if settings.hard_tabs {
10000 IndentSize::tab()
10001 } else {
10002 let tab_size = settings.tab_size.get();
10003 let indent_remainder = snapshot
10004 .text_for_range(Point::new(cursor.row, 0)..cursor)
10005 .flat_map(str::chars)
10006 .fold(row_delta % tab_size, |counter: u32, c| {
10007 if c == '\t' {
10008 0
10009 } else {
10010 (counter + 1) % tab_size
10011 }
10012 });
10013
10014 let chars_to_next_tab_stop = tab_size - indent_remainder;
10015 IndentSize::spaces(chars_to_next_tab_stop)
10016 };
10017 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10018 selection.end = selection.start;
10019 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10020 row_delta += tab_size.len;
10021 }
10022
10023 self.transact(window, cx, |this, window, cx| {
10024 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10025 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10026 this.refresh_edit_prediction(true, false, window, cx);
10027 });
10028 }
10029
10030 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10031 if self.read_only(cx) {
10032 return;
10033 }
10034 if self.mode.is_single_line() {
10035 cx.propagate();
10036 return;
10037 }
10038
10039 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10040 let mut selections = self.selections.all::<Point>(cx);
10041 let mut prev_edited_row = 0;
10042 let mut row_delta = 0;
10043 let mut edits = Vec::new();
10044 let buffer = self.buffer.read(cx);
10045 let snapshot = buffer.snapshot(cx);
10046 for selection in &mut selections {
10047 if selection.start.row != prev_edited_row {
10048 row_delta = 0;
10049 }
10050 prev_edited_row = selection.end.row;
10051
10052 row_delta =
10053 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10054 }
10055
10056 self.transact(window, cx, |this, window, cx| {
10057 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10058 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10059 });
10060 }
10061
10062 fn indent_selection(
10063 buffer: &MultiBuffer,
10064 snapshot: &MultiBufferSnapshot,
10065 selection: &mut Selection<Point>,
10066 edits: &mut Vec<(Range<Point>, String)>,
10067 delta_for_start_row: u32,
10068 cx: &App,
10069 ) -> u32 {
10070 let settings = buffer.language_settings_at(selection.start, cx);
10071 let tab_size = settings.tab_size.get();
10072 let indent_kind = if settings.hard_tabs {
10073 IndentKind::Tab
10074 } else {
10075 IndentKind::Space
10076 };
10077 let mut start_row = selection.start.row;
10078 let mut end_row = selection.end.row + 1;
10079
10080 // If a selection ends at the beginning of a line, don't indent
10081 // that last line.
10082 if selection.end.column == 0 && selection.end.row > selection.start.row {
10083 end_row -= 1;
10084 }
10085
10086 // Avoid re-indenting a row that has already been indented by a
10087 // previous selection, but still update this selection's column
10088 // to reflect that indentation.
10089 if delta_for_start_row > 0 {
10090 start_row += 1;
10091 selection.start.column += delta_for_start_row;
10092 if selection.end.row == selection.start.row {
10093 selection.end.column += delta_for_start_row;
10094 }
10095 }
10096
10097 let mut delta_for_end_row = 0;
10098 let has_multiple_rows = start_row + 1 != end_row;
10099 for row in start_row..end_row {
10100 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10101 let indent_delta = match (current_indent.kind, indent_kind) {
10102 (IndentKind::Space, IndentKind::Space) => {
10103 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10104 IndentSize::spaces(columns_to_next_tab_stop)
10105 }
10106 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10107 (_, IndentKind::Tab) => IndentSize::tab(),
10108 };
10109
10110 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10111 0
10112 } else {
10113 selection.start.column
10114 };
10115 let row_start = Point::new(row, start);
10116 edits.push((
10117 row_start..row_start,
10118 indent_delta.chars().collect::<String>(),
10119 ));
10120
10121 // Update this selection's endpoints to reflect the indentation.
10122 if row == selection.start.row {
10123 selection.start.column += indent_delta.len;
10124 }
10125 if row == selection.end.row {
10126 selection.end.column += indent_delta.len;
10127 delta_for_end_row = indent_delta.len;
10128 }
10129 }
10130
10131 if selection.start.row == selection.end.row {
10132 delta_for_start_row + delta_for_end_row
10133 } else {
10134 delta_for_end_row
10135 }
10136 }
10137
10138 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10139 if self.read_only(cx) {
10140 return;
10141 }
10142 if self.mode.is_single_line() {
10143 cx.propagate();
10144 return;
10145 }
10146
10147 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10148 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10149 let selections = self.selections.all::<Point>(cx);
10150 let mut deletion_ranges = Vec::new();
10151 let mut last_outdent = None;
10152 {
10153 let buffer = self.buffer.read(cx);
10154 let snapshot = buffer.snapshot(cx);
10155 for selection in &selections {
10156 let settings = buffer.language_settings_at(selection.start, cx);
10157 let tab_size = settings.tab_size.get();
10158 let mut rows = selection.spanned_rows(false, &display_map);
10159
10160 // Avoid re-outdenting a row that has already been outdented by a
10161 // previous selection.
10162 if let Some(last_row) = last_outdent
10163 && last_row == rows.start
10164 {
10165 rows.start = rows.start.next_row();
10166 }
10167 let has_multiple_rows = rows.len() > 1;
10168 for row in rows.iter_rows() {
10169 let indent_size = snapshot.indent_size_for_line(row);
10170 if indent_size.len > 0 {
10171 let deletion_len = match indent_size.kind {
10172 IndentKind::Space => {
10173 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10174 if columns_to_prev_tab_stop == 0 {
10175 tab_size
10176 } else {
10177 columns_to_prev_tab_stop
10178 }
10179 }
10180 IndentKind::Tab => 1,
10181 };
10182 let start = if has_multiple_rows
10183 || deletion_len > selection.start.column
10184 || indent_size.len < selection.start.column
10185 {
10186 0
10187 } else {
10188 selection.start.column - deletion_len
10189 };
10190 deletion_ranges.push(
10191 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10192 );
10193 last_outdent = Some(row);
10194 }
10195 }
10196 }
10197 }
10198
10199 self.transact(window, cx, |this, window, cx| {
10200 this.buffer.update(cx, |buffer, cx| {
10201 let empty_str: Arc<str> = Arc::default();
10202 buffer.edit(
10203 deletion_ranges
10204 .into_iter()
10205 .map(|range| (range, empty_str.clone())),
10206 None,
10207 cx,
10208 );
10209 });
10210 let selections = this.selections.all::<usize>(cx);
10211 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10212 });
10213 }
10214
10215 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10216 if self.read_only(cx) {
10217 return;
10218 }
10219 if self.mode.is_single_line() {
10220 cx.propagate();
10221 return;
10222 }
10223
10224 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10225 let selections = self
10226 .selections
10227 .all::<usize>(cx)
10228 .into_iter()
10229 .map(|s| s.range());
10230
10231 self.transact(window, cx, |this, window, cx| {
10232 this.buffer.update(cx, |buffer, cx| {
10233 buffer.autoindent_ranges(selections, cx);
10234 });
10235 let selections = this.selections.all::<usize>(cx);
10236 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10237 });
10238 }
10239
10240 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10241 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10242 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10243 let selections = self.selections.all::<Point>(cx);
10244
10245 let mut new_cursors = Vec::new();
10246 let mut edit_ranges = Vec::new();
10247 let mut selections = selections.iter().peekable();
10248 while let Some(selection) = selections.next() {
10249 let mut rows = selection.spanned_rows(false, &display_map);
10250 let goal_display_column = selection.head().to_display_point(&display_map).column();
10251
10252 // Accumulate contiguous regions of rows that we want to delete.
10253 while let Some(next_selection) = selections.peek() {
10254 let next_rows = next_selection.spanned_rows(false, &display_map);
10255 if next_rows.start <= rows.end {
10256 rows.end = next_rows.end;
10257 selections.next().unwrap();
10258 } else {
10259 break;
10260 }
10261 }
10262
10263 let buffer = &display_map.buffer_snapshot;
10264 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10265 let edit_end;
10266 let cursor_buffer_row;
10267 if buffer.max_point().row >= rows.end.0 {
10268 // If there's a line after the range, delete the \n from the end of the row range
10269 // and position the cursor on the next line.
10270 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10271 cursor_buffer_row = rows.end;
10272 } else {
10273 // If there isn't a line after the range, delete the \n from the line before the
10274 // start of the row range and position the cursor there.
10275 edit_start = edit_start.saturating_sub(1);
10276 edit_end = buffer.len();
10277 cursor_buffer_row = rows.start.previous_row();
10278 }
10279
10280 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10281 *cursor.column_mut() =
10282 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10283
10284 new_cursors.push((
10285 selection.id,
10286 buffer.anchor_after(cursor.to_point(&display_map)),
10287 ));
10288 edit_ranges.push(edit_start..edit_end);
10289 }
10290
10291 self.transact(window, cx, |this, window, cx| {
10292 let buffer = this.buffer.update(cx, |buffer, cx| {
10293 let empty_str: Arc<str> = Arc::default();
10294 buffer.edit(
10295 edit_ranges
10296 .into_iter()
10297 .map(|range| (range, empty_str.clone())),
10298 None,
10299 cx,
10300 );
10301 buffer.snapshot(cx)
10302 });
10303 let new_selections = new_cursors
10304 .into_iter()
10305 .map(|(id, cursor)| {
10306 let cursor = cursor.to_point(&buffer);
10307 Selection {
10308 id,
10309 start: cursor,
10310 end: cursor,
10311 reversed: false,
10312 goal: SelectionGoal::None,
10313 }
10314 })
10315 .collect();
10316
10317 this.change_selections(Default::default(), window, cx, |s| {
10318 s.select(new_selections);
10319 });
10320 });
10321 }
10322
10323 pub fn join_lines_impl(
10324 &mut self,
10325 insert_whitespace: bool,
10326 window: &mut Window,
10327 cx: &mut Context<Self>,
10328 ) {
10329 if self.read_only(cx) {
10330 return;
10331 }
10332 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10333 for selection in self.selections.all::<Point>(cx) {
10334 let start = MultiBufferRow(selection.start.row);
10335 // Treat single line selections as if they include the next line. Otherwise this action
10336 // would do nothing for single line selections individual cursors.
10337 let end = if selection.start.row == selection.end.row {
10338 MultiBufferRow(selection.start.row + 1)
10339 } else {
10340 MultiBufferRow(selection.end.row)
10341 };
10342
10343 if let Some(last_row_range) = row_ranges.last_mut()
10344 && start <= last_row_range.end
10345 {
10346 last_row_range.end = end;
10347 continue;
10348 }
10349 row_ranges.push(start..end);
10350 }
10351
10352 let snapshot = self.buffer.read(cx).snapshot(cx);
10353 let mut cursor_positions = Vec::new();
10354 for row_range in &row_ranges {
10355 let anchor = snapshot.anchor_before(Point::new(
10356 row_range.end.previous_row().0,
10357 snapshot.line_len(row_range.end.previous_row()),
10358 ));
10359 cursor_positions.push(anchor..anchor);
10360 }
10361
10362 self.transact(window, cx, |this, window, cx| {
10363 for row_range in row_ranges.into_iter().rev() {
10364 for row in row_range.iter_rows().rev() {
10365 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10366 let next_line_row = row.next_row();
10367 let indent = snapshot.indent_size_for_line(next_line_row);
10368 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10369
10370 let replace =
10371 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10372 " "
10373 } else {
10374 ""
10375 };
10376
10377 this.buffer.update(cx, |buffer, cx| {
10378 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10379 });
10380 }
10381 }
10382
10383 this.change_selections(Default::default(), window, cx, |s| {
10384 s.select_anchor_ranges(cursor_positions)
10385 });
10386 });
10387 }
10388
10389 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10390 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10391 self.join_lines_impl(true, window, cx);
10392 }
10393
10394 pub fn sort_lines_case_sensitive(
10395 &mut self,
10396 _: &SortLinesCaseSensitive,
10397 window: &mut Window,
10398 cx: &mut Context<Self>,
10399 ) {
10400 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10401 }
10402
10403 pub fn sort_lines_by_length(
10404 &mut self,
10405 _: &SortLinesByLength,
10406 window: &mut Window,
10407 cx: &mut Context<Self>,
10408 ) {
10409 self.manipulate_immutable_lines(window, cx, |lines| {
10410 lines.sort_by_key(|&line| line.chars().count())
10411 })
10412 }
10413
10414 pub fn sort_lines_case_insensitive(
10415 &mut self,
10416 _: &SortLinesCaseInsensitive,
10417 window: &mut Window,
10418 cx: &mut Context<Self>,
10419 ) {
10420 self.manipulate_immutable_lines(window, cx, |lines| {
10421 lines.sort_by_key(|line| line.to_lowercase())
10422 })
10423 }
10424
10425 pub fn unique_lines_case_insensitive(
10426 &mut self,
10427 _: &UniqueLinesCaseInsensitive,
10428 window: &mut Window,
10429 cx: &mut Context<Self>,
10430 ) {
10431 self.manipulate_immutable_lines(window, cx, |lines| {
10432 let mut seen = HashSet::default();
10433 lines.retain(|line| seen.insert(line.to_lowercase()));
10434 })
10435 }
10436
10437 pub fn unique_lines_case_sensitive(
10438 &mut self,
10439 _: &UniqueLinesCaseSensitive,
10440 window: &mut Window,
10441 cx: &mut Context<Self>,
10442 ) {
10443 self.manipulate_immutable_lines(window, cx, |lines| {
10444 let mut seen = HashSet::default();
10445 lines.retain(|line| seen.insert(*line));
10446 })
10447 }
10448
10449 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10450 let snapshot = self.buffer.read(cx).snapshot(cx);
10451 for selection in self.selections.disjoint_anchors().iter() {
10452 if snapshot
10453 .language_at(selection.start)
10454 .and_then(|lang| lang.config().wrap_characters.as_ref())
10455 .is_some()
10456 {
10457 return true;
10458 }
10459 }
10460 false
10461 }
10462
10463 fn wrap_selections_in_tag(
10464 &mut self,
10465 _: &WrapSelectionsInTag,
10466 window: &mut Window,
10467 cx: &mut Context<Self>,
10468 ) {
10469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10470
10471 let snapshot = self.buffer.read(cx).snapshot(cx);
10472
10473 let mut edits = Vec::new();
10474 let mut boundaries = Vec::new();
10475
10476 for selection in self.selections.all::<Point>(cx).iter() {
10477 let Some(wrap_config) = snapshot
10478 .language_at(selection.start)
10479 .and_then(|lang| lang.config().wrap_characters.clone())
10480 else {
10481 continue;
10482 };
10483
10484 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10485 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10486
10487 let start_before = snapshot.anchor_before(selection.start);
10488 let end_after = snapshot.anchor_after(selection.end);
10489
10490 edits.push((start_before..start_before, open_tag));
10491 edits.push((end_after..end_after, close_tag));
10492
10493 boundaries.push((
10494 start_before,
10495 end_after,
10496 wrap_config.start_prefix.len(),
10497 wrap_config.end_suffix.len(),
10498 ));
10499 }
10500
10501 if edits.is_empty() {
10502 return;
10503 }
10504
10505 self.transact(window, cx, |this, window, cx| {
10506 let buffer = this.buffer.update(cx, |buffer, cx| {
10507 buffer.edit(edits, None, cx);
10508 buffer.snapshot(cx)
10509 });
10510
10511 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10512 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10513 boundaries.into_iter()
10514 {
10515 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10516 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10517 new_selections.push(open_offset..open_offset);
10518 new_selections.push(close_offset..close_offset);
10519 }
10520
10521 this.change_selections(Default::default(), window, cx, |s| {
10522 s.select_ranges(new_selections);
10523 });
10524
10525 this.request_autoscroll(Autoscroll::fit(), cx);
10526 });
10527 }
10528
10529 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10530 let Some(project) = self.project.clone() else {
10531 return;
10532 };
10533 self.reload(project, window, cx)
10534 .detach_and_notify_err(window, cx);
10535 }
10536
10537 pub fn restore_file(
10538 &mut self,
10539 _: &::git::RestoreFile,
10540 window: &mut Window,
10541 cx: &mut Context<Self>,
10542 ) {
10543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10544 let mut buffer_ids = HashSet::default();
10545 let snapshot = self.buffer().read(cx).snapshot(cx);
10546 for selection in self.selections.all::<usize>(cx) {
10547 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10548 }
10549
10550 let buffer = self.buffer().read(cx);
10551 let ranges = buffer_ids
10552 .into_iter()
10553 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10554 .collect::<Vec<_>>();
10555
10556 self.restore_hunks_in_ranges(ranges, window, cx);
10557 }
10558
10559 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10560 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10561 let selections = self
10562 .selections
10563 .all(cx)
10564 .into_iter()
10565 .map(|s| s.range())
10566 .collect();
10567 self.restore_hunks_in_ranges(selections, window, cx);
10568 }
10569
10570 pub fn restore_hunks_in_ranges(
10571 &mut self,
10572 ranges: Vec<Range<Point>>,
10573 window: &mut Window,
10574 cx: &mut Context<Editor>,
10575 ) {
10576 let mut revert_changes = HashMap::default();
10577 let chunk_by = self
10578 .snapshot(window, cx)
10579 .hunks_for_ranges(ranges)
10580 .into_iter()
10581 .chunk_by(|hunk| hunk.buffer_id);
10582 for (buffer_id, hunks) in &chunk_by {
10583 let hunks = hunks.collect::<Vec<_>>();
10584 for hunk in &hunks {
10585 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10586 }
10587 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10588 }
10589 drop(chunk_by);
10590 if !revert_changes.is_empty() {
10591 self.transact(window, cx, |editor, window, cx| {
10592 editor.restore(revert_changes, window, cx);
10593 });
10594 }
10595 }
10596
10597 pub fn open_active_item_in_terminal(
10598 &mut self,
10599 _: &OpenInTerminal,
10600 window: &mut Window,
10601 cx: &mut Context<Self>,
10602 ) {
10603 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10604 let project_path = buffer.read(cx).project_path(cx)?;
10605 let project = self.project()?.read(cx);
10606 let entry = project.entry_for_path(&project_path, cx)?;
10607 let parent = match &entry.canonical_path {
10608 Some(canonical_path) => canonical_path.to_path_buf(),
10609 None => project.absolute_path(&project_path, cx)?,
10610 }
10611 .parent()?
10612 .to_path_buf();
10613 Some(parent)
10614 }) {
10615 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10616 }
10617 }
10618
10619 fn set_breakpoint_context_menu(
10620 &mut self,
10621 display_row: DisplayRow,
10622 position: Option<Anchor>,
10623 clicked_point: gpui::Point<Pixels>,
10624 window: &mut Window,
10625 cx: &mut Context<Self>,
10626 ) {
10627 let source = self
10628 .buffer
10629 .read(cx)
10630 .snapshot(cx)
10631 .anchor_before(Point::new(display_row.0, 0u32));
10632
10633 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10634
10635 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10636 self,
10637 source,
10638 clicked_point,
10639 context_menu,
10640 window,
10641 cx,
10642 );
10643 }
10644
10645 fn add_edit_breakpoint_block(
10646 &mut self,
10647 anchor: Anchor,
10648 breakpoint: &Breakpoint,
10649 edit_action: BreakpointPromptEditAction,
10650 window: &mut Window,
10651 cx: &mut Context<Self>,
10652 ) {
10653 let weak_editor = cx.weak_entity();
10654 let bp_prompt = cx.new(|cx| {
10655 BreakpointPromptEditor::new(
10656 weak_editor,
10657 anchor,
10658 breakpoint.clone(),
10659 edit_action,
10660 window,
10661 cx,
10662 )
10663 });
10664
10665 let height = bp_prompt.update(cx, |this, cx| {
10666 this.prompt
10667 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10668 });
10669 let cloned_prompt = bp_prompt.clone();
10670 let blocks = vec![BlockProperties {
10671 style: BlockStyle::Sticky,
10672 placement: BlockPlacement::Above(anchor),
10673 height: Some(height),
10674 render: Arc::new(move |cx| {
10675 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10676 cloned_prompt.clone().into_any_element()
10677 }),
10678 priority: 0,
10679 }];
10680
10681 let focus_handle = bp_prompt.focus_handle(cx);
10682 window.focus(&focus_handle);
10683
10684 let block_ids = self.insert_blocks(blocks, None, cx);
10685 bp_prompt.update(cx, |prompt, _| {
10686 prompt.add_block_ids(block_ids);
10687 });
10688 }
10689
10690 pub(crate) fn breakpoint_at_row(
10691 &self,
10692 row: u32,
10693 window: &mut Window,
10694 cx: &mut Context<Self>,
10695 ) -> Option<(Anchor, Breakpoint)> {
10696 let snapshot = self.snapshot(window, cx);
10697 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10698
10699 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10700 }
10701
10702 pub(crate) fn breakpoint_at_anchor(
10703 &self,
10704 breakpoint_position: Anchor,
10705 snapshot: &EditorSnapshot,
10706 cx: &mut Context<Self>,
10707 ) -> Option<(Anchor, Breakpoint)> {
10708 let buffer = self
10709 .buffer
10710 .read(cx)
10711 .buffer_for_anchor(breakpoint_position, cx)?;
10712
10713 let enclosing_excerpt = breakpoint_position.excerpt_id;
10714 let buffer_snapshot = buffer.read(cx).snapshot();
10715
10716 let row = buffer_snapshot
10717 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10718 .row;
10719
10720 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10721 let anchor_end = snapshot
10722 .buffer_snapshot
10723 .anchor_after(Point::new(row, line_len));
10724
10725 self.breakpoint_store
10726 .as_ref()?
10727 .read_with(cx, |breakpoint_store, cx| {
10728 breakpoint_store
10729 .breakpoints(
10730 &buffer,
10731 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10732 &buffer_snapshot,
10733 cx,
10734 )
10735 .next()
10736 .and_then(|(bp, _)| {
10737 let breakpoint_row = buffer_snapshot
10738 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10739 .row;
10740
10741 if breakpoint_row == row {
10742 snapshot
10743 .buffer_snapshot
10744 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10745 .map(|position| (position, bp.bp.clone()))
10746 } else {
10747 None
10748 }
10749 })
10750 })
10751 }
10752
10753 pub fn edit_log_breakpoint(
10754 &mut self,
10755 _: &EditLogBreakpoint,
10756 window: &mut Window,
10757 cx: &mut Context<Self>,
10758 ) {
10759 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10760 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10761 message: None,
10762 state: BreakpointState::Enabled,
10763 condition: None,
10764 hit_condition: None,
10765 });
10766
10767 self.add_edit_breakpoint_block(
10768 anchor,
10769 &breakpoint,
10770 BreakpointPromptEditAction::Log,
10771 window,
10772 cx,
10773 );
10774 }
10775 }
10776
10777 fn breakpoints_at_cursors(
10778 &self,
10779 window: &mut Window,
10780 cx: &mut Context<Self>,
10781 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10782 let snapshot = self.snapshot(window, cx);
10783 let cursors = self
10784 .selections
10785 .disjoint_anchors()
10786 .iter()
10787 .map(|selection| {
10788 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10789
10790 let breakpoint_position = self
10791 .breakpoint_at_row(cursor_position.row, window, cx)
10792 .map(|bp| bp.0)
10793 .unwrap_or_else(|| {
10794 snapshot
10795 .display_snapshot
10796 .buffer_snapshot
10797 .anchor_after(Point::new(cursor_position.row, 0))
10798 });
10799
10800 let breakpoint = self
10801 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10802 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10803
10804 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10805 })
10806 // 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.
10807 .collect::<HashMap<Anchor, _>>();
10808
10809 cursors.into_iter().collect()
10810 }
10811
10812 pub fn enable_breakpoint(
10813 &mut self,
10814 _: &crate::actions::EnableBreakpoint,
10815 window: &mut Window,
10816 cx: &mut Context<Self>,
10817 ) {
10818 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10819 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10820 continue;
10821 };
10822 self.edit_breakpoint_at_anchor(
10823 anchor,
10824 breakpoint,
10825 BreakpointEditAction::InvertState,
10826 cx,
10827 );
10828 }
10829 }
10830
10831 pub fn disable_breakpoint(
10832 &mut self,
10833 _: &crate::actions::DisableBreakpoint,
10834 window: &mut Window,
10835 cx: &mut Context<Self>,
10836 ) {
10837 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10838 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10839 continue;
10840 };
10841 self.edit_breakpoint_at_anchor(
10842 anchor,
10843 breakpoint,
10844 BreakpointEditAction::InvertState,
10845 cx,
10846 );
10847 }
10848 }
10849
10850 pub fn toggle_breakpoint(
10851 &mut self,
10852 _: &crate::actions::ToggleBreakpoint,
10853 window: &mut Window,
10854 cx: &mut Context<Self>,
10855 ) {
10856 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10857 if let Some(breakpoint) = breakpoint {
10858 self.edit_breakpoint_at_anchor(
10859 anchor,
10860 breakpoint,
10861 BreakpointEditAction::Toggle,
10862 cx,
10863 );
10864 } else {
10865 self.edit_breakpoint_at_anchor(
10866 anchor,
10867 Breakpoint::new_standard(),
10868 BreakpointEditAction::Toggle,
10869 cx,
10870 );
10871 }
10872 }
10873 }
10874
10875 pub fn edit_breakpoint_at_anchor(
10876 &mut self,
10877 breakpoint_position: Anchor,
10878 breakpoint: Breakpoint,
10879 edit_action: BreakpointEditAction,
10880 cx: &mut Context<Self>,
10881 ) {
10882 let Some(breakpoint_store) = &self.breakpoint_store else {
10883 return;
10884 };
10885
10886 let Some(buffer) = self
10887 .buffer
10888 .read(cx)
10889 .buffer_for_anchor(breakpoint_position, cx)
10890 else {
10891 return;
10892 };
10893
10894 breakpoint_store.update(cx, |breakpoint_store, cx| {
10895 breakpoint_store.toggle_breakpoint(
10896 buffer,
10897 BreakpointWithPosition {
10898 position: breakpoint_position.text_anchor,
10899 bp: breakpoint,
10900 },
10901 edit_action,
10902 cx,
10903 );
10904 });
10905
10906 cx.notify();
10907 }
10908
10909 #[cfg(any(test, feature = "test-support"))]
10910 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10911 self.breakpoint_store.clone()
10912 }
10913
10914 pub fn prepare_restore_change(
10915 &self,
10916 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10917 hunk: &MultiBufferDiffHunk,
10918 cx: &mut App,
10919 ) -> Option<()> {
10920 if hunk.is_created_file() {
10921 return None;
10922 }
10923 let buffer = self.buffer.read(cx);
10924 let diff = buffer.diff_for(hunk.buffer_id)?;
10925 let buffer = buffer.buffer(hunk.buffer_id)?;
10926 let buffer = buffer.read(cx);
10927 let original_text = diff
10928 .read(cx)
10929 .base_text()
10930 .as_rope()
10931 .slice(hunk.diff_base_byte_range.clone());
10932 let buffer_snapshot = buffer.snapshot();
10933 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10934 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10935 probe
10936 .0
10937 .start
10938 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10939 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10940 }) {
10941 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10942 Some(())
10943 } else {
10944 None
10945 }
10946 }
10947
10948 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10949 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10950 }
10951
10952 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10953 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10954 }
10955
10956 fn manipulate_lines<M>(
10957 &mut self,
10958 window: &mut Window,
10959 cx: &mut Context<Self>,
10960 mut manipulate: M,
10961 ) where
10962 M: FnMut(&str) -> LineManipulationResult,
10963 {
10964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10965
10966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10967 let buffer = self.buffer.read(cx).snapshot(cx);
10968
10969 let mut edits = Vec::new();
10970
10971 let selections = self.selections.all::<Point>(cx);
10972 let mut selections = selections.iter().peekable();
10973 let mut contiguous_row_selections = Vec::new();
10974 let mut new_selections = Vec::new();
10975 let mut added_lines = 0;
10976 let mut removed_lines = 0;
10977
10978 while let Some(selection) = selections.next() {
10979 let (start_row, end_row) = consume_contiguous_rows(
10980 &mut contiguous_row_selections,
10981 selection,
10982 &display_map,
10983 &mut selections,
10984 );
10985
10986 let start_point = Point::new(start_row.0, 0);
10987 let end_point = Point::new(
10988 end_row.previous_row().0,
10989 buffer.line_len(end_row.previous_row()),
10990 );
10991 let text = buffer
10992 .text_for_range(start_point..end_point)
10993 .collect::<String>();
10994
10995 let LineManipulationResult {
10996 new_text,
10997 line_count_before,
10998 line_count_after,
10999 } = manipulate(&text);
11000
11001 edits.push((start_point..end_point, new_text));
11002
11003 // Selections must change based on added and removed line count
11004 let start_row =
11005 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11006 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11007 new_selections.push(Selection {
11008 id: selection.id,
11009 start: start_row,
11010 end: end_row,
11011 goal: SelectionGoal::None,
11012 reversed: selection.reversed,
11013 });
11014
11015 if line_count_after > line_count_before {
11016 added_lines += line_count_after - line_count_before;
11017 } else if line_count_before > line_count_after {
11018 removed_lines += line_count_before - line_count_after;
11019 }
11020 }
11021
11022 self.transact(window, cx, |this, window, cx| {
11023 let buffer = this.buffer.update(cx, |buffer, cx| {
11024 buffer.edit(edits, None, cx);
11025 buffer.snapshot(cx)
11026 });
11027
11028 // Recalculate offsets on newly edited buffer
11029 let new_selections = new_selections
11030 .iter()
11031 .map(|s| {
11032 let start_point = Point::new(s.start.0, 0);
11033 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11034 Selection {
11035 id: s.id,
11036 start: buffer.point_to_offset(start_point),
11037 end: buffer.point_to_offset(end_point),
11038 goal: s.goal,
11039 reversed: s.reversed,
11040 }
11041 })
11042 .collect();
11043
11044 this.change_selections(Default::default(), window, cx, |s| {
11045 s.select(new_selections);
11046 });
11047
11048 this.request_autoscroll(Autoscroll::fit(), cx);
11049 });
11050 }
11051
11052 fn manipulate_immutable_lines<Fn>(
11053 &mut self,
11054 window: &mut Window,
11055 cx: &mut Context<Self>,
11056 mut callback: Fn,
11057 ) where
11058 Fn: FnMut(&mut Vec<&str>),
11059 {
11060 self.manipulate_lines(window, cx, |text| {
11061 let mut lines: Vec<&str> = text.split('\n').collect();
11062 let line_count_before = lines.len();
11063
11064 callback(&mut lines);
11065
11066 LineManipulationResult {
11067 new_text: lines.join("\n"),
11068 line_count_before,
11069 line_count_after: lines.len(),
11070 }
11071 });
11072 }
11073
11074 fn manipulate_mutable_lines<Fn>(
11075 &mut self,
11076 window: &mut Window,
11077 cx: &mut Context<Self>,
11078 mut callback: Fn,
11079 ) where
11080 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11081 {
11082 self.manipulate_lines(window, cx, |text| {
11083 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11084 let line_count_before = lines.len();
11085
11086 callback(&mut lines);
11087
11088 LineManipulationResult {
11089 new_text: lines.join("\n"),
11090 line_count_before,
11091 line_count_after: lines.len(),
11092 }
11093 });
11094 }
11095
11096 pub fn convert_indentation_to_spaces(
11097 &mut self,
11098 _: &ConvertIndentationToSpaces,
11099 window: &mut Window,
11100 cx: &mut Context<Self>,
11101 ) {
11102 let settings = self.buffer.read(cx).language_settings(cx);
11103 let tab_size = settings.tab_size.get() as usize;
11104
11105 self.manipulate_mutable_lines(window, cx, |lines| {
11106 // Allocates a reasonably sized scratch buffer once for the whole loop
11107 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11108 // Avoids recomputing spaces that could be inserted many times
11109 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11110 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11111 .collect();
11112
11113 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11114 let mut chars = line.as_ref().chars();
11115 let mut col = 0;
11116 let mut changed = false;
11117
11118 for ch in chars.by_ref() {
11119 match ch {
11120 ' ' => {
11121 reindented_line.push(' ');
11122 col += 1;
11123 }
11124 '\t' => {
11125 // \t are converted to spaces depending on the current column
11126 let spaces_len = tab_size - (col % tab_size);
11127 reindented_line.extend(&space_cache[spaces_len - 1]);
11128 col += spaces_len;
11129 changed = true;
11130 }
11131 _ => {
11132 // If we dont append before break, the character is consumed
11133 reindented_line.push(ch);
11134 break;
11135 }
11136 }
11137 }
11138
11139 if !changed {
11140 reindented_line.clear();
11141 continue;
11142 }
11143 // Append the rest of the line and replace old reference with new one
11144 reindented_line.extend(chars);
11145 *line = Cow::Owned(reindented_line.clone());
11146 reindented_line.clear();
11147 }
11148 });
11149 }
11150
11151 pub fn convert_indentation_to_tabs(
11152 &mut self,
11153 _: &ConvertIndentationToTabs,
11154 window: &mut Window,
11155 cx: &mut Context<Self>,
11156 ) {
11157 let settings = self.buffer.read(cx).language_settings(cx);
11158 let tab_size = settings.tab_size.get() as usize;
11159
11160 self.manipulate_mutable_lines(window, cx, |lines| {
11161 // Allocates a reasonably sized buffer once for the whole loop
11162 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11163 // Avoids recomputing spaces that could be inserted many times
11164 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11165 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11166 .collect();
11167
11168 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11169 let mut chars = line.chars();
11170 let mut spaces_count = 0;
11171 let mut first_non_indent_char = None;
11172 let mut changed = false;
11173
11174 for ch in chars.by_ref() {
11175 match ch {
11176 ' ' => {
11177 // Keep track of spaces. Append \t when we reach tab_size
11178 spaces_count += 1;
11179 changed = true;
11180 if spaces_count == tab_size {
11181 reindented_line.push('\t');
11182 spaces_count = 0;
11183 }
11184 }
11185 '\t' => {
11186 reindented_line.push('\t');
11187 spaces_count = 0;
11188 }
11189 _ => {
11190 // Dont append it yet, we might have remaining spaces
11191 first_non_indent_char = Some(ch);
11192 break;
11193 }
11194 }
11195 }
11196
11197 if !changed {
11198 reindented_line.clear();
11199 continue;
11200 }
11201 // Remaining spaces that didn't make a full tab stop
11202 if spaces_count > 0 {
11203 reindented_line.extend(&space_cache[spaces_count - 1]);
11204 }
11205 // If we consume an extra character that was not indentation, add it back
11206 if let Some(extra_char) = first_non_indent_char {
11207 reindented_line.push(extra_char);
11208 }
11209 // Append the rest of the line and replace old reference with new one
11210 reindented_line.extend(chars);
11211 *line = Cow::Owned(reindented_line.clone());
11212 reindented_line.clear();
11213 }
11214 });
11215 }
11216
11217 pub fn convert_to_upper_case(
11218 &mut self,
11219 _: &ConvertToUpperCase,
11220 window: &mut Window,
11221 cx: &mut Context<Self>,
11222 ) {
11223 self.manipulate_text(window, cx, |text| text.to_uppercase())
11224 }
11225
11226 pub fn convert_to_lower_case(
11227 &mut self,
11228 _: &ConvertToLowerCase,
11229 window: &mut Window,
11230 cx: &mut Context<Self>,
11231 ) {
11232 self.manipulate_text(window, cx, |text| text.to_lowercase())
11233 }
11234
11235 pub fn convert_to_title_case(
11236 &mut self,
11237 _: &ConvertToTitleCase,
11238 window: &mut Window,
11239 cx: &mut Context<Self>,
11240 ) {
11241 self.manipulate_text(window, cx, |text| {
11242 text.split('\n')
11243 .map(|line| line.to_case(Case::Title))
11244 .join("\n")
11245 })
11246 }
11247
11248 pub fn convert_to_snake_case(
11249 &mut self,
11250 _: &ConvertToSnakeCase,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 ) {
11254 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11255 }
11256
11257 pub fn convert_to_kebab_case(
11258 &mut self,
11259 _: &ConvertToKebabCase,
11260 window: &mut Window,
11261 cx: &mut Context<Self>,
11262 ) {
11263 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11264 }
11265
11266 pub fn convert_to_upper_camel_case(
11267 &mut self,
11268 _: &ConvertToUpperCamelCase,
11269 window: &mut Window,
11270 cx: &mut Context<Self>,
11271 ) {
11272 self.manipulate_text(window, cx, |text| {
11273 text.split('\n')
11274 .map(|line| line.to_case(Case::UpperCamel))
11275 .join("\n")
11276 })
11277 }
11278
11279 pub fn convert_to_lower_camel_case(
11280 &mut self,
11281 _: &ConvertToLowerCamelCase,
11282 window: &mut Window,
11283 cx: &mut Context<Self>,
11284 ) {
11285 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11286 }
11287
11288 pub fn convert_to_opposite_case(
11289 &mut self,
11290 _: &ConvertToOppositeCase,
11291 window: &mut Window,
11292 cx: &mut Context<Self>,
11293 ) {
11294 self.manipulate_text(window, cx, |text| {
11295 text.chars()
11296 .fold(String::with_capacity(text.len()), |mut t, c| {
11297 if c.is_uppercase() {
11298 t.extend(c.to_lowercase());
11299 } else {
11300 t.extend(c.to_uppercase());
11301 }
11302 t
11303 })
11304 })
11305 }
11306
11307 pub fn convert_to_sentence_case(
11308 &mut self,
11309 _: &ConvertToSentenceCase,
11310 window: &mut Window,
11311 cx: &mut Context<Self>,
11312 ) {
11313 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11314 }
11315
11316 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11317 self.manipulate_text(window, cx, |text| {
11318 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11319 if has_upper_case_characters {
11320 text.to_lowercase()
11321 } else {
11322 text.to_uppercase()
11323 }
11324 })
11325 }
11326
11327 pub fn convert_to_rot13(
11328 &mut self,
11329 _: &ConvertToRot13,
11330 window: &mut Window,
11331 cx: &mut Context<Self>,
11332 ) {
11333 self.manipulate_text(window, cx, |text| {
11334 text.chars()
11335 .map(|c| match c {
11336 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11337 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11338 _ => c,
11339 })
11340 .collect()
11341 })
11342 }
11343
11344 pub fn convert_to_rot47(
11345 &mut self,
11346 _: &ConvertToRot47,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 ) {
11350 self.manipulate_text(window, cx, |text| {
11351 text.chars()
11352 .map(|c| {
11353 let code_point = c as u32;
11354 if code_point >= 33 && code_point <= 126 {
11355 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11356 }
11357 c
11358 })
11359 .collect()
11360 })
11361 }
11362
11363 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11364 where
11365 Fn: FnMut(&str) -> String,
11366 {
11367 let buffer = self.buffer.read(cx).snapshot(cx);
11368
11369 let mut new_selections = Vec::new();
11370 let mut edits = Vec::new();
11371 let mut selection_adjustment = 0i32;
11372
11373 for selection in self.selections.all::<usize>(cx) {
11374 let selection_is_empty = selection.is_empty();
11375
11376 let (start, end) = if selection_is_empty {
11377 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11378 (word_range.start, word_range.end)
11379 } else {
11380 (selection.start, selection.end)
11381 };
11382
11383 let text = buffer.text_for_range(start..end).collect::<String>();
11384 let old_length = text.len() as i32;
11385 let text = callback(&text);
11386
11387 new_selections.push(Selection {
11388 start: (start as i32 - selection_adjustment) as usize,
11389 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11390 goal: SelectionGoal::None,
11391 ..selection
11392 });
11393
11394 selection_adjustment += old_length - text.len() as i32;
11395
11396 edits.push((start..end, text));
11397 }
11398
11399 self.transact(window, cx, |this, window, cx| {
11400 this.buffer.update(cx, |buffer, cx| {
11401 buffer.edit(edits, None, cx);
11402 });
11403
11404 this.change_selections(Default::default(), window, cx, |s| {
11405 s.select(new_selections);
11406 });
11407
11408 this.request_autoscroll(Autoscroll::fit(), cx);
11409 });
11410 }
11411
11412 pub fn move_selection_on_drop(
11413 &mut self,
11414 selection: &Selection<Anchor>,
11415 target: DisplayPoint,
11416 is_cut: bool,
11417 window: &mut Window,
11418 cx: &mut Context<Self>,
11419 ) {
11420 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11421 let buffer = &display_map.buffer_snapshot;
11422 let mut edits = Vec::new();
11423 let insert_point = display_map
11424 .clip_point(target, Bias::Left)
11425 .to_point(&display_map);
11426 let text = buffer
11427 .text_for_range(selection.start..selection.end)
11428 .collect::<String>();
11429 if is_cut {
11430 edits.push(((selection.start..selection.end), String::new()));
11431 }
11432 let insert_anchor = buffer.anchor_before(insert_point);
11433 edits.push(((insert_anchor..insert_anchor), text));
11434 let last_edit_start = insert_anchor.bias_left(buffer);
11435 let last_edit_end = insert_anchor.bias_right(buffer);
11436 self.transact(window, cx, |this, window, cx| {
11437 this.buffer.update(cx, |buffer, cx| {
11438 buffer.edit(edits, None, cx);
11439 });
11440 this.change_selections(Default::default(), window, cx, |s| {
11441 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11442 });
11443 });
11444 }
11445
11446 pub fn clear_selection_drag_state(&mut self) {
11447 self.selection_drag_state = SelectionDragState::None;
11448 }
11449
11450 pub fn duplicate(
11451 &mut self,
11452 upwards: bool,
11453 whole_lines: bool,
11454 window: &mut Window,
11455 cx: &mut Context<Self>,
11456 ) {
11457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11458
11459 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11460 let buffer = &display_map.buffer_snapshot;
11461 let selections = self.selections.all::<Point>(cx);
11462
11463 let mut edits = Vec::new();
11464 let mut selections_iter = selections.iter().peekable();
11465 while let Some(selection) = selections_iter.next() {
11466 let mut rows = selection.spanned_rows(false, &display_map);
11467 // duplicate line-wise
11468 if whole_lines || selection.start == selection.end {
11469 // Avoid duplicating the same lines twice.
11470 while let Some(next_selection) = selections_iter.peek() {
11471 let next_rows = next_selection.spanned_rows(false, &display_map);
11472 if next_rows.start < rows.end {
11473 rows.end = next_rows.end;
11474 selections_iter.next().unwrap();
11475 } else {
11476 break;
11477 }
11478 }
11479
11480 // Copy the text from the selected row region and splice it either at the start
11481 // or end of the region.
11482 let start = Point::new(rows.start.0, 0);
11483 let end = Point::new(
11484 rows.end.previous_row().0,
11485 buffer.line_len(rows.end.previous_row()),
11486 );
11487 let text = buffer
11488 .text_for_range(start..end)
11489 .chain(Some("\n"))
11490 .collect::<String>();
11491 let insert_location = if upwards {
11492 Point::new(rows.end.0, 0)
11493 } else {
11494 start
11495 };
11496 edits.push((insert_location..insert_location, text));
11497 } else {
11498 // duplicate character-wise
11499 let start = selection.start;
11500 let end = selection.end;
11501 let text = buffer.text_for_range(start..end).collect::<String>();
11502 edits.push((selection.end..selection.end, text));
11503 }
11504 }
11505
11506 self.transact(window, cx, |this, _, cx| {
11507 this.buffer.update(cx, |buffer, cx| {
11508 buffer.edit(edits, None, cx);
11509 });
11510
11511 this.request_autoscroll(Autoscroll::fit(), cx);
11512 });
11513 }
11514
11515 pub fn duplicate_line_up(
11516 &mut self,
11517 _: &DuplicateLineUp,
11518 window: &mut Window,
11519 cx: &mut Context<Self>,
11520 ) {
11521 self.duplicate(true, true, window, cx);
11522 }
11523
11524 pub fn duplicate_line_down(
11525 &mut self,
11526 _: &DuplicateLineDown,
11527 window: &mut Window,
11528 cx: &mut Context<Self>,
11529 ) {
11530 self.duplicate(false, true, window, cx);
11531 }
11532
11533 pub fn duplicate_selection(
11534 &mut self,
11535 _: &DuplicateSelection,
11536 window: &mut Window,
11537 cx: &mut Context<Self>,
11538 ) {
11539 self.duplicate(false, false, window, cx);
11540 }
11541
11542 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11544 if self.mode.is_single_line() {
11545 cx.propagate();
11546 return;
11547 }
11548
11549 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11550 let buffer = self.buffer.read(cx).snapshot(cx);
11551
11552 let mut edits = Vec::new();
11553 let mut unfold_ranges = Vec::new();
11554 let mut refold_creases = Vec::new();
11555
11556 let selections = self.selections.all::<Point>(cx);
11557 let mut selections = selections.iter().peekable();
11558 let mut contiguous_row_selections = Vec::new();
11559 let mut new_selections = Vec::new();
11560
11561 while let Some(selection) = selections.next() {
11562 // Find all the selections that span a contiguous row range
11563 let (start_row, end_row) = consume_contiguous_rows(
11564 &mut contiguous_row_selections,
11565 selection,
11566 &display_map,
11567 &mut selections,
11568 );
11569
11570 // Move the text spanned by the row range to be before the line preceding the row range
11571 if start_row.0 > 0 {
11572 let range_to_move = Point::new(
11573 start_row.previous_row().0,
11574 buffer.line_len(start_row.previous_row()),
11575 )
11576 ..Point::new(
11577 end_row.previous_row().0,
11578 buffer.line_len(end_row.previous_row()),
11579 );
11580 let insertion_point = display_map
11581 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11582 .0;
11583
11584 // Don't move lines across excerpts
11585 if buffer
11586 .excerpt_containing(insertion_point..range_to_move.end)
11587 .is_some()
11588 {
11589 let text = buffer
11590 .text_for_range(range_to_move.clone())
11591 .flat_map(|s| s.chars())
11592 .skip(1)
11593 .chain(['\n'])
11594 .collect::<String>();
11595
11596 edits.push((
11597 buffer.anchor_after(range_to_move.start)
11598 ..buffer.anchor_before(range_to_move.end),
11599 String::new(),
11600 ));
11601 let insertion_anchor = buffer.anchor_after(insertion_point);
11602 edits.push((insertion_anchor..insertion_anchor, text));
11603
11604 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11605
11606 // Move selections up
11607 new_selections.extend(contiguous_row_selections.drain(..).map(
11608 |mut selection| {
11609 selection.start.row -= row_delta;
11610 selection.end.row -= row_delta;
11611 selection
11612 },
11613 ));
11614
11615 // Move folds up
11616 unfold_ranges.push(range_to_move.clone());
11617 for fold in display_map.folds_in_range(
11618 buffer.anchor_before(range_to_move.start)
11619 ..buffer.anchor_after(range_to_move.end),
11620 ) {
11621 let mut start = fold.range.start.to_point(&buffer);
11622 let mut end = fold.range.end.to_point(&buffer);
11623 start.row -= row_delta;
11624 end.row -= row_delta;
11625 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11626 }
11627 }
11628 }
11629
11630 // If we didn't move line(s), preserve the existing selections
11631 new_selections.append(&mut contiguous_row_selections);
11632 }
11633
11634 self.transact(window, cx, |this, window, cx| {
11635 this.unfold_ranges(&unfold_ranges, true, true, cx);
11636 this.buffer.update(cx, |buffer, cx| {
11637 for (range, text) in edits {
11638 buffer.edit([(range, text)], None, cx);
11639 }
11640 });
11641 this.fold_creases(refold_creases, true, window, cx);
11642 this.change_selections(Default::default(), window, cx, |s| {
11643 s.select(new_selections);
11644 })
11645 });
11646 }
11647
11648 pub fn move_line_down(
11649 &mut self,
11650 _: &MoveLineDown,
11651 window: &mut Window,
11652 cx: &mut Context<Self>,
11653 ) {
11654 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11655 if self.mode.is_single_line() {
11656 cx.propagate();
11657 return;
11658 }
11659
11660 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11661 let buffer = self.buffer.read(cx).snapshot(cx);
11662
11663 let mut edits = Vec::new();
11664 let mut unfold_ranges = Vec::new();
11665 let mut refold_creases = Vec::new();
11666
11667 let selections = self.selections.all::<Point>(cx);
11668 let mut selections = selections.iter().peekable();
11669 let mut contiguous_row_selections = Vec::new();
11670 let mut new_selections = Vec::new();
11671
11672 while let Some(selection) = selections.next() {
11673 // Find all the selections that span a contiguous row range
11674 let (start_row, end_row) = consume_contiguous_rows(
11675 &mut contiguous_row_selections,
11676 selection,
11677 &display_map,
11678 &mut selections,
11679 );
11680
11681 // Move the text spanned by the row range to be after the last line of the row range
11682 if end_row.0 <= buffer.max_point().row {
11683 let range_to_move =
11684 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11685 let insertion_point = display_map
11686 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11687 .0;
11688
11689 // Don't move lines across excerpt boundaries
11690 if buffer
11691 .excerpt_containing(range_to_move.start..insertion_point)
11692 .is_some()
11693 {
11694 let mut text = String::from("\n");
11695 text.extend(buffer.text_for_range(range_to_move.clone()));
11696 text.pop(); // Drop trailing newline
11697 edits.push((
11698 buffer.anchor_after(range_to_move.start)
11699 ..buffer.anchor_before(range_to_move.end),
11700 String::new(),
11701 ));
11702 let insertion_anchor = buffer.anchor_after(insertion_point);
11703 edits.push((insertion_anchor..insertion_anchor, text));
11704
11705 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11706
11707 // Move selections down
11708 new_selections.extend(contiguous_row_selections.drain(..).map(
11709 |mut selection| {
11710 selection.start.row += row_delta;
11711 selection.end.row += row_delta;
11712 selection
11713 },
11714 ));
11715
11716 // Move folds down
11717 unfold_ranges.push(range_to_move.clone());
11718 for fold in display_map.folds_in_range(
11719 buffer.anchor_before(range_to_move.start)
11720 ..buffer.anchor_after(range_to_move.end),
11721 ) {
11722 let mut start = fold.range.start.to_point(&buffer);
11723 let mut end = fold.range.end.to_point(&buffer);
11724 start.row += row_delta;
11725 end.row += row_delta;
11726 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11727 }
11728 }
11729 }
11730
11731 // If we didn't move line(s), preserve the existing selections
11732 new_selections.append(&mut contiguous_row_selections);
11733 }
11734
11735 self.transact(window, cx, |this, window, cx| {
11736 this.unfold_ranges(&unfold_ranges, true, true, cx);
11737 this.buffer.update(cx, |buffer, cx| {
11738 for (range, text) in edits {
11739 buffer.edit([(range, text)], None, cx);
11740 }
11741 });
11742 this.fold_creases(refold_creases, true, window, cx);
11743 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11744 });
11745 }
11746
11747 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11748 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11749 let text_layout_details = &self.text_layout_details(window);
11750 self.transact(window, cx, |this, window, cx| {
11751 let edits = this.change_selections(Default::default(), window, cx, |s| {
11752 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11753 s.move_with(|display_map, selection| {
11754 if !selection.is_empty() {
11755 return;
11756 }
11757
11758 let mut head = selection.head();
11759 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11760 if head.column() == display_map.line_len(head.row()) {
11761 transpose_offset = display_map
11762 .buffer_snapshot
11763 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11764 }
11765
11766 if transpose_offset == 0 {
11767 return;
11768 }
11769
11770 *head.column_mut() += 1;
11771 head = display_map.clip_point(head, Bias::Right);
11772 let goal = SelectionGoal::HorizontalPosition(
11773 display_map
11774 .x_for_display_point(head, text_layout_details)
11775 .into(),
11776 );
11777 selection.collapse_to(head, goal);
11778
11779 let transpose_start = display_map
11780 .buffer_snapshot
11781 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11782 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11783 let transpose_end = display_map
11784 .buffer_snapshot
11785 .clip_offset(transpose_offset + 1, Bias::Right);
11786 if let Some(ch) =
11787 display_map.buffer_snapshot.chars_at(transpose_start).next()
11788 {
11789 edits.push((transpose_start..transpose_offset, String::new()));
11790 edits.push((transpose_end..transpose_end, ch.to_string()));
11791 }
11792 }
11793 });
11794 edits
11795 });
11796 this.buffer
11797 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11798 let selections = this.selections.all::<usize>(cx);
11799 this.change_selections(Default::default(), window, cx, |s| {
11800 s.select(selections);
11801 });
11802 });
11803 }
11804
11805 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11806 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11807 if self.mode.is_single_line() {
11808 cx.propagate();
11809 return;
11810 }
11811
11812 self.rewrap_impl(RewrapOptions::default(), cx)
11813 }
11814
11815 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11816 let buffer = self.buffer.read(cx).snapshot(cx);
11817 let selections = self.selections.all::<Point>(cx);
11818
11819 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11820 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11821 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11822 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11823 .peekable();
11824
11825 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11826 row
11827 } else {
11828 return Vec::new();
11829 };
11830
11831 let language_settings = buffer.language_settings_at(selection.head(), cx);
11832 let language_scope = buffer.language_scope_at(selection.head());
11833
11834 let indent_and_prefix_for_row =
11835 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11836 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11837 let (comment_prefix, rewrap_prefix) =
11838 if let Some(language_scope) = &language_scope {
11839 let indent_end = Point::new(row, indent.len);
11840 let comment_prefix = language_scope
11841 .line_comment_prefixes()
11842 .iter()
11843 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11844 .map(|prefix| prefix.to_string());
11845 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11846 let line_text_after_indent = buffer
11847 .text_for_range(indent_end..line_end)
11848 .collect::<String>();
11849 let rewrap_prefix = language_scope
11850 .rewrap_prefixes()
11851 .iter()
11852 .find_map(|prefix_regex| {
11853 prefix_regex.find(&line_text_after_indent).map(|mat| {
11854 if mat.start() == 0 {
11855 Some(mat.as_str().to_string())
11856 } else {
11857 None
11858 }
11859 })
11860 })
11861 .flatten();
11862 (comment_prefix, rewrap_prefix)
11863 } else {
11864 (None, None)
11865 };
11866 (indent, comment_prefix, rewrap_prefix)
11867 };
11868
11869 let mut ranges = Vec::new();
11870 let from_empty_selection = selection.is_empty();
11871
11872 let mut current_range_start = first_row;
11873 let mut prev_row = first_row;
11874 let (
11875 mut current_range_indent,
11876 mut current_range_comment_prefix,
11877 mut current_range_rewrap_prefix,
11878 ) = indent_and_prefix_for_row(first_row);
11879
11880 for row in non_blank_rows_iter.skip(1) {
11881 let has_paragraph_break = row > prev_row + 1;
11882
11883 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11884 indent_and_prefix_for_row(row);
11885
11886 let has_indent_change = row_indent != current_range_indent;
11887 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11888
11889 let has_boundary_change = has_comment_change
11890 || row_rewrap_prefix.is_some()
11891 || (has_indent_change && current_range_comment_prefix.is_some());
11892
11893 if has_paragraph_break || has_boundary_change {
11894 ranges.push((
11895 language_settings.clone(),
11896 Point::new(current_range_start, 0)
11897 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11898 current_range_indent,
11899 current_range_comment_prefix.clone(),
11900 current_range_rewrap_prefix.clone(),
11901 from_empty_selection,
11902 ));
11903 current_range_start = row;
11904 current_range_indent = row_indent;
11905 current_range_comment_prefix = row_comment_prefix;
11906 current_range_rewrap_prefix = row_rewrap_prefix;
11907 }
11908 prev_row = row;
11909 }
11910
11911 ranges.push((
11912 language_settings.clone(),
11913 Point::new(current_range_start, 0)
11914 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11915 current_range_indent,
11916 current_range_comment_prefix,
11917 current_range_rewrap_prefix,
11918 from_empty_selection,
11919 ));
11920
11921 ranges
11922 });
11923
11924 let mut edits = Vec::new();
11925 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11926
11927 for (
11928 language_settings,
11929 wrap_range,
11930 indent_size,
11931 comment_prefix,
11932 rewrap_prefix,
11933 from_empty_selection,
11934 ) in wrap_ranges
11935 {
11936 let mut start_row = wrap_range.start.row;
11937 let mut end_row = wrap_range.end.row;
11938
11939 // Skip selections that overlap with a range that has already been rewrapped.
11940 let selection_range = start_row..end_row;
11941 if rewrapped_row_ranges
11942 .iter()
11943 .any(|range| range.overlaps(&selection_range))
11944 {
11945 continue;
11946 }
11947
11948 let tab_size = language_settings.tab_size;
11949
11950 let indent_prefix = indent_size.chars().collect::<String>();
11951 let mut line_prefix = indent_prefix.clone();
11952 let mut inside_comment = false;
11953 if let Some(prefix) = &comment_prefix {
11954 line_prefix.push_str(prefix);
11955 inside_comment = true;
11956 }
11957 if let Some(prefix) = &rewrap_prefix {
11958 line_prefix.push_str(prefix);
11959 }
11960
11961 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11962 RewrapBehavior::InComments => inside_comment,
11963 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11964 RewrapBehavior::Anywhere => true,
11965 };
11966
11967 let should_rewrap = options.override_language_settings
11968 || allow_rewrap_based_on_language
11969 || self.hard_wrap.is_some();
11970 if !should_rewrap {
11971 continue;
11972 }
11973
11974 if from_empty_selection {
11975 'expand_upwards: while start_row > 0 {
11976 let prev_row = start_row - 1;
11977 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11978 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11979 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11980 {
11981 start_row = prev_row;
11982 } else {
11983 break 'expand_upwards;
11984 }
11985 }
11986
11987 'expand_downwards: while end_row < buffer.max_point().row {
11988 let next_row = end_row + 1;
11989 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11990 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11991 && !buffer.is_line_blank(MultiBufferRow(next_row))
11992 {
11993 end_row = next_row;
11994 } else {
11995 break 'expand_downwards;
11996 }
11997 }
11998 }
11999
12000 let start = Point::new(start_row, 0);
12001 let start_offset = start.to_offset(&buffer);
12002 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12003 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12004 let Some(lines_without_prefixes) = selection_text
12005 .lines()
12006 .enumerate()
12007 .map(|(ix, line)| {
12008 let line_trimmed = line.trim_start();
12009 if rewrap_prefix.is_some() && ix > 0 {
12010 Ok(line_trimmed)
12011 } else {
12012 line_trimmed
12013 .strip_prefix(&line_prefix.trim_start())
12014 .with_context(|| {
12015 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12016 })
12017 }
12018 })
12019 .collect::<Result<Vec<_>, _>>()
12020 .log_err()
12021 else {
12022 continue;
12023 };
12024
12025 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12026 buffer
12027 .language_settings_at(Point::new(start_row, 0), cx)
12028 .preferred_line_length as usize
12029 });
12030
12031 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12032 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12033 } else {
12034 line_prefix.clone()
12035 };
12036
12037 let wrapped_text = wrap_with_prefix(
12038 line_prefix,
12039 subsequent_lines_prefix,
12040 lines_without_prefixes.join("\n"),
12041 wrap_column,
12042 tab_size,
12043 options.preserve_existing_whitespace,
12044 );
12045
12046 // TODO: should always use char-based diff while still supporting cursor behavior that
12047 // matches vim.
12048 let mut diff_options = DiffOptions::default();
12049 if options.override_language_settings {
12050 diff_options.max_word_diff_len = 0;
12051 diff_options.max_word_diff_line_count = 0;
12052 } else {
12053 diff_options.max_word_diff_len = usize::MAX;
12054 diff_options.max_word_diff_line_count = usize::MAX;
12055 }
12056
12057 for (old_range, new_text) in
12058 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12059 {
12060 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12061 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12062 edits.push((edit_start..edit_end, new_text));
12063 }
12064
12065 rewrapped_row_ranges.push(start_row..=end_row);
12066 }
12067
12068 self.buffer
12069 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12070 }
12071
12072 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12073 let mut text = String::new();
12074 let buffer = self.buffer.read(cx).snapshot(cx);
12075 let mut selections = self.selections.all::<Point>(cx);
12076 let mut clipboard_selections = Vec::with_capacity(selections.len());
12077 {
12078 let max_point = buffer.max_point();
12079 let mut is_first = true;
12080 for selection in &mut selections {
12081 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12082 if is_entire_line {
12083 selection.start = Point::new(selection.start.row, 0);
12084 if !selection.is_empty() && selection.end.column == 0 {
12085 selection.end = cmp::min(max_point, selection.end);
12086 } else {
12087 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12088 }
12089 selection.goal = SelectionGoal::None;
12090 }
12091 if is_first {
12092 is_first = false;
12093 } else {
12094 text += "\n";
12095 }
12096 let mut len = 0;
12097 for chunk in buffer.text_for_range(selection.start..selection.end) {
12098 text.push_str(chunk);
12099 len += chunk.len();
12100 }
12101 clipboard_selections.push(ClipboardSelection {
12102 len,
12103 is_entire_line,
12104 first_line_indent: buffer
12105 .indent_size_for_line(MultiBufferRow(selection.start.row))
12106 .len,
12107 });
12108 }
12109 }
12110
12111 self.transact(window, cx, |this, window, cx| {
12112 this.change_selections(Default::default(), window, cx, |s| {
12113 s.select(selections);
12114 });
12115 this.insert("", window, cx);
12116 });
12117 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12118 }
12119
12120 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12121 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12122 let item = self.cut_common(window, cx);
12123 cx.write_to_clipboard(item);
12124 }
12125
12126 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12127 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12128 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12129 s.move_with(|snapshot, sel| {
12130 if sel.is_empty() {
12131 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12132 }
12133 });
12134 });
12135 let item = self.cut_common(window, cx);
12136 cx.set_global(KillRing(item))
12137 }
12138
12139 pub fn kill_ring_yank(
12140 &mut self,
12141 _: &KillRingYank,
12142 window: &mut Window,
12143 cx: &mut Context<Self>,
12144 ) {
12145 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12146 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12147 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12148 (kill_ring.text().to_string(), kill_ring.metadata_json())
12149 } else {
12150 return;
12151 }
12152 } else {
12153 return;
12154 };
12155 self.do_paste(&text, metadata, false, window, cx);
12156 }
12157
12158 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12159 self.do_copy(true, cx);
12160 }
12161
12162 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12163 self.do_copy(false, cx);
12164 }
12165
12166 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12167 let selections = self.selections.all::<Point>(cx);
12168 let buffer = self.buffer.read(cx).read(cx);
12169 let mut text = String::new();
12170
12171 let mut clipboard_selections = Vec::with_capacity(selections.len());
12172 {
12173 let max_point = buffer.max_point();
12174 let mut is_first = true;
12175 for selection in &selections {
12176 let mut start = selection.start;
12177 let mut end = selection.end;
12178 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12179 if is_entire_line {
12180 start = Point::new(start.row, 0);
12181 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12182 }
12183
12184 let mut trimmed_selections = Vec::new();
12185 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12186 let row = MultiBufferRow(start.row);
12187 let first_indent = buffer.indent_size_for_line(row);
12188 if first_indent.len == 0 || start.column > first_indent.len {
12189 trimmed_selections.push(start..end);
12190 } else {
12191 trimmed_selections.push(
12192 Point::new(row.0, first_indent.len)
12193 ..Point::new(row.0, buffer.line_len(row)),
12194 );
12195 for row in start.row + 1..=end.row {
12196 let mut line_len = buffer.line_len(MultiBufferRow(row));
12197 if row == end.row {
12198 line_len = end.column;
12199 }
12200 if line_len == 0 {
12201 trimmed_selections
12202 .push(Point::new(row, 0)..Point::new(row, line_len));
12203 continue;
12204 }
12205 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12206 if row_indent_size.len >= first_indent.len {
12207 trimmed_selections.push(
12208 Point::new(row, first_indent.len)..Point::new(row, line_len),
12209 );
12210 } else {
12211 trimmed_selections.clear();
12212 trimmed_selections.push(start..end);
12213 break;
12214 }
12215 }
12216 }
12217 } else {
12218 trimmed_selections.push(start..end);
12219 }
12220
12221 for trimmed_range in trimmed_selections {
12222 if is_first {
12223 is_first = false;
12224 } else {
12225 text += "\n";
12226 }
12227 let mut len = 0;
12228 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12229 text.push_str(chunk);
12230 len += chunk.len();
12231 }
12232 clipboard_selections.push(ClipboardSelection {
12233 len,
12234 is_entire_line,
12235 first_line_indent: buffer
12236 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12237 .len,
12238 });
12239 }
12240 }
12241 }
12242
12243 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12244 text,
12245 clipboard_selections,
12246 ));
12247 }
12248
12249 pub fn do_paste(
12250 &mut self,
12251 text: &String,
12252 clipboard_selections: Option<Vec<ClipboardSelection>>,
12253 handle_entire_lines: bool,
12254 window: &mut Window,
12255 cx: &mut Context<Self>,
12256 ) {
12257 if self.read_only(cx) {
12258 return;
12259 }
12260
12261 let clipboard_text = Cow::Borrowed(text);
12262
12263 self.transact(window, cx, |this, window, cx| {
12264 let had_active_edit_prediction = this.has_active_edit_prediction();
12265
12266 if let Some(mut clipboard_selections) = clipboard_selections {
12267 let old_selections = this.selections.all::<usize>(cx);
12268 let all_selections_were_entire_line =
12269 clipboard_selections.iter().all(|s| s.is_entire_line);
12270 let first_selection_indent_column =
12271 clipboard_selections.first().map(|s| s.first_line_indent);
12272 if clipboard_selections.len() != old_selections.len() {
12273 clipboard_selections.drain(..);
12274 }
12275 let cursor_offset = this.selections.last::<usize>(cx).head();
12276 let mut auto_indent_on_paste = true;
12277
12278 this.buffer.update(cx, |buffer, cx| {
12279 let snapshot = buffer.read(cx);
12280 auto_indent_on_paste = snapshot
12281 .language_settings_at(cursor_offset, cx)
12282 .auto_indent_on_paste;
12283
12284 let mut start_offset = 0;
12285 let mut edits = Vec::new();
12286 let mut original_indent_columns = Vec::new();
12287 for (ix, selection) in old_selections.iter().enumerate() {
12288 let to_insert;
12289 let entire_line;
12290 let original_indent_column;
12291 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12292 let end_offset = start_offset + clipboard_selection.len;
12293 to_insert = &clipboard_text[start_offset..end_offset];
12294 entire_line = clipboard_selection.is_entire_line;
12295 start_offset = end_offset + 1;
12296 original_indent_column = Some(clipboard_selection.first_line_indent);
12297 } else {
12298 to_insert = clipboard_text.as_str();
12299 entire_line = all_selections_were_entire_line;
12300 original_indent_column = first_selection_indent_column
12301 }
12302
12303 // If the corresponding selection was empty when this slice of the
12304 // clipboard text was written, then the entire line containing the
12305 // selection was copied. If this selection is also currently empty,
12306 // then paste the line before the current line of the buffer.
12307 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12308 let column = selection.start.to_point(&snapshot).column as usize;
12309 let line_start = selection.start - column;
12310 line_start..line_start
12311 } else {
12312 selection.range()
12313 };
12314
12315 edits.push((range, to_insert));
12316 original_indent_columns.push(original_indent_column);
12317 }
12318 drop(snapshot);
12319
12320 buffer.edit(
12321 edits,
12322 if auto_indent_on_paste {
12323 Some(AutoindentMode::Block {
12324 original_indent_columns,
12325 })
12326 } else {
12327 None
12328 },
12329 cx,
12330 );
12331 });
12332
12333 let selections = this.selections.all::<usize>(cx);
12334 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12335 } else {
12336 this.insert(&clipboard_text, window, cx);
12337 }
12338
12339 let trigger_in_words =
12340 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12341
12342 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12343 });
12344 }
12345
12346 pub fn diff_clipboard_with_selection(
12347 &mut self,
12348 _: &DiffClipboardWithSelection,
12349 window: &mut Window,
12350 cx: &mut Context<Self>,
12351 ) {
12352 let selections = self.selections.all::<usize>(cx);
12353
12354 if selections.is_empty() {
12355 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12356 return;
12357 };
12358
12359 let clipboard_text = match cx.read_from_clipboard() {
12360 Some(item) => match item.entries().first() {
12361 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12362 _ => None,
12363 },
12364 None => None,
12365 };
12366
12367 let Some(clipboard_text) = clipboard_text else {
12368 log::warn!("Clipboard doesn't contain text.");
12369 return;
12370 };
12371
12372 window.dispatch_action(
12373 Box::new(DiffClipboardWithSelectionData {
12374 clipboard_text,
12375 editor: cx.entity(),
12376 }),
12377 cx,
12378 );
12379 }
12380
12381 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12382 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12383 if let Some(item) = cx.read_from_clipboard() {
12384 let entries = item.entries();
12385
12386 match entries.first() {
12387 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12388 // of all the pasted entries.
12389 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12390 .do_paste(
12391 clipboard_string.text(),
12392 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12393 true,
12394 window,
12395 cx,
12396 ),
12397 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12398 }
12399 }
12400 }
12401
12402 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12403 if self.read_only(cx) {
12404 return;
12405 }
12406
12407 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12408
12409 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12410 if let Some((selections, _)) =
12411 self.selection_history.transaction(transaction_id).cloned()
12412 {
12413 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12414 s.select_anchors(selections.to_vec());
12415 });
12416 } else {
12417 log::error!(
12418 "No entry in selection_history found for undo. \
12419 This may correspond to a bug where undo does not update the selection. \
12420 If this is occurring, please add details to \
12421 https://github.com/zed-industries/zed/issues/22692"
12422 );
12423 }
12424 self.request_autoscroll(Autoscroll::fit(), cx);
12425 self.unmark_text(window, cx);
12426 self.refresh_edit_prediction(true, false, window, cx);
12427 cx.emit(EditorEvent::Edited { transaction_id });
12428 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12429 }
12430 }
12431
12432 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12433 if self.read_only(cx) {
12434 return;
12435 }
12436
12437 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12438
12439 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12440 if let Some((_, Some(selections))) =
12441 self.selection_history.transaction(transaction_id).cloned()
12442 {
12443 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12444 s.select_anchors(selections.to_vec());
12445 });
12446 } else {
12447 log::error!(
12448 "No entry in selection_history found for redo. \
12449 This may correspond to a bug where undo does not update the selection. \
12450 If this is occurring, please add details to \
12451 https://github.com/zed-industries/zed/issues/22692"
12452 );
12453 }
12454 self.request_autoscroll(Autoscroll::fit(), cx);
12455 self.unmark_text(window, cx);
12456 self.refresh_edit_prediction(true, false, window, cx);
12457 cx.emit(EditorEvent::Edited { transaction_id });
12458 }
12459 }
12460
12461 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12462 self.buffer
12463 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12464 }
12465
12466 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12467 self.buffer
12468 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12469 }
12470
12471 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12472 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12473 self.change_selections(Default::default(), window, cx, |s| {
12474 s.move_with(|map, selection| {
12475 let cursor = if selection.is_empty() {
12476 movement::left(map, selection.start)
12477 } else {
12478 selection.start
12479 };
12480 selection.collapse_to(cursor, SelectionGoal::None);
12481 });
12482 })
12483 }
12484
12485 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12487 self.change_selections(Default::default(), window, cx, |s| {
12488 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12489 })
12490 }
12491
12492 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12493 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12494 self.change_selections(Default::default(), window, cx, |s| {
12495 s.move_with(|map, selection| {
12496 let cursor = if selection.is_empty() {
12497 movement::right(map, selection.end)
12498 } else {
12499 selection.end
12500 };
12501 selection.collapse_to(cursor, SelectionGoal::None)
12502 });
12503 })
12504 }
12505
12506 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12507 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12508 self.change_selections(Default::default(), window, cx, |s| {
12509 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12510 })
12511 }
12512
12513 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12514 if self.take_rename(true, window, cx).is_some() {
12515 return;
12516 }
12517
12518 if self.mode.is_single_line() {
12519 cx.propagate();
12520 return;
12521 }
12522
12523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12524
12525 let text_layout_details = &self.text_layout_details(window);
12526 let selection_count = self.selections.count();
12527 let first_selection = self.selections.first_anchor();
12528
12529 self.change_selections(Default::default(), window, cx, |s| {
12530 s.move_with(|map, selection| {
12531 if !selection.is_empty() {
12532 selection.goal = SelectionGoal::None;
12533 }
12534 let (cursor, goal) = movement::up(
12535 map,
12536 selection.start,
12537 selection.goal,
12538 false,
12539 text_layout_details,
12540 );
12541 selection.collapse_to(cursor, goal);
12542 });
12543 });
12544
12545 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12546 {
12547 cx.propagate();
12548 }
12549 }
12550
12551 pub fn move_up_by_lines(
12552 &mut self,
12553 action: &MoveUpByLines,
12554 window: &mut Window,
12555 cx: &mut Context<Self>,
12556 ) {
12557 if self.take_rename(true, window, cx).is_some() {
12558 return;
12559 }
12560
12561 if self.mode.is_single_line() {
12562 cx.propagate();
12563 return;
12564 }
12565
12566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12567
12568 let text_layout_details = &self.text_layout_details(window);
12569
12570 self.change_selections(Default::default(), window, cx, |s| {
12571 s.move_with(|map, selection| {
12572 if !selection.is_empty() {
12573 selection.goal = SelectionGoal::None;
12574 }
12575 let (cursor, goal) = movement::up_by_rows(
12576 map,
12577 selection.start,
12578 action.lines,
12579 selection.goal,
12580 false,
12581 text_layout_details,
12582 );
12583 selection.collapse_to(cursor, goal);
12584 });
12585 })
12586 }
12587
12588 pub fn move_down_by_lines(
12589 &mut self,
12590 action: &MoveDownByLines,
12591 window: &mut Window,
12592 cx: &mut Context<Self>,
12593 ) {
12594 if self.take_rename(true, window, cx).is_some() {
12595 return;
12596 }
12597
12598 if self.mode.is_single_line() {
12599 cx.propagate();
12600 return;
12601 }
12602
12603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12604
12605 let text_layout_details = &self.text_layout_details(window);
12606
12607 self.change_selections(Default::default(), window, cx, |s| {
12608 s.move_with(|map, selection| {
12609 if !selection.is_empty() {
12610 selection.goal = SelectionGoal::None;
12611 }
12612 let (cursor, goal) = movement::down_by_rows(
12613 map,
12614 selection.start,
12615 action.lines,
12616 selection.goal,
12617 false,
12618 text_layout_details,
12619 );
12620 selection.collapse_to(cursor, goal);
12621 });
12622 })
12623 }
12624
12625 pub fn select_down_by_lines(
12626 &mut self,
12627 action: &SelectDownByLines,
12628 window: &mut Window,
12629 cx: &mut Context<Self>,
12630 ) {
12631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12632 let text_layout_details = &self.text_layout_details(window);
12633 self.change_selections(Default::default(), window, cx, |s| {
12634 s.move_heads_with(|map, head, goal| {
12635 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12636 })
12637 })
12638 }
12639
12640 pub fn select_up_by_lines(
12641 &mut self,
12642 action: &SelectUpByLines,
12643 window: &mut Window,
12644 cx: &mut Context<Self>,
12645 ) {
12646 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12647 let text_layout_details = &self.text_layout_details(window);
12648 self.change_selections(Default::default(), window, cx, |s| {
12649 s.move_heads_with(|map, head, goal| {
12650 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12651 })
12652 })
12653 }
12654
12655 pub fn select_page_up(
12656 &mut self,
12657 _: &SelectPageUp,
12658 window: &mut Window,
12659 cx: &mut Context<Self>,
12660 ) {
12661 let Some(row_count) = self.visible_row_count() else {
12662 return;
12663 };
12664
12665 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12666
12667 let text_layout_details = &self.text_layout_details(window);
12668
12669 self.change_selections(Default::default(), window, cx, |s| {
12670 s.move_heads_with(|map, head, goal| {
12671 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12672 })
12673 })
12674 }
12675
12676 pub fn move_page_up(
12677 &mut self,
12678 action: &MovePageUp,
12679 window: &mut Window,
12680 cx: &mut Context<Self>,
12681 ) {
12682 if self.take_rename(true, window, cx).is_some() {
12683 return;
12684 }
12685
12686 if self
12687 .context_menu
12688 .borrow_mut()
12689 .as_mut()
12690 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12691 .unwrap_or(false)
12692 {
12693 return;
12694 }
12695
12696 if matches!(self.mode, EditorMode::SingleLine) {
12697 cx.propagate();
12698 return;
12699 }
12700
12701 let Some(row_count) = self.visible_row_count() else {
12702 return;
12703 };
12704
12705 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12706
12707 let effects = if action.center_cursor {
12708 SelectionEffects::scroll(Autoscroll::center())
12709 } else {
12710 SelectionEffects::default()
12711 };
12712
12713 let text_layout_details = &self.text_layout_details(window);
12714
12715 self.change_selections(effects, window, cx, |s| {
12716 s.move_with(|map, selection| {
12717 if !selection.is_empty() {
12718 selection.goal = SelectionGoal::None;
12719 }
12720 let (cursor, goal) = movement::up_by_rows(
12721 map,
12722 selection.end,
12723 row_count,
12724 selection.goal,
12725 false,
12726 text_layout_details,
12727 );
12728 selection.collapse_to(cursor, goal);
12729 });
12730 });
12731 }
12732
12733 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12735 let text_layout_details = &self.text_layout_details(window);
12736 self.change_selections(Default::default(), window, cx, |s| {
12737 s.move_heads_with(|map, head, goal| {
12738 movement::up(map, head, goal, false, text_layout_details)
12739 })
12740 })
12741 }
12742
12743 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12744 self.take_rename(true, window, cx);
12745
12746 if self.mode.is_single_line() {
12747 cx.propagate();
12748 return;
12749 }
12750
12751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12752
12753 let text_layout_details = &self.text_layout_details(window);
12754 let selection_count = self.selections.count();
12755 let first_selection = self.selections.first_anchor();
12756
12757 self.change_selections(Default::default(), window, cx, |s| {
12758 s.move_with(|map, selection| {
12759 if !selection.is_empty() {
12760 selection.goal = SelectionGoal::None;
12761 }
12762 let (cursor, goal) = movement::down(
12763 map,
12764 selection.end,
12765 selection.goal,
12766 false,
12767 text_layout_details,
12768 );
12769 selection.collapse_to(cursor, goal);
12770 });
12771 });
12772
12773 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12774 {
12775 cx.propagate();
12776 }
12777 }
12778
12779 pub fn select_page_down(
12780 &mut self,
12781 _: &SelectPageDown,
12782 window: &mut Window,
12783 cx: &mut Context<Self>,
12784 ) {
12785 let Some(row_count) = self.visible_row_count() else {
12786 return;
12787 };
12788
12789 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12790
12791 let text_layout_details = &self.text_layout_details(window);
12792
12793 self.change_selections(Default::default(), window, cx, |s| {
12794 s.move_heads_with(|map, head, goal| {
12795 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12796 })
12797 })
12798 }
12799
12800 pub fn move_page_down(
12801 &mut self,
12802 action: &MovePageDown,
12803 window: &mut Window,
12804 cx: &mut Context<Self>,
12805 ) {
12806 if self.take_rename(true, window, cx).is_some() {
12807 return;
12808 }
12809
12810 if self
12811 .context_menu
12812 .borrow_mut()
12813 .as_mut()
12814 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12815 .unwrap_or(false)
12816 {
12817 return;
12818 }
12819
12820 if matches!(self.mode, EditorMode::SingleLine) {
12821 cx.propagate();
12822 return;
12823 }
12824
12825 let Some(row_count) = self.visible_row_count() else {
12826 return;
12827 };
12828
12829 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12830
12831 let effects = if action.center_cursor {
12832 SelectionEffects::scroll(Autoscroll::center())
12833 } else {
12834 SelectionEffects::default()
12835 };
12836
12837 let text_layout_details = &self.text_layout_details(window);
12838 self.change_selections(effects, window, cx, |s| {
12839 s.move_with(|map, selection| {
12840 if !selection.is_empty() {
12841 selection.goal = SelectionGoal::None;
12842 }
12843 let (cursor, goal) = movement::down_by_rows(
12844 map,
12845 selection.end,
12846 row_count,
12847 selection.goal,
12848 false,
12849 text_layout_details,
12850 );
12851 selection.collapse_to(cursor, goal);
12852 });
12853 });
12854 }
12855
12856 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12858 let text_layout_details = &self.text_layout_details(window);
12859 self.change_selections(Default::default(), window, cx, |s| {
12860 s.move_heads_with(|map, head, goal| {
12861 movement::down(map, head, goal, false, text_layout_details)
12862 })
12863 });
12864 }
12865
12866 pub fn context_menu_first(
12867 &mut self,
12868 _: &ContextMenuFirst,
12869 window: &mut Window,
12870 cx: &mut Context<Self>,
12871 ) {
12872 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12873 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12874 }
12875 }
12876
12877 pub fn context_menu_prev(
12878 &mut self,
12879 _: &ContextMenuPrevious,
12880 window: &mut Window,
12881 cx: &mut Context<Self>,
12882 ) {
12883 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12884 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12885 }
12886 }
12887
12888 pub fn context_menu_next(
12889 &mut self,
12890 _: &ContextMenuNext,
12891 window: &mut Window,
12892 cx: &mut Context<Self>,
12893 ) {
12894 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12895 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12896 }
12897 }
12898
12899 pub fn context_menu_last(
12900 &mut self,
12901 _: &ContextMenuLast,
12902 window: &mut Window,
12903 cx: &mut Context<Self>,
12904 ) {
12905 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12906 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12907 }
12908 }
12909
12910 pub fn signature_help_prev(
12911 &mut self,
12912 _: &SignatureHelpPrevious,
12913 _: &mut Window,
12914 cx: &mut Context<Self>,
12915 ) {
12916 if let Some(popover) = self.signature_help_state.popover_mut() {
12917 if popover.current_signature == 0 {
12918 popover.current_signature = popover.signatures.len() - 1;
12919 } else {
12920 popover.current_signature -= 1;
12921 }
12922 cx.notify();
12923 }
12924 }
12925
12926 pub fn signature_help_next(
12927 &mut self,
12928 _: &SignatureHelpNext,
12929 _: &mut Window,
12930 cx: &mut Context<Self>,
12931 ) {
12932 if let Some(popover) = self.signature_help_state.popover_mut() {
12933 if popover.current_signature + 1 == popover.signatures.len() {
12934 popover.current_signature = 0;
12935 } else {
12936 popover.current_signature += 1;
12937 }
12938 cx.notify();
12939 }
12940 }
12941
12942 pub fn move_to_previous_word_start(
12943 &mut self,
12944 _: &MoveToPreviousWordStart,
12945 window: &mut Window,
12946 cx: &mut Context<Self>,
12947 ) {
12948 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12949 self.change_selections(Default::default(), window, cx, |s| {
12950 s.move_cursors_with(|map, head, _| {
12951 (
12952 movement::previous_word_start(map, head),
12953 SelectionGoal::None,
12954 )
12955 });
12956 })
12957 }
12958
12959 pub fn move_to_previous_subword_start(
12960 &mut self,
12961 _: &MoveToPreviousSubwordStart,
12962 window: &mut Window,
12963 cx: &mut Context<Self>,
12964 ) {
12965 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12966 self.change_selections(Default::default(), window, cx, |s| {
12967 s.move_cursors_with(|map, head, _| {
12968 (
12969 movement::previous_subword_start(map, head),
12970 SelectionGoal::None,
12971 )
12972 });
12973 })
12974 }
12975
12976 pub fn select_to_previous_word_start(
12977 &mut self,
12978 _: &SelectToPreviousWordStart,
12979 window: &mut Window,
12980 cx: &mut Context<Self>,
12981 ) {
12982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12983 self.change_selections(Default::default(), window, cx, |s| {
12984 s.move_heads_with(|map, head, _| {
12985 (
12986 movement::previous_word_start(map, head),
12987 SelectionGoal::None,
12988 )
12989 });
12990 })
12991 }
12992
12993 pub fn select_to_previous_subword_start(
12994 &mut self,
12995 _: &SelectToPreviousSubwordStart,
12996 window: &mut Window,
12997 cx: &mut Context<Self>,
12998 ) {
12999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13000 self.change_selections(Default::default(), window, cx, |s| {
13001 s.move_heads_with(|map, head, _| {
13002 (
13003 movement::previous_subword_start(map, head),
13004 SelectionGoal::None,
13005 )
13006 });
13007 })
13008 }
13009
13010 pub fn delete_to_previous_word_start(
13011 &mut self,
13012 action: &DeleteToPreviousWordStart,
13013 window: &mut Window,
13014 cx: &mut Context<Self>,
13015 ) {
13016 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13017 self.transact(window, cx, |this, window, cx| {
13018 this.select_autoclose_pair(window, cx);
13019 this.change_selections(Default::default(), window, cx, |s| {
13020 s.move_with(|map, selection| {
13021 if selection.is_empty() {
13022 let cursor = if action.ignore_newlines {
13023 movement::previous_word_start(map, selection.head())
13024 } else {
13025 movement::previous_word_start_or_newline(map, selection.head())
13026 };
13027 selection.set_head(cursor, SelectionGoal::None);
13028 }
13029 });
13030 });
13031 this.insert("", window, cx);
13032 });
13033 }
13034
13035 pub fn delete_to_previous_subword_start(
13036 &mut self,
13037 _: &DeleteToPreviousSubwordStart,
13038 window: &mut Window,
13039 cx: &mut Context<Self>,
13040 ) {
13041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13042 self.transact(window, cx, |this, window, cx| {
13043 this.select_autoclose_pair(window, cx);
13044 this.change_selections(Default::default(), window, cx, |s| {
13045 s.move_with(|map, selection| {
13046 if selection.is_empty() {
13047 let cursor = movement::previous_subword_start(map, selection.head());
13048 selection.set_head(cursor, SelectionGoal::None);
13049 }
13050 });
13051 });
13052 this.insert("", window, cx);
13053 });
13054 }
13055
13056 pub fn move_to_next_word_end(
13057 &mut self,
13058 _: &MoveToNextWordEnd,
13059 window: &mut Window,
13060 cx: &mut Context<Self>,
13061 ) {
13062 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13063 self.change_selections(Default::default(), window, cx, |s| {
13064 s.move_cursors_with(|map, head, _| {
13065 (movement::next_word_end(map, head), SelectionGoal::None)
13066 });
13067 })
13068 }
13069
13070 pub fn move_to_next_subword_end(
13071 &mut self,
13072 _: &MoveToNextSubwordEnd,
13073 window: &mut Window,
13074 cx: &mut Context<Self>,
13075 ) {
13076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13077 self.change_selections(Default::default(), window, cx, |s| {
13078 s.move_cursors_with(|map, head, _| {
13079 (movement::next_subword_end(map, head), SelectionGoal::None)
13080 });
13081 })
13082 }
13083
13084 pub fn select_to_next_word_end(
13085 &mut self,
13086 _: &SelectToNextWordEnd,
13087 window: &mut Window,
13088 cx: &mut Context<Self>,
13089 ) {
13090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13091 self.change_selections(Default::default(), window, cx, |s| {
13092 s.move_heads_with(|map, head, _| {
13093 (movement::next_word_end(map, head), SelectionGoal::None)
13094 });
13095 })
13096 }
13097
13098 pub fn select_to_next_subword_end(
13099 &mut self,
13100 _: &SelectToNextSubwordEnd,
13101 window: &mut Window,
13102 cx: &mut Context<Self>,
13103 ) {
13104 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13105 self.change_selections(Default::default(), window, cx, |s| {
13106 s.move_heads_with(|map, head, _| {
13107 (movement::next_subword_end(map, head), SelectionGoal::None)
13108 });
13109 })
13110 }
13111
13112 pub fn delete_to_next_word_end(
13113 &mut self,
13114 action: &DeleteToNextWordEnd,
13115 window: &mut Window,
13116 cx: &mut Context<Self>,
13117 ) {
13118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13119 self.transact(window, cx, |this, window, cx| {
13120 this.change_selections(Default::default(), window, cx, |s| {
13121 s.move_with(|map, selection| {
13122 if selection.is_empty() {
13123 let cursor = if action.ignore_newlines {
13124 movement::next_word_end(map, selection.head())
13125 } else {
13126 movement::next_word_end_or_newline(map, selection.head())
13127 };
13128 selection.set_head(cursor, SelectionGoal::None);
13129 }
13130 });
13131 });
13132 this.insert("", window, cx);
13133 });
13134 }
13135
13136 pub fn delete_to_next_subword_end(
13137 &mut self,
13138 _: &DeleteToNextSubwordEnd,
13139 window: &mut Window,
13140 cx: &mut Context<Self>,
13141 ) {
13142 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13143 self.transact(window, cx, |this, window, cx| {
13144 this.change_selections(Default::default(), window, cx, |s| {
13145 s.move_with(|map, selection| {
13146 if selection.is_empty() {
13147 let cursor = movement::next_subword_end(map, selection.head());
13148 selection.set_head(cursor, SelectionGoal::None);
13149 }
13150 });
13151 });
13152 this.insert("", window, cx);
13153 });
13154 }
13155
13156 pub fn move_to_beginning_of_line(
13157 &mut self,
13158 action: &MoveToBeginningOfLine,
13159 window: &mut Window,
13160 cx: &mut Context<Self>,
13161 ) {
13162 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13163 self.change_selections(Default::default(), window, cx, |s| {
13164 s.move_cursors_with(|map, head, _| {
13165 (
13166 movement::indented_line_beginning(
13167 map,
13168 head,
13169 action.stop_at_soft_wraps,
13170 action.stop_at_indent,
13171 ),
13172 SelectionGoal::None,
13173 )
13174 });
13175 })
13176 }
13177
13178 pub fn select_to_beginning_of_line(
13179 &mut self,
13180 action: &SelectToBeginningOfLine,
13181 window: &mut Window,
13182 cx: &mut Context<Self>,
13183 ) {
13184 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13185 self.change_selections(Default::default(), window, cx, |s| {
13186 s.move_heads_with(|map, head, _| {
13187 (
13188 movement::indented_line_beginning(
13189 map,
13190 head,
13191 action.stop_at_soft_wraps,
13192 action.stop_at_indent,
13193 ),
13194 SelectionGoal::None,
13195 )
13196 });
13197 });
13198 }
13199
13200 pub fn delete_to_beginning_of_line(
13201 &mut self,
13202 action: &DeleteToBeginningOfLine,
13203 window: &mut Window,
13204 cx: &mut Context<Self>,
13205 ) {
13206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13207 self.transact(window, cx, |this, window, cx| {
13208 this.change_selections(Default::default(), window, cx, |s| {
13209 s.move_with(|_, selection| {
13210 selection.reversed = true;
13211 });
13212 });
13213
13214 this.select_to_beginning_of_line(
13215 &SelectToBeginningOfLine {
13216 stop_at_soft_wraps: false,
13217 stop_at_indent: action.stop_at_indent,
13218 },
13219 window,
13220 cx,
13221 );
13222 this.backspace(&Backspace, window, cx);
13223 });
13224 }
13225
13226 pub fn move_to_end_of_line(
13227 &mut self,
13228 action: &MoveToEndOfLine,
13229 window: &mut Window,
13230 cx: &mut Context<Self>,
13231 ) {
13232 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13233 self.change_selections(Default::default(), window, cx, |s| {
13234 s.move_cursors_with(|map, head, _| {
13235 (
13236 movement::line_end(map, head, action.stop_at_soft_wraps),
13237 SelectionGoal::None,
13238 )
13239 });
13240 })
13241 }
13242
13243 pub fn select_to_end_of_line(
13244 &mut self,
13245 action: &SelectToEndOfLine,
13246 window: &mut Window,
13247 cx: &mut Context<Self>,
13248 ) {
13249 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13250 self.change_selections(Default::default(), window, cx, |s| {
13251 s.move_heads_with(|map, head, _| {
13252 (
13253 movement::line_end(map, head, action.stop_at_soft_wraps),
13254 SelectionGoal::None,
13255 )
13256 });
13257 })
13258 }
13259
13260 pub fn delete_to_end_of_line(
13261 &mut self,
13262 _: &DeleteToEndOfLine,
13263 window: &mut Window,
13264 cx: &mut Context<Self>,
13265 ) {
13266 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13267 self.transact(window, cx, |this, window, cx| {
13268 this.select_to_end_of_line(
13269 &SelectToEndOfLine {
13270 stop_at_soft_wraps: false,
13271 },
13272 window,
13273 cx,
13274 );
13275 this.delete(&Delete, window, cx);
13276 });
13277 }
13278
13279 pub fn cut_to_end_of_line(
13280 &mut self,
13281 _: &CutToEndOfLine,
13282 window: &mut Window,
13283 cx: &mut Context<Self>,
13284 ) {
13285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13286 self.transact(window, cx, |this, window, cx| {
13287 this.select_to_end_of_line(
13288 &SelectToEndOfLine {
13289 stop_at_soft_wraps: false,
13290 },
13291 window,
13292 cx,
13293 );
13294 this.cut(&Cut, window, cx);
13295 });
13296 }
13297
13298 pub fn move_to_start_of_paragraph(
13299 &mut self,
13300 _: &MoveToStartOfParagraph,
13301 window: &mut Window,
13302 cx: &mut Context<Self>,
13303 ) {
13304 if matches!(self.mode, EditorMode::SingleLine) {
13305 cx.propagate();
13306 return;
13307 }
13308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13309 self.change_selections(Default::default(), window, cx, |s| {
13310 s.move_with(|map, selection| {
13311 selection.collapse_to(
13312 movement::start_of_paragraph(map, selection.head(), 1),
13313 SelectionGoal::None,
13314 )
13315 });
13316 })
13317 }
13318
13319 pub fn move_to_end_of_paragraph(
13320 &mut self,
13321 _: &MoveToEndOfParagraph,
13322 window: &mut Window,
13323 cx: &mut Context<Self>,
13324 ) {
13325 if matches!(self.mode, EditorMode::SingleLine) {
13326 cx.propagate();
13327 return;
13328 }
13329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13330 self.change_selections(Default::default(), window, cx, |s| {
13331 s.move_with(|map, selection| {
13332 selection.collapse_to(
13333 movement::end_of_paragraph(map, selection.head(), 1),
13334 SelectionGoal::None,
13335 )
13336 });
13337 })
13338 }
13339
13340 pub fn select_to_start_of_paragraph(
13341 &mut self,
13342 _: &SelectToStartOfParagraph,
13343 window: &mut Window,
13344 cx: &mut Context<Self>,
13345 ) {
13346 if matches!(self.mode, EditorMode::SingleLine) {
13347 cx.propagate();
13348 return;
13349 }
13350 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13351 self.change_selections(Default::default(), window, cx, |s| {
13352 s.move_heads_with(|map, head, _| {
13353 (
13354 movement::start_of_paragraph(map, head, 1),
13355 SelectionGoal::None,
13356 )
13357 });
13358 })
13359 }
13360
13361 pub fn select_to_end_of_paragraph(
13362 &mut self,
13363 _: &SelectToEndOfParagraph,
13364 window: &mut Window,
13365 cx: &mut Context<Self>,
13366 ) {
13367 if matches!(self.mode, EditorMode::SingleLine) {
13368 cx.propagate();
13369 return;
13370 }
13371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13372 self.change_selections(Default::default(), window, cx, |s| {
13373 s.move_heads_with(|map, head, _| {
13374 (
13375 movement::end_of_paragraph(map, head, 1),
13376 SelectionGoal::None,
13377 )
13378 });
13379 })
13380 }
13381
13382 pub fn move_to_start_of_excerpt(
13383 &mut self,
13384 _: &MoveToStartOfExcerpt,
13385 window: &mut Window,
13386 cx: &mut Context<Self>,
13387 ) {
13388 if matches!(self.mode, EditorMode::SingleLine) {
13389 cx.propagate();
13390 return;
13391 }
13392 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13393 self.change_selections(Default::default(), window, cx, |s| {
13394 s.move_with(|map, selection| {
13395 selection.collapse_to(
13396 movement::start_of_excerpt(
13397 map,
13398 selection.head(),
13399 workspace::searchable::Direction::Prev,
13400 ),
13401 SelectionGoal::None,
13402 )
13403 });
13404 })
13405 }
13406
13407 pub fn move_to_start_of_next_excerpt(
13408 &mut self,
13409 _: &MoveToStartOfNextExcerpt,
13410 window: &mut Window,
13411 cx: &mut Context<Self>,
13412 ) {
13413 if matches!(self.mode, EditorMode::SingleLine) {
13414 cx.propagate();
13415 return;
13416 }
13417
13418 self.change_selections(Default::default(), window, cx, |s| {
13419 s.move_with(|map, selection| {
13420 selection.collapse_to(
13421 movement::start_of_excerpt(
13422 map,
13423 selection.head(),
13424 workspace::searchable::Direction::Next,
13425 ),
13426 SelectionGoal::None,
13427 )
13428 });
13429 })
13430 }
13431
13432 pub fn move_to_end_of_excerpt(
13433 &mut self,
13434 _: &MoveToEndOfExcerpt,
13435 window: &mut Window,
13436 cx: &mut Context<Self>,
13437 ) {
13438 if matches!(self.mode, EditorMode::SingleLine) {
13439 cx.propagate();
13440 return;
13441 }
13442 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13443 self.change_selections(Default::default(), window, cx, |s| {
13444 s.move_with(|map, selection| {
13445 selection.collapse_to(
13446 movement::end_of_excerpt(
13447 map,
13448 selection.head(),
13449 workspace::searchable::Direction::Next,
13450 ),
13451 SelectionGoal::None,
13452 )
13453 });
13454 })
13455 }
13456
13457 pub fn move_to_end_of_previous_excerpt(
13458 &mut self,
13459 _: &MoveToEndOfPreviousExcerpt,
13460 window: &mut Window,
13461 cx: &mut Context<Self>,
13462 ) {
13463 if matches!(self.mode, EditorMode::SingleLine) {
13464 cx.propagate();
13465 return;
13466 }
13467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13468 self.change_selections(Default::default(), window, cx, |s| {
13469 s.move_with(|map, selection| {
13470 selection.collapse_to(
13471 movement::end_of_excerpt(
13472 map,
13473 selection.head(),
13474 workspace::searchable::Direction::Prev,
13475 ),
13476 SelectionGoal::None,
13477 )
13478 });
13479 })
13480 }
13481
13482 pub fn select_to_start_of_excerpt(
13483 &mut self,
13484 _: &SelectToStartOfExcerpt,
13485 window: &mut Window,
13486 cx: &mut Context<Self>,
13487 ) {
13488 if matches!(self.mode, EditorMode::SingleLine) {
13489 cx.propagate();
13490 return;
13491 }
13492 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13493 self.change_selections(Default::default(), window, cx, |s| {
13494 s.move_heads_with(|map, head, _| {
13495 (
13496 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13497 SelectionGoal::None,
13498 )
13499 });
13500 })
13501 }
13502
13503 pub fn select_to_start_of_next_excerpt(
13504 &mut self,
13505 _: &SelectToStartOfNextExcerpt,
13506 window: &mut Window,
13507 cx: &mut Context<Self>,
13508 ) {
13509 if matches!(self.mode, EditorMode::SingleLine) {
13510 cx.propagate();
13511 return;
13512 }
13513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13514 self.change_selections(Default::default(), window, cx, |s| {
13515 s.move_heads_with(|map, head, _| {
13516 (
13517 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13518 SelectionGoal::None,
13519 )
13520 });
13521 })
13522 }
13523
13524 pub fn select_to_end_of_excerpt(
13525 &mut self,
13526 _: &SelectToEndOfExcerpt,
13527 window: &mut Window,
13528 cx: &mut Context<Self>,
13529 ) {
13530 if matches!(self.mode, EditorMode::SingleLine) {
13531 cx.propagate();
13532 return;
13533 }
13534 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13535 self.change_selections(Default::default(), window, cx, |s| {
13536 s.move_heads_with(|map, head, _| {
13537 (
13538 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13539 SelectionGoal::None,
13540 )
13541 });
13542 })
13543 }
13544
13545 pub fn select_to_end_of_previous_excerpt(
13546 &mut self,
13547 _: &SelectToEndOfPreviousExcerpt,
13548 window: &mut Window,
13549 cx: &mut Context<Self>,
13550 ) {
13551 if matches!(self.mode, EditorMode::SingleLine) {
13552 cx.propagate();
13553 return;
13554 }
13555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13556 self.change_selections(Default::default(), window, cx, |s| {
13557 s.move_heads_with(|map, head, _| {
13558 (
13559 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13560 SelectionGoal::None,
13561 )
13562 });
13563 })
13564 }
13565
13566 pub fn move_to_beginning(
13567 &mut self,
13568 _: &MoveToBeginning,
13569 window: &mut Window,
13570 cx: &mut Context<Self>,
13571 ) {
13572 if matches!(self.mode, EditorMode::SingleLine) {
13573 cx.propagate();
13574 return;
13575 }
13576 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13577 self.change_selections(Default::default(), window, cx, |s| {
13578 s.select_ranges(vec![0..0]);
13579 });
13580 }
13581
13582 pub fn select_to_beginning(
13583 &mut self,
13584 _: &SelectToBeginning,
13585 window: &mut Window,
13586 cx: &mut Context<Self>,
13587 ) {
13588 let mut selection = self.selections.last::<Point>(cx);
13589 selection.set_head(Point::zero(), SelectionGoal::None);
13590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13591 self.change_selections(Default::default(), window, cx, |s| {
13592 s.select(vec![selection]);
13593 });
13594 }
13595
13596 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13597 if matches!(self.mode, EditorMode::SingleLine) {
13598 cx.propagate();
13599 return;
13600 }
13601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13602 let cursor = self.buffer.read(cx).read(cx).len();
13603 self.change_selections(Default::default(), window, cx, |s| {
13604 s.select_ranges(vec![cursor..cursor])
13605 });
13606 }
13607
13608 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13609 self.nav_history = nav_history;
13610 }
13611
13612 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13613 self.nav_history.as_ref()
13614 }
13615
13616 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13617 self.push_to_nav_history(
13618 self.selections.newest_anchor().head(),
13619 None,
13620 false,
13621 true,
13622 cx,
13623 );
13624 }
13625
13626 fn push_to_nav_history(
13627 &mut self,
13628 cursor_anchor: Anchor,
13629 new_position: Option<Point>,
13630 is_deactivate: bool,
13631 always: bool,
13632 cx: &mut Context<Self>,
13633 ) {
13634 if let Some(nav_history) = self.nav_history.as_mut() {
13635 let buffer = self.buffer.read(cx).read(cx);
13636 let cursor_position = cursor_anchor.to_point(&buffer);
13637 let scroll_state = self.scroll_manager.anchor();
13638 let scroll_top_row = scroll_state.top_row(&buffer);
13639 drop(buffer);
13640
13641 if let Some(new_position) = new_position {
13642 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13643 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13644 return;
13645 }
13646 }
13647
13648 nav_history.push(
13649 Some(NavigationData {
13650 cursor_anchor,
13651 cursor_position,
13652 scroll_anchor: scroll_state,
13653 scroll_top_row,
13654 }),
13655 cx,
13656 );
13657 cx.emit(EditorEvent::PushedToNavHistory {
13658 anchor: cursor_anchor,
13659 is_deactivate,
13660 })
13661 }
13662 }
13663
13664 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13665 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13666 let buffer = self.buffer.read(cx).snapshot(cx);
13667 let mut selection = self.selections.first::<usize>(cx);
13668 selection.set_head(buffer.len(), SelectionGoal::None);
13669 self.change_selections(Default::default(), window, cx, |s| {
13670 s.select(vec![selection]);
13671 });
13672 }
13673
13674 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13675 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13676 let end = self.buffer.read(cx).read(cx).len();
13677 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13678 s.select_ranges(vec![0..end]);
13679 });
13680 }
13681
13682 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13684 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13685 let mut selections = self.selections.all::<Point>(cx);
13686 let max_point = display_map.buffer_snapshot.max_point();
13687 for selection in &mut selections {
13688 let rows = selection.spanned_rows(true, &display_map);
13689 selection.start = Point::new(rows.start.0, 0);
13690 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13691 selection.reversed = false;
13692 }
13693 self.change_selections(Default::default(), window, cx, |s| {
13694 s.select(selections);
13695 });
13696 }
13697
13698 pub fn split_selection_into_lines(
13699 &mut self,
13700 action: &SplitSelectionIntoLines,
13701 window: &mut Window,
13702 cx: &mut Context<Self>,
13703 ) {
13704 let selections = self
13705 .selections
13706 .all::<Point>(cx)
13707 .into_iter()
13708 .map(|selection| selection.start..selection.end)
13709 .collect::<Vec<_>>();
13710 self.unfold_ranges(&selections, true, true, cx);
13711
13712 let mut new_selection_ranges = Vec::new();
13713 {
13714 let buffer = self.buffer.read(cx).read(cx);
13715 for selection in selections {
13716 for row in selection.start.row..selection.end.row {
13717 let line_start = Point::new(row, 0);
13718 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13719
13720 if action.keep_selections {
13721 // Keep the selection range for each line
13722 let selection_start = if row == selection.start.row {
13723 selection.start
13724 } else {
13725 line_start
13726 };
13727 new_selection_ranges.push(selection_start..line_end);
13728 } else {
13729 // Collapse to cursor at end of line
13730 new_selection_ranges.push(line_end..line_end);
13731 }
13732 }
13733
13734 let is_multiline_selection = selection.start.row != selection.end.row;
13735 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13736 // so this action feels more ergonomic when paired with other selection operations
13737 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13738 if !should_skip_last {
13739 if action.keep_selections {
13740 if is_multiline_selection {
13741 let line_start = Point::new(selection.end.row, 0);
13742 new_selection_ranges.push(line_start..selection.end);
13743 } else {
13744 new_selection_ranges.push(selection.start..selection.end);
13745 }
13746 } else {
13747 new_selection_ranges.push(selection.end..selection.end);
13748 }
13749 }
13750 }
13751 }
13752 self.change_selections(Default::default(), window, cx, |s| {
13753 s.select_ranges(new_selection_ranges);
13754 });
13755 }
13756
13757 pub fn add_selection_above(
13758 &mut self,
13759 _: &AddSelectionAbove,
13760 window: &mut Window,
13761 cx: &mut Context<Self>,
13762 ) {
13763 self.add_selection(true, window, cx);
13764 }
13765
13766 pub fn add_selection_below(
13767 &mut self,
13768 _: &AddSelectionBelow,
13769 window: &mut Window,
13770 cx: &mut Context<Self>,
13771 ) {
13772 self.add_selection(false, window, cx);
13773 }
13774
13775 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13776 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13777
13778 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13779 let all_selections = self.selections.all::<Point>(cx);
13780 let text_layout_details = self.text_layout_details(window);
13781
13782 let (mut columnar_selections, new_selections_to_columnarize) = {
13783 if let Some(state) = self.add_selections_state.as_ref() {
13784 let columnar_selection_ids: HashSet<_> = state
13785 .groups
13786 .iter()
13787 .flat_map(|group| group.stack.iter())
13788 .copied()
13789 .collect();
13790
13791 all_selections
13792 .into_iter()
13793 .partition(|s| columnar_selection_ids.contains(&s.id))
13794 } else {
13795 (Vec::new(), all_selections)
13796 }
13797 };
13798
13799 let mut state = self
13800 .add_selections_state
13801 .take()
13802 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13803
13804 for selection in new_selections_to_columnarize {
13805 let range = selection.display_range(&display_map).sorted();
13806 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13807 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13808 let positions = start_x.min(end_x)..start_x.max(end_x);
13809 let mut stack = Vec::new();
13810 for row in range.start.row().0..=range.end.row().0 {
13811 if let Some(selection) = self.selections.build_columnar_selection(
13812 &display_map,
13813 DisplayRow(row),
13814 &positions,
13815 selection.reversed,
13816 &text_layout_details,
13817 ) {
13818 stack.push(selection.id);
13819 columnar_selections.push(selection);
13820 }
13821 }
13822 if !stack.is_empty() {
13823 if above {
13824 stack.reverse();
13825 }
13826 state.groups.push(AddSelectionsGroup { above, stack });
13827 }
13828 }
13829
13830 let mut final_selections = Vec::new();
13831 let end_row = if above {
13832 DisplayRow(0)
13833 } else {
13834 display_map.max_point().row()
13835 };
13836
13837 let mut last_added_item_per_group = HashMap::default();
13838 for group in state.groups.iter_mut() {
13839 if let Some(last_id) = group.stack.last() {
13840 last_added_item_per_group.insert(*last_id, group);
13841 }
13842 }
13843
13844 for selection in columnar_selections {
13845 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13846 if above == group.above {
13847 let range = selection.display_range(&display_map).sorted();
13848 debug_assert_eq!(range.start.row(), range.end.row());
13849 let mut row = range.start.row();
13850 let positions =
13851 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13852 px(start)..px(end)
13853 } else {
13854 let start_x =
13855 display_map.x_for_display_point(range.start, &text_layout_details);
13856 let end_x =
13857 display_map.x_for_display_point(range.end, &text_layout_details);
13858 start_x.min(end_x)..start_x.max(end_x)
13859 };
13860
13861 let mut maybe_new_selection = None;
13862 while row != end_row {
13863 if above {
13864 row.0 -= 1;
13865 } else {
13866 row.0 += 1;
13867 }
13868 if let Some(new_selection) = self.selections.build_columnar_selection(
13869 &display_map,
13870 row,
13871 &positions,
13872 selection.reversed,
13873 &text_layout_details,
13874 ) {
13875 maybe_new_selection = Some(new_selection);
13876 break;
13877 }
13878 }
13879
13880 if let Some(new_selection) = maybe_new_selection {
13881 group.stack.push(new_selection.id);
13882 if above {
13883 final_selections.push(new_selection);
13884 final_selections.push(selection);
13885 } else {
13886 final_selections.push(selection);
13887 final_selections.push(new_selection);
13888 }
13889 } else {
13890 final_selections.push(selection);
13891 }
13892 } else {
13893 group.stack.pop();
13894 }
13895 } else {
13896 final_selections.push(selection);
13897 }
13898 }
13899
13900 self.change_selections(Default::default(), window, cx, |s| {
13901 s.select(final_selections);
13902 });
13903
13904 let final_selection_ids: HashSet<_> = self
13905 .selections
13906 .all::<Point>(cx)
13907 .iter()
13908 .map(|s| s.id)
13909 .collect();
13910 state.groups.retain_mut(|group| {
13911 // selections might get merged above so we remove invalid items from stacks
13912 group.stack.retain(|id| final_selection_ids.contains(id));
13913
13914 // single selection in stack can be treated as initial state
13915 group.stack.len() > 1
13916 });
13917
13918 if !state.groups.is_empty() {
13919 self.add_selections_state = Some(state);
13920 }
13921 }
13922
13923 fn select_match_ranges(
13924 &mut self,
13925 range: Range<usize>,
13926 reversed: bool,
13927 replace_newest: bool,
13928 auto_scroll: Option<Autoscroll>,
13929 window: &mut Window,
13930 cx: &mut Context<Editor>,
13931 ) {
13932 self.unfold_ranges(
13933 std::slice::from_ref(&range),
13934 false,
13935 auto_scroll.is_some(),
13936 cx,
13937 );
13938 let effects = if let Some(scroll) = auto_scroll {
13939 SelectionEffects::scroll(scroll)
13940 } else {
13941 SelectionEffects::no_scroll()
13942 };
13943 self.change_selections(effects, window, cx, |s| {
13944 if replace_newest {
13945 s.delete(s.newest_anchor().id);
13946 }
13947 if reversed {
13948 s.insert_range(range.end..range.start);
13949 } else {
13950 s.insert_range(range);
13951 }
13952 });
13953 }
13954
13955 pub fn select_next_match_internal(
13956 &mut self,
13957 display_map: &DisplaySnapshot,
13958 replace_newest: bool,
13959 autoscroll: Option<Autoscroll>,
13960 window: &mut Window,
13961 cx: &mut Context<Self>,
13962 ) -> Result<()> {
13963 let buffer = &display_map.buffer_snapshot;
13964 let mut selections = self.selections.all::<usize>(cx);
13965 if let Some(mut select_next_state) = self.select_next_state.take() {
13966 let query = &select_next_state.query;
13967 if !select_next_state.done {
13968 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13969 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13970 let mut next_selected_range = None;
13971
13972 let bytes_after_last_selection =
13973 buffer.bytes_in_range(last_selection.end..buffer.len());
13974 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13975 let query_matches = query
13976 .stream_find_iter(bytes_after_last_selection)
13977 .map(|result| (last_selection.end, result))
13978 .chain(
13979 query
13980 .stream_find_iter(bytes_before_first_selection)
13981 .map(|result| (0, result)),
13982 );
13983
13984 for (start_offset, query_match) in query_matches {
13985 let query_match = query_match.unwrap(); // can only fail due to I/O
13986 let offset_range =
13987 start_offset + query_match.start()..start_offset + query_match.end();
13988
13989 if !select_next_state.wordwise
13990 || (!buffer.is_inside_word(offset_range.start, false)
13991 && !buffer.is_inside_word(offset_range.end, false))
13992 {
13993 // TODO: This is n^2, because we might check all the selections
13994 if !selections
13995 .iter()
13996 .any(|selection| selection.range().overlaps(&offset_range))
13997 {
13998 next_selected_range = Some(offset_range);
13999 break;
14000 }
14001 }
14002 }
14003
14004 if let Some(next_selected_range) = next_selected_range {
14005 self.select_match_ranges(
14006 next_selected_range,
14007 last_selection.reversed,
14008 replace_newest,
14009 autoscroll,
14010 window,
14011 cx,
14012 );
14013 } else {
14014 select_next_state.done = true;
14015 }
14016 }
14017
14018 self.select_next_state = Some(select_next_state);
14019 } else {
14020 let mut only_carets = true;
14021 let mut same_text_selected = true;
14022 let mut selected_text = None;
14023
14024 let mut selections_iter = selections.iter().peekable();
14025 while let Some(selection) = selections_iter.next() {
14026 if selection.start != selection.end {
14027 only_carets = false;
14028 }
14029
14030 if same_text_selected {
14031 if selected_text.is_none() {
14032 selected_text =
14033 Some(buffer.text_for_range(selection.range()).collect::<String>());
14034 }
14035
14036 if let Some(next_selection) = selections_iter.peek() {
14037 if next_selection.range().len() == selection.range().len() {
14038 let next_selected_text = buffer
14039 .text_for_range(next_selection.range())
14040 .collect::<String>();
14041 if Some(next_selected_text) != selected_text {
14042 same_text_selected = false;
14043 selected_text = None;
14044 }
14045 } else {
14046 same_text_selected = false;
14047 selected_text = None;
14048 }
14049 }
14050 }
14051 }
14052
14053 if only_carets {
14054 for selection in &mut selections {
14055 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14056 selection.start = word_range.start;
14057 selection.end = word_range.end;
14058 selection.goal = SelectionGoal::None;
14059 selection.reversed = false;
14060 self.select_match_ranges(
14061 selection.start..selection.end,
14062 selection.reversed,
14063 replace_newest,
14064 autoscroll,
14065 window,
14066 cx,
14067 );
14068 }
14069
14070 if selections.len() == 1 {
14071 let selection = selections
14072 .last()
14073 .expect("ensured that there's only one selection");
14074 let query = buffer
14075 .text_for_range(selection.start..selection.end)
14076 .collect::<String>();
14077 let is_empty = query.is_empty();
14078 let select_state = SelectNextState {
14079 query: AhoCorasick::new(&[query])?,
14080 wordwise: true,
14081 done: is_empty,
14082 };
14083 self.select_next_state = Some(select_state);
14084 } else {
14085 self.select_next_state = None;
14086 }
14087 } else if let Some(selected_text) = selected_text {
14088 self.select_next_state = Some(SelectNextState {
14089 query: AhoCorasick::new(&[selected_text])?,
14090 wordwise: false,
14091 done: false,
14092 });
14093 self.select_next_match_internal(
14094 display_map,
14095 replace_newest,
14096 autoscroll,
14097 window,
14098 cx,
14099 )?;
14100 }
14101 }
14102 Ok(())
14103 }
14104
14105 pub fn select_all_matches(
14106 &mut self,
14107 _action: &SelectAllMatches,
14108 window: &mut Window,
14109 cx: &mut Context<Self>,
14110 ) -> Result<()> {
14111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14112
14113 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14114
14115 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14116 let Some(select_next_state) = self.select_next_state.as_mut() else {
14117 return Ok(());
14118 };
14119 if select_next_state.done {
14120 return Ok(());
14121 }
14122
14123 let mut new_selections = Vec::new();
14124
14125 let reversed = self.selections.oldest::<usize>(cx).reversed;
14126 let buffer = &display_map.buffer_snapshot;
14127 let query_matches = select_next_state
14128 .query
14129 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14130
14131 for query_match in query_matches.into_iter() {
14132 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14133 let offset_range = if reversed {
14134 query_match.end()..query_match.start()
14135 } else {
14136 query_match.start()..query_match.end()
14137 };
14138
14139 if !select_next_state.wordwise
14140 || (!buffer.is_inside_word(offset_range.start, false)
14141 && !buffer.is_inside_word(offset_range.end, false))
14142 {
14143 new_selections.push(offset_range.start..offset_range.end);
14144 }
14145 }
14146
14147 select_next_state.done = true;
14148
14149 if new_selections.is_empty() {
14150 log::error!("bug: new_selections is empty in select_all_matches");
14151 return Ok(());
14152 }
14153
14154 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14155 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14156 selections.select_ranges(new_selections)
14157 });
14158
14159 Ok(())
14160 }
14161
14162 pub fn select_next(
14163 &mut self,
14164 action: &SelectNext,
14165 window: &mut Window,
14166 cx: &mut Context<Self>,
14167 ) -> Result<()> {
14168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14169 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14170 self.select_next_match_internal(
14171 &display_map,
14172 action.replace_newest,
14173 Some(Autoscroll::newest()),
14174 window,
14175 cx,
14176 )?;
14177 Ok(())
14178 }
14179
14180 pub fn select_previous(
14181 &mut self,
14182 action: &SelectPrevious,
14183 window: &mut Window,
14184 cx: &mut Context<Self>,
14185 ) -> Result<()> {
14186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14187 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14188 let buffer = &display_map.buffer_snapshot;
14189 let mut selections = self.selections.all::<usize>(cx);
14190 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14191 let query = &select_prev_state.query;
14192 if !select_prev_state.done {
14193 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14194 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14195 let mut next_selected_range = None;
14196 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14197 let bytes_before_last_selection =
14198 buffer.reversed_bytes_in_range(0..last_selection.start);
14199 let bytes_after_first_selection =
14200 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14201 let query_matches = query
14202 .stream_find_iter(bytes_before_last_selection)
14203 .map(|result| (last_selection.start, result))
14204 .chain(
14205 query
14206 .stream_find_iter(bytes_after_first_selection)
14207 .map(|result| (buffer.len(), result)),
14208 );
14209 for (end_offset, query_match) in query_matches {
14210 let query_match = query_match.unwrap(); // can only fail due to I/O
14211 let offset_range =
14212 end_offset - query_match.end()..end_offset - query_match.start();
14213
14214 if !select_prev_state.wordwise
14215 || (!buffer.is_inside_word(offset_range.start, false)
14216 && !buffer.is_inside_word(offset_range.end, false))
14217 {
14218 next_selected_range = Some(offset_range);
14219 break;
14220 }
14221 }
14222
14223 if let Some(next_selected_range) = next_selected_range {
14224 self.select_match_ranges(
14225 next_selected_range,
14226 last_selection.reversed,
14227 action.replace_newest,
14228 Some(Autoscroll::newest()),
14229 window,
14230 cx,
14231 );
14232 } else {
14233 select_prev_state.done = true;
14234 }
14235 }
14236
14237 self.select_prev_state = Some(select_prev_state);
14238 } else {
14239 let mut only_carets = true;
14240 let mut same_text_selected = true;
14241 let mut selected_text = None;
14242
14243 let mut selections_iter = selections.iter().peekable();
14244 while let Some(selection) = selections_iter.next() {
14245 if selection.start != selection.end {
14246 only_carets = false;
14247 }
14248
14249 if same_text_selected {
14250 if selected_text.is_none() {
14251 selected_text =
14252 Some(buffer.text_for_range(selection.range()).collect::<String>());
14253 }
14254
14255 if let Some(next_selection) = selections_iter.peek() {
14256 if next_selection.range().len() == selection.range().len() {
14257 let next_selected_text = buffer
14258 .text_for_range(next_selection.range())
14259 .collect::<String>();
14260 if Some(next_selected_text) != selected_text {
14261 same_text_selected = false;
14262 selected_text = None;
14263 }
14264 } else {
14265 same_text_selected = false;
14266 selected_text = None;
14267 }
14268 }
14269 }
14270 }
14271
14272 if only_carets {
14273 for selection in &mut selections {
14274 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14275 selection.start = word_range.start;
14276 selection.end = word_range.end;
14277 selection.goal = SelectionGoal::None;
14278 selection.reversed = false;
14279 self.select_match_ranges(
14280 selection.start..selection.end,
14281 selection.reversed,
14282 action.replace_newest,
14283 Some(Autoscroll::newest()),
14284 window,
14285 cx,
14286 );
14287 }
14288 if selections.len() == 1 {
14289 let selection = selections
14290 .last()
14291 .expect("ensured that there's only one selection");
14292 let query = buffer
14293 .text_for_range(selection.start..selection.end)
14294 .collect::<String>();
14295 let is_empty = query.is_empty();
14296 let select_state = SelectNextState {
14297 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14298 wordwise: true,
14299 done: is_empty,
14300 };
14301 self.select_prev_state = Some(select_state);
14302 } else {
14303 self.select_prev_state = None;
14304 }
14305 } else if let Some(selected_text) = selected_text {
14306 self.select_prev_state = Some(SelectNextState {
14307 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14308 wordwise: false,
14309 done: false,
14310 });
14311 self.select_previous(action, window, cx)?;
14312 }
14313 }
14314 Ok(())
14315 }
14316
14317 pub fn find_next_match(
14318 &mut self,
14319 _: &FindNextMatch,
14320 window: &mut Window,
14321 cx: &mut Context<Self>,
14322 ) -> Result<()> {
14323 let selections = self.selections.disjoint_anchors();
14324 match selections.first() {
14325 Some(first) if selections.len() >= 2 => {
14326 self.change_selections(Default::default(), window, cx, |s| {
14327 s.select_ranges([first.range()]);
14328 });
14329 }
14330 _ => self.select_next(
14331 &SelectNext {
14332 replace_newest: true,
14333 },
14334 window,
14335 cx,
14336 )?,
14337 }
14338 Ok(())
14339 }
14340
14341 pub fn find_previous_match(
14342 &mut self,
14343 _: &FindPreviousMatch,
14344 window: &mut Window,
14345 cx: &mut Context<Self>,
14346 ) -> Result<()> {
14347 let selections = self.selections.disjoint_anchors();
14348 match selections.last() {
14349 Some(last) if selections.len() >= 2 => {
14350 self.change_selections(Default::default(), window, cx, |s| {
14351 s.select_ranges([last.range()]);
14352 });
14353 }
14354 _ => self.select_previous(
14355 &SelectPrevious {
14356 replace_newest: true,
14357 },
14358 window,
14359 cx,
14360 )?,
14361 }
14362 Ok(())
14363 }
14364
14365 pub fn toggle_comments(
14366 &mut self,
14367 action: &ToggleComments,
14368 window: &mut Window,
14369 cx: &mut Context<Self>,
14370 ) {
14371 if self.read_only(cx) {
14372 return;
14373 }
14374 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14375 let text_layout_details = &self.text_layout_details(window);
14376 self.transact(window, cx, |this, window, cx| {
14377 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14378 let mut edits = Vec::new();
14379 let mut selection_edit_ranges = Vec::new();
14380 let mut last_toggled_row = None;
14381 let snapshot = this.buffer.read(cx).read(cx);
14382 let empty_str: Arc<str> = Arc::default();
14383 let mut suffixes_inserted = Vec::new();
14384 let ignore_indent = action.ignore_indent;
14385
14386 fn comment_prefix_range(
14387 snapshot: &MultiBufferSnapshot,
14388 row: MultiBufferRow,
14389 comment_prefix: &str,
14390 comment_prefix_whitespace: &str,
14391 ignore_indent: bool,
14392 ) -> Range<Point> {
14393 let indent_size = if ignore_indent {
14394 0
14395 } else {
14396 snapshot.indent_size_for_line(row).len
14397 };
14398
14399 let start = Point::new(row.0, indent_size);
14400
14401 let mut line_bytes = snapshot
14402 .bytes_in_range(start..snapshot.max_point())
14403 .flatten()
14404 .copied();
14405
14406 // If this line currently begins with the line comment prefix, then record
14407 // the range containing the prefix.
14408 if line_bytes
14409 .by_ref()
14410 .take(comment_prefix.len())
14411 .eq(comment_prefix.bytes())
14412 {
14413 // Include any whitespace that matches the comment prefix.
14414 let matching_whitespace_len = line_bytes
14415 .zip(comment_prefix_whitespace.bytes())
14416 .take_while(|(a, b)| a == b)
14417 .count() as u32;
14418 let end = Point::new(
14419 start.row,
14420 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14421 );
14422 start..end
14423 } else {
14424 start..start
14425 }
14426 }
14427
14428 fn comment_suffix_range(
14429 snapshot: &MultiBufferSnapshot,
14430 row: MultiBufferRow,
14431 comment_suffix: &str,
14432 comment_suffix_has_leading_space: bool,
14433 ) -> Range<Point> {
14434 let end = Point::new(row.0, snapshot.line_len(row));
14435 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14436
14437 let mut line_end_bytes = snapshot
14438 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14439 .flatten()
14440 .copied();
14441
14442 let leading_space_len = if suffix_start_column > 0
14443 && line_end_bytes.next() == Some(b' ')
14444 && comment_suffix_has_leading_space
14445 {
14446 1
14447 } else {
14448 0
14449 };
14450
14451 // If this line currently begins with the line comment prefix, then record
14452 // the range containing the prefix.
14453 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14454 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14455 start..end
14456 } else {
14457 end..end
14458 }
14459 }
14460
14461 // TODO: Handle selections that cross excerpts
14462 for selection in &mut selections {
14463 let start_column = snapshot
14464 .indent_size_for_line(MultiBufferRow(selection.start.row))
14465 .len;
14466 let language = if let Some(language) =
14467 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14468 {
14469 language
14470 } else {
14471 continue;
14472 };
14473
14474 selection_edit_ranges.clear();
14475
14476 // If multiple selections contain a given row, avoid processing that
14477 // row more than once.
14478 let mut start_row = MultiBufferRow(selection.start.row);
14479 if last_toggled_row == Some(start_row) {
14480 start_row = start_row.next_row();
14481 }
14482 let end_row =
14483 if selection.end.row > selection.start.row && selection.end.column == 0 {
14484 MultiBufferRow(selection.end.row - 1)
14485 } else {
14486 MultiBufferRow(selection.end.row)
14487 };
14488 last_toggled_row = Some(end_row);
14489
14490 if start_row > end_row {
14491 continue;
14492 }
14493
14494 // If the language has line comments, toggle those.
14495 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14496
14497 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14498 if ignore_indent {
14499 full_comment_prefixes = full_comment_prefixes
14500 .into_iter()
14501 .map(|s| Arc::from(s.trim_end()))
14502 .collect();
14503 }
14504
14505 if !full_comment_prefixes.is_empty() {
14506 let first_prefix = full_comment_prefixes
14507 .first()
14508 .expect("prefixes is non-empty");
14509 let prefix_trimmed_lengths = full_comment_prefixes
14510 .iter()
14511 .map(|p| p.trim_end_matches(' ').len())
14512 .collect::<SmallVec<[usize; 4]>>();
14513
14514 let mut all_selection_lines_are_comments = true;
14515
14516 for row in start_row.0..=end_row.0 {
14517 let row = MultiBufferRow(row);
14518 if start_row < end_row && snapshot.is_line_blank(row) {
14519 continue;
14520 }
14521
14522 let prefix_range = full_comment_prefixes
14523 .iter()
14524 .zip(prefix_trimmed_lengths.iter().copied())
14525 .map(|(prefix, trimmed_prefix_len)| {
14526 comment_prefix_range(
14527 snapshot.deref(),
14528 row,
14529 &prefix[..trimmed_prefix_len],
14530 &prefix[trimmed_prefix_len..],
14531 ignore_indent,
14532 )
14533 })
14534 .max_by_key(|range| range.end.column - range.start.column)
14535 .expect("prefixes is non-empty");
14536
14537 if prefix_range.is_empty() {
14538 all_selection_lines_are_comments = false;
14539 }
14540
14541 selection_edit_ranges.push(prefix_range);
14542 }
14543
14544 if all_selection_lines_are_comments {
14545 edits.extend(
14546 selection_edit_ranges
14547 .iter()
14548 .cloned()
14549 .map(|range| (range, empty_str.clone())),
14550 );
14551 } else {
14552 let min_column = selection_edit_ranges
14553 .iter()
14554 .map(|range| range.start.column)
14555 .min()
14556 .unwrap_or(0);
14557 edits.extend(selection_edit_ranges.iter().map(|range| {
14558 let position = Point::new(range.start.row, min_column);
14559 (position..position, first_prefix.clone())
14560 }));
14561 }
14562 } else if let Some(BlockCommentConfig {
14563 start: full_comment_prefix,
14564 end: comment_suffix,
14565 ..
14566 }) = language.block_comment()
14567 {
14568 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14569 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14570 let prefix_range = comment_prefix_range(
14571 snapshot.deref(),
14572 start_row,
14573 comment_prefix,
14574 comment_prefix_whitespace,
14575 ignore_indent,
14576 );
14577 let suffix_range = comment_suffix_range(
14578 snapshot.deref(),
14579 end_row,
14580 comment_suffix.trim_start_matches(' '),
14581 comment_suffix.starts_with(' '),
14582 );
14583
14584 if prefix_range.is_empty() || suffix_range.is_empty() {
14585 edits.push((
14586 prefix_range.start..prefix_range.start,
14587 full_comment_prefix.clone(),
14588 ));
14589 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14590 suffixes_inserted.push((end_row, comment_suffix.len()));
14591 } else {
14592 edits.push((prefix_range, empty_str.clone()));
14593 edits.push((suffix_range, empty_str.clone()));
14594 }
14595 } else {
14596 continue;
14597 }
14598 }
14599
14600 drop(snapshot);
14601 this.buffer.update(cx, |buffer, cx| {
14602 buffer.edit(edits, None, cx);
14603 });
14604
14605 // Adjust selections so that they end before any comment suffixes that
14606 // were inserted.
14607 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14608 let mut selections = this.selections.all::<Point>(cx);
14609 let snapshot = this.buffer.read(cx).read(cx);
14610 for selection in &mut selections {
14611 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14612 match row.cmp(&MultiBufferRow(selection.end.row)) {
14613 Ordering::Less => {
14614 suffixes_inserted.next();
14615 continue;
14616 }
14617 Ordering::Greater => break,
14618 Ordering::Equal => {
14619 if selection.end.column == snapshot.line_len(row) {
14620 if selection.is_empty() {
14621 selection.start.column -= suffix_len as u32;
14622 }
14623 selection.end.column -= suffix_len as u32;
14624 }
14625 break;
14626 }
14627 }
14628 }
14629 }
14630
14631 drop(snapshot);
14632 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14633
14634 let selections = this.selections.all::<Point>(cx);
14635 let selections_on_single_row = selections.windows(2).all(|selections| {
14636 selections[0].start.row == selections[1].start.row
14637 && selections[0].end.row == selections[1].end.row
14638 && selections[0].start.row == selections[0].end.row
14639 });
14640 let selections_selecting = selections
14641 .iter()
14642 .any(|selection| selection.start != selection.end);
14643 let advance_downwards = action.advance_downwards
14644 && selections_on_single_row
14645 && !selections_selecting
14646 && !matches!(this.mode, EditorMode::SingleLine);
14647
14648 if advance_downwards {
14649 let snapshot = this.buffer.read(cx).snapshot(cx);
14650
14651 this.change_selections(Default::default(), window, cx, |s| {
14652 s.move_cursors_with(|display_snapshot, display_point, _| {
14653 let mut point = display_point.to_point(display_snapshot);
14654 point.row += 1;
14655 point = snapshot.clip_point(point, Bias::Left);
14656 let display_point = point.to_display_point(display_snapshot);
14657 let goal = SelectionGoal::HorizontalPosition(
14658 display_snapshot
14659 .x_for_display_point(display_point, text_layout_details)
14660 .into(),
14661 );
14662 (display_point, goal)
14663 })
14664 });
14665 }
14666 });
14667 }
14668
14669 pub fn select_enclosing_symbol(
14670 &mut self,
14671 _: &SelectEnclosingSymbol,
14672 window: &mut Window,
14673 cx: &mut Context<Self>,
14674 ) {
14675 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14676
14677 let buffer = self.buffer.read(cx).snapshot(cx);
14678 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14679
14680 fn update_selection(
14681 selection: &Selection<usize>,
14682 buffer_snap: &MultiBufferSnapshot,
14683 ) -> Option<Selection<usize>> {
14684 let cursor = selection.head();
14685 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14686 for symbol in symbols.iter().rev() {
14687 let start = symbol.range.start.to_offset(buffer_snap);
14688 let end = symbol.range.end.to_offset(buffer_snap);
14689 let new_range = start..end;
14690 if start < selection.start || end > selection.end {
14691 return Some(Selection {
14692 id: selection.id,
14693 start: new_range.start,
14694 end: new_range.end,
14695 goal: SelectionGoal::None,
14696 reversed: selection.reversed,
14697 });
14698 }
14699 }
14700 None
14701 }
14702
14703 let mut selected_larger_symbol = false;
14704 let new_selections = old_selections
14705 .iter()
14706 .map(|selection| match update_selection(selection, &buffer) {
14707 Some(new_selection) => {
14708 if new_selection.range() != selection.range() {
14709 selected_larger_symbol = true;
14710 }
14711 new_selection
14712 }
14713 None => selection.clone(),
14714 })
14715 .collect::<Vec<_>>();
14716
14717 if selected_larger_symbol {
14718 self.change_selections(Default::default(), window, cx, |s| {
14719 s.select(new_selections);
14720 });
14721 }
14722 }
14723
14724 pub fn select_larger_syntax_node(
14725 &mut self,
14726 _: &SelectLargerSyntaxNode,
14727 window: &mut Window,
14728 cx: &mut Context<Self>,
14729 ) {
14730 let Some(visible_row_count) = self.visible_row_count() else {
14731 return;
14732 };
14733 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14734 if old_selections.is_empty() {
14735 return;
14736 }
14737
14738 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14739
14740 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14741 let buffer = self.buffer.read(cx).snapshot(cx);
14742
14743 let mut selected_larger_node = false;
14744 let mut new_selections = old_selections
14745 .iter()
14746 .map(|selection| {
14747 let old_range = selection.start..selection.end;
14748
14749 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14750 // manually select word at selection
14751 if ["string_content", "inline"].contains(&node.kind()) {
14752 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14753 // ignore if word is already selected
14754 if !word_range.is_empty() && old_range != word_range {
14755 let (last_word_range, _) =
14756 buffer.surrounding_word(old_range.end, false);
14757 // only select word if start and end point belongs to same word
14758 if word_range == last_word_range {
14759 selected_larger_node = true;
14760 return Selection {
14761 id: selection.id,
14762 start: word_range.start,
14763 end: word_range.end,
14764 goal: SelectionGoal::None,
14765 reversed: selection.reversed,
14766 };
14767 }
14768 }
14769 }
14770 }
14771
14772 let mut new_range = old_range.clone();
14773 while let Some((_node, containing_range)) =
14774 buffer.syntax_ancestor(new_range.clone())
14775 {
14776 new_range = match containing_range {
14777 MultiOrSingleBufferOffsetRange::Single(_) => break,
14778 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14779 };
14780 if !display_map.intersects_fold(new_range.start)
14781 && !display_map.intersects_fold(new_range.end)
14782 {
14783 break;
14784 }
14785 }
14786
14787 selected_larger_node |= new_range != old_range;
14788 Selection {
14789 id: selection.id,
14790 start: new_range.start,
14791 end: new_range.end,
14792 goal: SelectionGoal::None,
14793 reversed: selection.reversed,
14794 }
14795 })
14796 .collect::<Vec<_>>();
14797
14798 if !selected_larger_node {
14799 return; // don't put this call in the history
14800 }
14801
14802 // scroll based on transformation done to the last selection created by the user
14803 let (last_old, last_new) = old_selections
14804 .last()
14805 .zip(new_selections.last().cloned())
14806 .expect("old_selections isn't empty");
14807
14808 // revert selection
14809 let is_selection_reversed = {
14810 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14811 new_selections.last_mut().expect("checked above").reversed =
14812 should_newest_selection_be_reversed;
14813 should_newest_selection_be_reversed
14814 };
14815
14816 if selected_larger_node {
14817 self.select_syntax_node_history.disable_clearing = true;
14818 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14819 s.select(new_selections.clone());
14820 });
14821 self.select_syntax_node_history.disable_clearing = false;
14822 }
14823
14824 let start_row = last_new.start.to_display_point(&display_map).row().0;
14825 let end_row = last_new.end.to_display_point(&display_map).row().0;
14826 let selection_height = end_row - start_row + 1;
14827 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14828
14829 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14830 let scroll_behavior = if fits_on_the_screen {
14831 self.request_autoscroll(Autoscroll::fit(), cx);
14832 SelectSyntaxNodeScrollBehavior::FitSelection
14833 } else if is_selection_reversed {
14834 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14835 SelectSyntaxNodeScrollBehavior::CursorTop
14836 } else {
14837 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14838 SelectSyntaxNodeScrollBehavior::CursorBottom
14839 };
14840
14841 self.select_syntax_node_history.push((
14842 old_selections,
14843 scroll_behavior,
14844 is_selection_reversed,
14845 ));
14846 }
14847
14848 pub fn select_smaller_syntax_node(
14849 &mut self,
14850 _: &SelectSmallerSyntaxNode,
14851 window: &mut Window,
14852 cx: &mut Context<Self>,
14853 ) {
14854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14855
14856 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14857 self.select_syntax_node_history.pop()
14858 {
14859 if let Some(selection) = selections.last_mut() {
14860 selection.reversed = is_selection_reversed;
14861 }
14862
14863 self.select_syntax_node_history.disable_clearing = true;
14864 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14865 s.select(selections.to_vec());
14866 });
14867 self.select_syntax_node_history.disable_clearing = false;
14868
14869 match scroll_behavior {
14870 SelectSyntaxNodeScrollBehavior::CursorTop => {
14871 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14872 }
14873 SelectSyntaxNodeScrollBehavior::FitSelection => {
14874 self.request_autoscroll(Autoscroll::fit(), cx);
14875 }
14876 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14877 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14878 }
14879 }
14880 }
14881 }
14882
14883 pub fn unwrap_syntax_node(
14884 &mut self,
14885 _: &UnwrapSyntaxNode,
14886 window: &mut Window,
14887 cx: &mut Context<Self>,
14888 ) {
14889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14890
14891 let buffer = self.buffer.read(cx).snapshot(cx);
14892 let selections = self
14893 .selections
14894 .all::<usize>(cx)
14895 .into_iter()
14896 // subtracting the offset requires sorting
14897 .sorted_by_key(|i| i.start);
14898
14899 let full_edits = selections
14900 .into_iter()
14901 .filter_map(|selection| {
14902 // Only requires two branches once if-let-chains stabilize (#53667)
14903 let child = if !selection.is_empty() {
14904 selection.range()
14905 } else if let Some((_, ancestor_range)) =
14906 buffer.syntax_ancestor(selection.start..selection.end)
14907 {
14908 match ancestor_range {
14909 MultiOrSingleBufferOffsetRange::Single(range) => range,
14910 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14911 }
14912 } else {
14913 selection.range()
14914 };
14915
14916 let mut parent = child.clone();
14917 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14918 parent = match ancestor_range {
14919 MultiOrSingleBufferOffsetRange::Single(range) => range,
14920 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14921 };
14922 if parent.start < child.start || parent.end > child.end {
14923 break;
14924 }
14925 }
14926
14927 if parent == child {
14928 return None;
14929 }
14930 let text = buffer.text_for_range(child).collect::<String>();
14931 Some((selection.id, parent, text))
14932 })
14933 .collect::<Vec<_>>();
14934
14935 self.transact(window, cx, |this, window, cx| {
14936 this.buffer.update(cx, |buffer, cx| {
14937 buffer.edit(
14938 full_edits
14939 .iter()
14940 .map(|(_, p, t)| (p.clone(), t.clone()))
14941 .collect::<Vec<_>>(),
14942 None,
14943 cx,
14944 );
14945 });
14946 this.change_selections(Default::default(), window, cx, |s| {
14947 let mut offset = 0;
14948 let mut selections = vec![];
14949 for (id, parent, text) in full_edits {
14950 let start = parent.start - offset;
14951 offset += parent.len() - text.len();
14952 selections.push(Selection {
14953 id,
14954 start,
14955 end: start + text.len(),
14956 reversed: false,
14957 goal: Default::default(),
14958 });
14959 }
14960 s.select(selections);
14961 });
14962 });
14963 }
14964
14965 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14966 if !EditorSettings::get_global(cx).gutter.runnables {
14967 self.clear_tasks();
14968 return Task::ready(());
14969 }
14970 let project = self.project().map(Entity::downgrade);
14971 let task_sources = self.lsp_task_sources(cx);
14972 let multi_buffer = self.buffer.downgrade();
14973 cx.spawn_in(window, async move |editor, cx| {
14974 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14975 let Some(project) = project.and_then(|p| p.upgrade()) else {
14976 return;
14977 };
14978 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14979 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14980 }) else {
14981 return;
14982 };
14983
14984 let hide_runnables = project
14985 .update(cx, |project, _| project.is_via_collab())
14986 .unwrap_or(true);
14987 if hide_runnables {
14988 return;
14989 }
14990 let new_rows =
14991 cx.background_spawn({
14992 let snapshot = display_snapshot.clone();
14993 async move {
14994 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14995 }
14996 })
14997 .await;
14998 let Ok(lsp_tasks) =
14999 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15000 else {
15001 return;
15002 };
15003 let lsp_tasks = lsp_tasks.await;
15004
15005 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15006 lsp_tasks
15007 .into_iter()
15008 .flat_map(|(kind, tasks)| {
15009 tasks.into_iter().filter_map(move |(location, task)| {
15010 Some((kind.clone(), location?, task))
15011 })
15012 })
15013 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15014 let buffer = location.target.buffer;
15015 let buffer_snapshot = buffer.read(cx).snapshot();
15016 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15017 |(excerpt_id, snapshot, _)| {
15018 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15019 display_snapshot
15020 .buffer_snapshot
15021 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15022 } else {
15023 None
15024 }
15025 },
15026 );
15027 if let Some(offset) = offset {
15028 let task_buffer_range =
15029 location.target.range.to_point(&buffer_snapshot);
15030 let context_buffer_range =
15031 task_buffer_range.to_offset(&buffer_snapshot);
15032 let context_range = BufferOffset(context_buffer_range.start)
15033 ..BufferOffset(context_buffer_range.end);
15034
15035 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15036 .or_insert_with(|| RunnableTasks {
15037 templates: Vec::new(),
15038 offset,
15039 column: task_buffer_range.start.column,
15040 extra_variables: HashMap::default(),
15041 context_range,
15042 })
15043 .templates
15044 .push((kind, task.original_task().clone()));
15045 }
15046
15047 acc
15048 })
15049 }) else {
15050 return;
15051 };
15052
15053 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15054 buffer.language_settings(cx).tasks.prefer_lsp
15055 }) else {
15056 return;
15057 };
15058
15059 let rows = Self::runnable_rows(
15060 project,
15061 display_snapshot,
15062 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15063 new_rows,
15064 cx.clone(),
15065 )
15066 .await;
15067 editor
15068 .update(cx, |editor, _| {
15069 editor.clear_tasks();
15070 for (key, mut value) in rows {
15071 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15072 value.templates.extend(lsp_tasks.templates);
15073 }
15074
15075 editor.insert_tasks(key, value);
15076 }
15077 for (key, value) in lsp_tasks_by_rows {
15078 editor.insert_tasks(key, value);
15079 }
15080 })
15081 .ok();
15082 })
15083 }
15084 fn fetch_runnable_ranges(
15085 snapshot: &DisplaySnapshot,
15086 range: Range<Anchor>,
15087 ) -> Vec<language::RunnableRange> {
15088 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15089 }
15090
15091 fn runnable_rows(
15092 project: Entity<Project>,
15093 snapshot: DisplaySnapshot,
15094 prefer_lsp: bool,
15095 runnable_ranges: Vec<RunnableRange>,
15096 cx: AsyncWindowContext,
15097 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15098 cx.spawn(async move |cx| {
15099 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15100 for mut runnable in runnable_ranges {
15101 let Some(tasks) = cx
15102 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15103 .ok()
15104 else {
15105 continue;
15106 };
15107 let mut tasks = tasks.await;
15108
15109 if prefer_lsp {
15110 tasks.retain(|(task_kind, _)| {
15111 !matches!(task_kind, TaskSourceKind::Language { .. })
15112 });
15113 }
15114 if tasks.is_empty() {
15115 continue;
15116 }
15117
15118 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15119 let Some(row) = snapshot
15120 .buffer_snapshot
15121 .buffer_line_for_row(MultiBufferRow(point.row))
15122 .map(|(_, range)| range.start.row)
15123 else {
15124 continue;
15125 };
15126
15127 let context_range =
15128 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15129 runnable_rows.push((
15130 (runnable.buffer_id, row),
15131 RunnableTasks {
15132 templates: tasks,
15133 offset: snapshot
15134 .buffer_snapshot
15135 .anchor_before(runnable.run_range.start),
15136 context_range,
15137 column: point.column,
15138 extra_variables: runnable.extra_captures,
15139 },
15140 ));
15141 }
15142 runnable_rows
15143 })
15144 }
15145
15146 fn templates_with_tags(
15147 project: &Entity<Project>,
15148 runnable: &mut Runnable,
15149 cx: &mut App,
15150 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15151 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15152 let (worktree_id, file) = project
15153 .buffer_for_id(runnable.buffer, cx)
15154 .and_then(|buffer| buffer.read(cx).file())
15155 .map(|file| (file.worktree_id(cx), file.clone()))
15156 .unzip();
15157
15158 (
15159 project.task_store().read(cx).task_inventory().cloned(),
15160 worktree_id,
15161 file,
15162 )
15163 });
15164
15165 let tags = mem::take(&mut runnable.tags);
15166 let language = runnable.language.clone();
15167 cx.spawn(async move |cx| {
15168 let mut templates_with_tags = Vec::new();
15169 if let Some(inventory) = inventory {
15170 for RunnableTag(tag) in tags {
15171 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15172 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15173 }) else {
15174 return templates_with_tags;
15175 };
15176 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15177 move |(_, template)| {
15178 template.tags.iter().any(|source_tag| source_tag == &tag)
15179 },
15180 ));
15181 }
15182 }
15183 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15184
15185 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15186 // Strongest source wins; if we have worktree tag binding, prefer that to
15187 // global and language bindings;
15188 // if we have a global binding, prefer that to language binding.
15189 let first_mismatch = templates_with_tags
15190 .iter()
15191 .position(|(tag_source, _)| tag_source != leading_tag_source);
15192 if let Some(index) = first_mismatch {
15193 templates_with_tags.truncate(index);
15194 }
15195 }
15196
15197 templates_with_tags
15198 })
15199 }
15200
15201 pub fn move_to_enclosing_bracket(
15202 &mut self,
15203 _: &MoveToEnclosingBracket,
15204 window: &mut Window,
15205 cx: &mut Context<Self>,
15206 ) {
15207 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15208 self.change_selections(Default::default(), window, cx, |s| {
15209 s.move_offsets_with(|snapshot, selection| {
15210 let Some(enclosing_bracket_ranges) =
15211 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15212 else {
15213 return;
15214 };
15215
15216 let mut best_length = usize::MAX;
15217 let mut best_inside = false;
15218 let mut best_in_bracket_range = false;
15219 let mut best_destination = None;
15220 for (open, close) in enclosing_bracket_ranges {
15221 let close = close.to_inclusive();
15222 let length = close.end() - open.start;
15223 let inside = selection.start >= open.end && selection.end <= *close.start();
15224 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15225 || close.contains(&selection.head());
15226
15227 // If best is next to a bracket and current isn't, skip
15228 if !in_bracket_range && best_in_bracket_range {
15229 continue;
15230 }
15231
15232 // Prefer smaller lengths unless best is inside and current isn't
15233 if length > best_length && (best_inside || !inside) {
15234 continue;
15235 }
15236
15237 best_length = length;
15238 best_inside = inside;
15239 best_in_bracket_range = in_bracket_range;
15240 best_destination = Some(
15241 if close.contains(&selection.start) && close.contains(&selection.end) {
15242 if inside { open.end } else { open.start }
15243 } else if inside {
15244 *close.start()
15245 } else {
15246 *close.end()
15247 },
15248 );
15249 }
15250
15251 if let Some(destination) = best_destination {
15252 selection.collapse_to(destination, SelectionGoal::None);
15253 }
15254 })
15255 });
15256 }
15257
15258 pub fn undo_selection(
15259 &mut self,
15260 _: &UndoSelection,
15261 window: &mut Window,
15262 cx: &mut Context<Self>,
15263 ) {
15264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15265 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15266 self.selection_history.mode = SelectionHistoryMode::Undoing;
15267 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15268 this.end_selection(window, cx);
15269 this.change_selections(
15270 SelectionEffects::scroll(Autoscroll::newest()),
15271 window,
15272 cx,
15273 |s| s.select_anchors(entry.selections.to_vec()),
15274 );
15275 });
15276 self.selection_history.mode = SelectionHistoryMode::Normal;
15277
15278 self.select_next_state = entry.select_next_state;
15279 self.select_prev_state = entry.select_prev_state;
15280 self.add_selections_state = entry.add_selections_state;
15281 }
15282 }
15283
15284 pub fn redo_selection(
15285 &mut self,
15286 _: &RedoSelection,
15287 window: &mut Window,
15288 cx: &mut Context<Self>,
15289 ) {
15290 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15291 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15292 self.selection_history.mode = SelectionHistoryMode::Redoing;
15293 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15294 this.end_selection(window, cx);
15295 this.change_selections(
15296 SelectionEffects::scroll(Autoscroll::newest()),
15297 window,
15298 cx,
15299 |s| s.select_anchors(entry.selections.to_vec()),
15300 );
15301 });
15302 self.selection_history.mode = SelectionHistoryMode::Normal;
15303
15304 self.select_next_state = entry.select_next_state;
15305 self.select_prev_state = entry.select_prev_state;
15306 self.add_selections_state = entry.add_selections_state;
15307 }
15308 }
15309
15310 pub fn expand_excerpts(
15311 &mut self,
15312 action: &ExpandExcerpts,
15313 _: &mut Window,
15314 cx: &mut Context<Self>,
15315 ) {
15316 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15317 }
15318
15319 pub fn expand_excerpts_down(
15320 &mut self,
15321 action: &ExpandExcerptsDown,
15322 _: &mut Window,
15323 cx: &mut Context<Self>,
15324 ) {
15325 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15326 }
15327
15328 pub fn expand_excerpts_up(
15329 &mut self,
15330 action: &ExpandExcerptsUp,
15331 _: &mut Window,
15332 cx: &mut Context<Self>,
15333 ) {
15334 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15335 }
15336
15337 pub fn expand_excerpts_for_direction(
15338 &mut self,
15339 lines: u32,
15340 direction: ExpandExcerptDirection,
15341
15342 cx: &mut Context<Self>,
15343 ) {
15344 let selections = self.selections.disjoint_anchors();
15345
15346 let lines = if lines == 0 {
15347 EditorSettings::get_global(cx).expand_excerpt_lines
15348 } else {
15349 lines
15350 };
15351
15352 self.buffer.update(cx, |buffer, cx| {
15353 let snapshot = buffer.snapshot(cx);
15354 let mut excerpt_ids = selections
15355 .iter()
15356 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15357 .collect::<Vec<_>>();
15358 excerpt_ids.sort();
15359 excerpt_ids.dedup();
15360 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15361 })
15362 }
15363
15364 pub fn expand_excerpt(
15365 &mut self,
15366 excerpt: ExcerptId,
15367 direction: ExpandExcerptDirection,
15368 window: &mut Window,
15369 cx: &mut Context<Self>,
15370 ) {
15371 let current_scroll_position = self.scroll_position(cx);
15372 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15373 let mut should_scroll_up = false;
15374
15375 if direction == ExpandExcerptDirection::Down {
15376 let multi_buffer = self.buffer.read(cx);
15377 let snapshot = multi_buffer.snapshot(cx);
15378 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15379 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15380 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15381 {
15382 let buffer_snapshot = buffer.read(cx).snapshot();
15383 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15384 let last_row = buffer_snapshot.max_point().row;
15385 let lines_below = last_row.saturating_sub(excerpt_end_row);
15386 should_scroll_up = lines_below >= lines_to_expand;
15387 }
15388 }
15389
15390 self.buffer.update(cx, |buffer, cx| {
15391 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15392 });
15393
15394 if should_scroll_up {
15395 let new_scroll_position =
15396 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15397 self.set_scroll_position(new_scroll_position, window, cx);
15398 }
15399 }
15400
15401 pub fn go_to_singleton_buffer_point(
15402 &mut self,
15403 point: Point,
15404 window: &mut Window,
15405 cx: &mut Context<Self>,
15406 ) {
15407 self.go_to_singleton_buffer_range(point..point, window, cx);
15408 }
15409
15410 pub fn go_to_singleton_buffer_range(
15411 &mut self,
15412 range: Range<Point>,
15413 window: &mut Window,
15414 cx: &mut Context<Self>,
15415 ) {
15416 let multibuffer = self.buffer().read(cx);
15417 let Some(buffer) = multibuffer.as_singleton() else {
15418 return;
15419 };
15420 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15421 return;
15422 };
15423 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15424 return;
15425 };
15426 self.change_selections(
15427 SelectionEffects::default().nav_history(true),
15428 window,
15429 cx,
15430 |s| s.select_anchor_ranges([start..end]),
15431 );
15432 }
15433
15434 pub fn go_to_diagnostic(
15435 &mut self,
15436 action: &GoToDiagnostic,
15437 window: &mut Window,
15438 cx: &mut Context<Self>,
15439 ) {
15440 if !self.diagnostics_enabled() {
15441 return;
15442 }
15443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15444 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15445 }
15446
15447 pub fn go_to_prev_diagnostic(
15448 &mut self,
15449 action: &GoToPreviousDiagnostic,
15450 window: &mut Window,
15451 cx: &mut Context<Self>,
15452 ) {
15453 if !self.diagnostics_enabled() {
15454 return;
15455 }
15456 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15457 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15458 }
15459
15460 pub fn go_to_diagnostic_impl(
15461 &mut self,
15462 direction: Direction,
15463 severity: GoToDiagnosticSeverityFilter,
15464 window: &mut Window,
15465 cx: &mut Context<Self>,
15466 ) {
15467 let buffer = self.buffer.read(cx).snapshot(cx);
15468 let selection = self.selections.newest::<usize>(cx);
15469
15470 let mut active_group_id = None;
15471 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15472 && active_group.active_range.start.to_offset(&buffer) == selection.start
15473 {
15474 active_group_id = Some(active_group.group_id);
15475 }
15476
15477 fn filtered(
15478 snapshot: EditorSnapshot,
15479 severity: GoToDiagnosticSeverityFilter,
15480 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15481 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15482 diagnostics
15483 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15484 .filter(|entry| entry.range.start != entry.range.end)
15485 .filter(|entry| !entry.diagnostic.is_unnecessary)
15486 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15487 }
15488
15489 let snapshot = self.snapshot(window, cx);
15490 let before = filtered(
15491 snapshot.clone(),
15492 severity,
15493 buffer
15494 .diagnostics_in_range(0..selection.start)
15495 .filter(|entry| entry.range.start <= selection.start),
15496 );
15497 let after = filtered(
15498 snapshot,
15499 severity,
15500 buffer
15501 .diagnostics_in_range(selection.start..buffer.len())
15502 .filter(|entry| entry.range.start >= selection.start),
15503 );
15504
15505 let mut found: Option<DiagnosticEntry<usize>> = None;
15506 if direction == Direction::Prev {
15507 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15508 {
15509 for diagnostic in prev_diagnostics.into_iter().rev() {
15510 if diagnostic.range.start != selection.start
15511 || active_group_id
15512 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15513 {
15514 found = Some(diagnostic);
15515 break 'outer;
15516 }
15517 }
15518 }
15519 } else {
15520 for diagnostic in after.chain(before) {
15521 if diagnostic.range.start != selection.start
15522 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15523 {
15524 found = Some(diagnostic);
15525 break;
15526 }
15527 }
15528 }
15529 let Some(next_diagnostic) = found else {
15530 return;
15531 };
15532
15533 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15534 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15535 return;
15536 };
15537 self.change_selections(Default::default(), window, cx, |s| {
15538 s.select_ranges(vec![
15539 next_diagnostic.range.start..next_diagnostic.range.start,
15540 ])
15541 });
15542 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15543 self.refresh_edit_prediction(false, true, window, cx);
15544 }
15545
15546 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15548 let snapshot = self.snapshot(window, cx);
15549 let selection = self.selections.newest::<Point>(cx);
15550 self.go_to_hunk_before_or_after_position(
15551 &snapshot,
15552 selection.head(),
15553 Direction::Next,
15554 window,
15555 cx,
15556 );
15557 }
15558
15559 pub fn go_to_hunk_before_or_after_position(
15560 &mut self,
15561 snapshot: &EditorSnapshot,
15562 position: Point,
15563 direction: Direction,
15564 window: &mut Window,
15565 cx: &mut Context<Editor>,
15566 ) {
15567 let row = if direction == Direction::Next {
15568 self.hunk_after_position(snapshot, position)
15569 .map(|hunk| hunk.row_range.start)
15570 } else {
15571 self.hunk_before_position(snapshot, position)
15572 };
15573
15574 if let Some(row) = row {
15575 let destination = Point::new(row.0, 0);
15576 let autoscroll = Autoscroll::center();
15577
15578 self.unfold_ranges(&[destination..destination], false, false, cx);
15579 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15580 s.select_ranges([destination..destination]);
15581 });
15582 }
15583 }
15584
15585 fn hunk_after_position(
15586 &mut self,
15587 snapshot: &EditorSnapshot,
15588 position: Point,
15589 ) -> Option<MultiBufferDiffHunk> {
15590 snapshot
15591 .buffer_snapshot
15592 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15593 .find(|hunk| hunk.row_range.start.0 > position.row)
15594 .or_else(|| {
15595 snapshot
15596 .buffer_snapshot
15597 .diff_hunks_in_range(Point::zero()..position)
15598 .find(|hunk| hunk.row_range.end.0 < position.row)
15599 })
15600 }
15601
15602 fn go_to_prev_hunk(
15603 &mut self,
15604 _: &GoToPreviousHunk,
15605 window: &mut Window,
15606 cx: &mut Context<Self>,
15607 ) {
15608 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15609 let snapshot = self.snapshot(window, cx);
15610 let selection = self.selections.newest::<Point>(cx);
15611 self.go_to_hunk_before_or_after_position(
15612 &snapshot,
15613 selection.head(),
15614 Direction::Prev,
15615 window,
15616 cx,
15617 );
15618 }
15619
15620 fn hunk_before_position(
15621 &mut self,
15622 snapshot: &EditorSnapshot,
15623 position: Point,
15624 ) -> Option<MultiBufferRow> {
15625 snapshot
15626 .buffer_snapshot
15627 .diff_hunk_before(position)
15628 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15629 }
15630
15631 fn go_to_next_change(
15632 &mut self,
15633 _: &GoToNextChange,
15634 window: &mut Window,
15635 cx: &mut Context<Self>,
15636 ) {
15637 if let Some(selections) = self
15638 .change_list
15639 .next_change(1, Direction::Next)
15640 .map(|s| s.to_vec())
15641 {
15642 self.change_selections(Default::default(), window, cx, |s| {
15643 let map = s.display_map();
15644 s.select_display_ranges(selections.iter().map(|a| {
15645 let point = a.to_display_point(&map);
15646 point..point
15647 }))
15648 })
15649 }
15650 }
15651
15652 fn go_to_previous_change(
15653 &mut self,
15654 _: &GoToPreviousChange,
15655 window: &mut Window,
15656 cx: &mut Context<Self>,
15657 ) {
15658 if let Some(selections) = self
15659 .change_list
15660 .next_change(1, Direction::Prev)
15661 .map(|s| s.to_vec())
15662 {
15663 self.change_selections(Default::default(), window, cx, |s| {
15664 let map = s.display_map();
15665 s.select_display_ranges(selections.iter().map(|a| {
15666 let point = a.to_display_point(&map);
15667 point..point
15668 }))
15669 })
15670 }
15671 }
15672
15673 fn go_to_line<T: 'static>(
15674 &mut self,
15675 position: Anchor,
15676 highlight_color: Option<Hsla>,
15677 window: &mut Window,
15678 cx: &mut Context<Self>,
15679 ) {
15680 let snapshot = self.snapshot(window, cx).display_snapshot;
15681 let position = position.to_point(&snapshot.buffer_snapshot);
15682 let start = snapshot
15683 .buffer_snapshot
15684 .clip_point(Point::new(position.row, 0), Bias::Left);
15685 let end = start + Point::new(1, 0);
15686 let start = snapshot.buffer_snapshot.anchor_before(start);
15687 let end = snapshot.buffer_snapshot.anchor_before(end);
15688
15689 self.highlight_rows::<T>(
15690 start..end,
15691 highlight_color
15692 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15693 Default::default(),
15694 cx,
15695 );
15696
15697 if self.buffer.read(cx).is_singleton() {
15698 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15699 }
15700 }
15701
15702 pub fn go_to_definition(
15703 &mut self,
15704 _: &GoToDefinition,
15705 window: &mut Window,
15706 cx: &mut Context<Self>,
15707 ) -> Task<Result<Navigated>> {
15708 let definition =
15709 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15710 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15711 cx.spawn_in(window, async move |editor, cx| {
15712 if definition.await? == Navigated::Yes {
15713 return Ok(Navigated::Yes);
15714 }
15715 match fallback_strategy {
15716 GoToDefinitionFallback::None => Ok(Navigated::No),
15717 GoToDefinitionFallback::FindAllReferences => {
15718 match editor.update_in(cx, |editor, window, cx| {
15719 editor.find_all_references(&FindAllReferences, window, cx)
15720 })? {
15721 Some(references) => references.await,
15722 None => Ok(Navigated::No),
15723 }
15724 }
15725 }
15726 })
15727 }
15728
15729 pub fn go_to_declaration(
15730 &mut self,
15731 _: &GoToDeclaration,
15732 window: &mut Window,
15733 cx: &mut Context<Self>,
15734 ) -> Task<Result<Navigated>> {
15735 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15736 }
15737
15738 pub fn go_to_declaration_split(
15739 &mut self,
15740 _: &GoToDeclaration,
15741 window: &mut Window,
15742 cx: &mut Context<Self>,
15743 ) -> Task<Result<Navigated>> {
15744 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15745 }
15746
15747 pub fn go_to_implementation(
15748 &mut self,
15749 _: &GoToImplementation,
15750 window: &mut Window,
15751 cx: &mut Context<Self>,
15752 ) -> Task<Result<Navigated>> {
15753 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15754 }
15755
15756 pub fn go_to_implementation_split(
15757 &mut self,
15758 _: &GoToImplementationSplit,
15759 window: &mut Window,
15760 cx: &mut Context<Self>,
15761 ) -> Task<Result<Navigated>> {
15762 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15763 }
15764
15765 pub fn go_to_type_definition(
15766 &mut self,
15767 _: &GoToTypeDefinition,
15768 window: &mut Window,
15769 cx: &mut Context<Self>,
15770 ) -> Task<Result<Navigated>> {
15771 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15772 }
15773
15774 pub fn go_to_definition_split(
15775 &mut self,
15776 _: &GoToDefinitionSplit,
15777 window: &mut Window,
15778 cx: &mut Context<Self>,
15779 ) -> Task<Result<Navigated>> {
15780 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15781 }
15782
15783 pub fn go_to_type_definition_split(
15784 &mut self,
15785 _: &GoToTypeDefinitionSplit,
15786 window: &mut Window,
15787 cx: &mut Context<Self>,
15788 ) -> Task<Result<Navigated>> {
15789 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15790 }
15791
15792 fn go_to_definition_of_kind(
15793 &mut self,
15794 kind: GotoDefinitionKind,
15795 split: bool,
15796 window: &mut Window,
15797 cx: &mut Context<Self>,
15798 ) -> Task<Result<Navigated>> {
15799 let Some(provider) = self.semantics_provider.clone() else {
15800 return Task::ready(Ok(Navigated::No));
15801 };
15802 let head = self.selections.newest::<usize>(cx).head();
15803 let buffer = self.buffer.read(cx);
15804 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15805 return Task::ready(Ok(Navigated::No));
15806 };
15807 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15808 return Task::ready(Ok(Navigated::No));
15809 };
15810
15811 cx.spawn_in(window, async move |editor, cx| {
15812 let Some(definitions) = definitions.await? else {
15813 return Ok(Navigated::No);
15814 };
15815 let navigated = editor
15816 .update_in(cx, |editor, window, cx| {
15817 editor.navigate_to_hover_links(
15818 Some(kind),
15819 definitions
15820 .into_iter()
15821 .filter(|location| {
15822 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15823 })
15824 .map(HoverLink::Text)
15825 .collect::<Vec<_>>(),
15826 split,
15827 window,
15828 cx,
15829 )
15830 })?
15831 .await?;
15832 anyhow::Ok(navigated)
15833 })
15834 }
15835
15836 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15837 let selection = self.selections.newest_anchor();
15838 let head = selection.head();
15839 let tail = selection.tail();
15840
15841 let Some((buffer, start_position)) =
15842 self.buffer.read(cx).text_anchor_for_position(head, cx)
15843 else {
15844 return;
15845 };
15846
15847 let end_position = if head != tail {
15848 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15849 return;
15850 };
15851 Some(pos)
15852 } else {
15853 None
15854 };
15855
15856 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15857 let url = if let Some(end_pos) = end_position {
15858 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15859 } else {
15860 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15861 };
15862
15863 if let Some(url) = url {
15864 editor.update(cx, |_, cx| {
15865 cx.open_url(&url);
15866 })
15867 } else {
15868 Ok(())
15869 }
15870 });
15871
15872 url_finder.detach();
15873 }
15874
15875 pub fn open_selected_filename(
15876 &mut self,
15877 _: &OpenSelectedFilename,
15878 window: &mut Window,
15879 cx: &mut Context<Self>,
15880 ) {
15881 let Some(workspace) = self.workspace() else {
15882 return;
15883 };
15884
15885 let position = self.selections.newest_anchor().head();
15886
15887 let Some((buffer, buffer_position)) =
15888 self.buffer.read(cx).text_anchor_for_position(position, cx)
15889 else {
15890 return;
15891 };
15892
15893 let project = self.project.clone();
15894
15895 cx.spawn_in(window, async move |_, cx| {
15896 let result = find_file(&buffer, project, buffer_position, cx).await;
15897
15898 if let Some((_, path)) = result {
15899 workspace
15900 .update_in(cx, |workspace, window, cx| {
15901 workspace.open_resolved_path(path, window, cx)
15902 })?
15903 .await?;
15904 }
15905 anyhow::Ok(())
15906 })
15907 .detach();
15908 }
15909
15910 pub(crate) fn navigate_to_hover_links(
15911 &mut self,
15912 kind: Option<GotoDefinitionKind>,
15913 definitions: Vec<HoverLink>,
15914 split: bool,
15915 window: &mut Window,
15916 cx: &mut Context<Editor>,
15917 ) -> Task<Result<Navigated>> {
15918 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15919 let mut first_url_or_file = None;
15920 let definitions: Vec<_> = definitions
15921 .into_iter()
15922 .filter_map(|def| match def {
15923 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15924 HoverLink::InlayHint(lsp_location, server_id) => {
15925 let computation =
15926 self.compute_target_location(lsp_location, server_id, window, cx);
15927 Some(cx.background_spawn(computation))
15928 }
15929 HoverLink::Url(url) => {
15930 first_url_or_file = Some(Either::Left(url));
15931 None
15932 }
15933 HoverLink::File(path) => {
15934 first_url_or_file = Some(Either::Right(path));
15935 None
15936 }
15937 })
15938 .collect();
15939
15940 let workspace = self.workspace();
15941
15942 cx.spawn_in(window, async move |editor, acx| {
15943 let mut locations: Vec<Location> = future::join_all(definitions)
15944 .await
15945 .into_iter()
15946 .filter_map(|location| location.transpose())
15947 .collect::<Result<_>>()
15948 .context("location tasks")?;
15949
15950 if locations.len() > 1 {
15951 let Some(workspace) = workspace else {
15952 return Ok(Navigated::No);
15953 };
15954
15955 let tab_kind = match kind {
15956 Some(GotoDefinitionKind::Implementation) => "Implementations",
15957 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15958 Some(GotoDefinitionKind::Declaration) => "Declarations",
15959 Some(GotoDefinitionKind::Type) => "Types",
15960 };
15961 let title = editor
15962 .update_in(acx, |_, _, cx| {
15963 let target = locations
15964 .iter()
15965 .map(|location| {
15966 location
15967 .buffer
15968 .read(cx)
15969 .text_for_range(location.range.clone())
15970 .collect::<String>()
15971 })
15972 .filter(|text| !text.contains('\n'))
15973 .unique()
15974 .take(3)
15975 .join(", ");
15976 if target.is_empty() {
15977 tab_kind.to_owned()
15978 } else {
15979 format!("{tab_kind} for {target}")
15980 }
15981 })
15982 .context("buffer title")?;
15983
15984 let opened = workspace
15985 .update_in(acx, |workspace, window, cx| {
15986 Self::open_locations_in_multibuffer(
15987 workspace,
15988 locations,
15989 title,
15990 split,
15991 MultibufferSelectionMode::First,
15992 window,
15993 cx,
15994 )
15995 })
15996 .is_ok();
15997
15998 anyhow::Ok(Navigated::from_bool(opened))
15999 } else if locations.is_empty() {
16000 // If there is one definition, just open it directly
16001 match first_url_or_file {
16002 Some(Either::Left(url)) => {
16003 acx.update(|_, cx| cx.open_url(&url))?;
16004 Ok(Navigated::Yes)
16005 }
16006 Some(Either::Right(path)) => {
16007 let Some(workspace) = workspace else {
16008 return Ok(Navigated::No);
16009 };
16010
16011 workspace
16012 .update_in(acx, |workspace, window, cx| {
16013 workspace.open_resolved_path(path, window, cx)
16014 })?
16015 .await?;
16016 Ok(Navigated::Yes)
16017 }
16018 None => Ok(Navigated::No),
16019 }
16020 } else {
16021 let Some(workspace) = workspace else {
16022 return Ok(Navigated::No);
16023 };
16024
16025 let target = locations.pop().unwrap();
16026 editor.update_in(acx, |editor, window, cx| {
16027 let pane = workspace.read(cx).active_pane().clone();
16028
16029 let range = target.range.to_point(target.buffer.read(cx));
16030 let range = editor.range_for_match(&range);
16031 let range = collapse_multiline_range(range);
16032
16033 if !split
16034 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16035 {
16036 editor.go_to_singleton_buffer_range(range, window, cx);
16037 } else {
16038 window.defer(cx, move |window, cx| {
16039 let target_editor: Entity<Self> =
16040 workspace.update(cx, |workspace, cx| {
16041 let pane = if split {
16042 workspace.adjacent_pane(window, cx)
16043 } else {
16044 workspace.active_pane().clone()
16045 };
16046
16047 workspace.open_project_item(
16048 pane,
16049 target.buffer.clone(),
16050 true,
16051 true,
16052 window,
16053 cx,
16054 )
16055 });
16056 target_editor.update(cx, |target_editor, cx| {
16057 // When selecting a definition in a different buffer, disable the nav history
16058 // to avoid creating a history entry at the previous cursor location.
16059 pane.update(cx, |pane, _| pane.disable_history());
16060 target_editor.go_to_singleton_buffer_range(range, window, cx);
16061 pane.update(cx, |pane, _| pane.enable_history());
16062 });
16063 });
16064 }
16065 Navigated::Yes
16066 })
16067 }
16068 })
16069 }
16070
16071 fn compute_target_location(
16072 &self,
16073 lsp_location: lsp::Location,
16074 server_id: LanguageServerId,
16075 window: &mut Window,
16076 cx: &mut Context<Self>,
16077 ) -> Task<anyhow::Result<Option<Location>>> {
16078 let Some(project) = self.project.clone() else {
16079 return Task::ready(Ok(None));
16080 };
16081
16082 cx.spawn_in(window, async move |editor, cx| {
16083 let location_task = editor.update(cx, |_, cx| {
16084 project.update(cx, |project, cx| {
16085 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16086 })
16087 })?;
16088 let location = Some({
16089 let target_buffer_handle = location_task.await.context("open local buffer")?;
16090 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16091 let target_start = target_buffer
16092 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16093 let target_end = target_buffer
16094 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16095 target_buffer.anchor_after(target_start)
16096 ..target_buffer.anchor_before(target_end)
16097 })?;
16098 Location {
16099 buffer: target_buffer_handle,
16100 range,
16101 }
16102 });
16103 Ok(location)
16104 })
16105 }
16106
16107 pub fn find_all_references(
16108 &mut self,
16109 _: &FindAllReferences,
16110 window: &mut Window,
16111 cx: &mut Context<Self>,
16112 ) -> Option<Task<Result<Navigated>>> {
16113 let selection = self.selections.newest::<usize>(cx);
16114 let multi_buffer = self.buffer.read(cx);
16115 let head = selection.head();
16116
16117 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16118 let head_anchor = multi_buffer_snapshot.anchor_at(
16119 head,
16120 if head < selection.tail() {
16121 Bias::Right
16122 } else {
16123 Bias::Left
16124 },
16125 );
16126
16127 match self
16128 .find_all_references_task_sources
16129 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16130 {
16131 Ok(_) => {
16132 log::info!(
16133 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16134 );
16135 return None;
16136 }
16137 Err(i) => {
16138 self.find_all_references_task_sources.insert(i, head_anchor);
16139 }
16140 }
16141
16142 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16143 let workspace = self.workspace()?;
16144 let project = workspace.read(cx).project().clone();
16145 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16146 Some(cx.spawn_in(window, async move |editor, cx| {
16147 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16148 if let Ok(i) = editor
16149 .find_all_references_task_sources
16150 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16151 {
16152 editor.find_all_references_task_sources.remove(i);
16153 }
16154 });
16155
16156 let Some(locations) = references.await? else {
16157 return anyhow::Ok(Navigated::No);
16158 };
16159 if locations.is_empty() {
16160 return anyhow::Ok(Navigated::No);
16161 }
16162
16163 workspace.update_in(cx, |workspace, window, cx| {
16164 let target = locations
16165 .iter()
16166 .map(|location| {
16167 location
16168 .buffer
16169 .read(cx)
16170 .text_for_range(location.range.clone())
16171 .collect::<String>()
16172 })
16173 .filter(|text| !text.contains('\n'))
16174 .unique()
16175 .take(3)
16176 .join(", ");
16177 let title = if target.is_empty() {
16178 "References".to_owned()
16179 } else {
16180 format!("References to {target}")
16181 };
16182 Self::open_locations_in_multibuffer(
16183 workspace,
16184 locations,
16185 title,
16186 false,
16187 MultibufferSelectionMode::First,
16188 window,
16189 cx,
16190 );
16191 Navigated::Yes
16192 })
16193 }))
16194 }
16195
16196 /// Opens a multibuffer with the given project locations in it
16197 pub fn open_locations_in_multibuffer(
16198 workspace: &mut Workspace,
16199 mut locations: Vec<Location>,
16200 title: String,
16201 split: bool,
16202 multibuffer_selection_mode: MultibufferSelectionMode,
16203 window: &mut Window,
16204 cx: &mut Context<Workspace>,
16205 ) {
16206 if locations.is_empty() {
16207 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16208 return;
16209 }
16210
16211 // If there are multiple definitions, open them in a multibuffer
16212 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16213 let mut locations = locations.into_iter().peekable();
16214 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16215 let capability = workspace.project().read(cx).capability();
16216
16217 let excerpt_buffer = cx.new(|cx| {
16218 let mut multibuffer = MultiBuffer::new(capability);
16219 while let Some(location) = locations.next() {
16220 let buffer = location.buffer.read(cx);
16221 let mut ranges_for_buffer = Vec::new();
16222 let range = location.range.to_point(buffer);
16223 ranges_for_buffer.push(range.clone());
16224
16225 while let Some(next_location) = locations.peek() {
16226 if next_location.buffer == location.buffer {
16227 ranges_for_buffer.push(next_location.range.to_point(buffer));
16228 locations.next();
16229 } else {
16230 break;
16231 }
16232 }
16233
16234 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16235 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16236 PathKey::for_buffer(&location.buffer, cx),
16237 location.buffer.clone(),
16238 ranges_for_buffer,
16239 multibuffer_context_lines(cx),
16240 cx,
16241 );
16242 ranges.extend(new_ranges)
16243 }
16244
16245 multibuffer.with_title(title)
16246 });
16247
16248 let editor = cx.new(|cx| {
16249 Editor::for_multibuffer(
16250 excerpt_buffer,
16251 Some(workspace.project().clone()),
16252 window,
16253 cx,
16254 )
16255 });
16256 editor.update(cx, |editor, cx| {
16257 match multibuffer_selection_mode {
16258 MultibufferSelectionMode::First => {
16259 if let Some(first_range) = ranges.first() {
16260 editor.change_selections(
16261 SelectionEffects::no_scroll(),
16262 window,
16263 cx,
16264 |selections| {
16265 selections.clear_disjoint();
16266 selections
16267 .select_anchor_ranges(std::iter::once(first_range.clone()));
16268 },
16269 );
16270 }
16271 editor.highlight_background::<Self>(
16272 &ranges,
16273 |theme| theme.colors().editor_highlighted_line_background,
16274 cx,
16275 );
16276 }
16277 MultibufferSelectionMode::All => {
16278 editor.change_selections(
16279 SelectionEffects::no_scroll(),
16280 window,
16281 cx,
16282 |selections| {
16283 selections.clear_disjoint();
16284 selections.select_anchor_ranges(ranges);
16285 },
16286 );
16287 }
16288 }
16289 editor.register_buffers_with_language_servers(cx);
16290 });
16291
16292 let item = Box::new(editor);
16293 let item_id = item.item_id();
16294
16295 if split {
16296 workspace.split_item(SplitDirection::Right, item, window, cx);
16297 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16298 let (preview_item_id, preview_item_idx) =
16299 workspace.active_pane().read_with(cx, |pane, _| {
16300 (pane.preview_item_id(), pane.preview_item_idx())
16301 });
16302
16303 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16304
16305 if let Some(preview_item_id) = preview_item_id {
16306 workspace.active_pane().update(cx, |pane, cx| {
16307 pane.remove_item(preview_item_id, false, false, window, cx);
16308 });
16309 }
16310 } else {
16311 workspace.add_item_to_active_pane(item, None, true, window, cx);
16312 }
16313 workspace.active_pane().update(cx, |pane, cx| {
16314 pane.set_preview_item_id(Some(item_id), cx);
16315 });
16316 }
16317
16318 pub fn rename(
16319 &mut self,
16320 _: &Rename,
16321 window: &mut Window,
16322 cx: &mut Context<Self>,
16323 ) -> Option<Task<Result<()>>> {
16324 use language::ToOffset as _;
16325
16326 let provider = self.semantics_provider.clone()?;
16327 let selection = self.selections.newest_anchor().clone();
16328 let (cursor_buffer, cursor_buffer_position) = self
16329 .buffer
16330 .read(cx)
16331 .text_anchor_for_position(selection.head(), cx)?;
16332 let (tail_buffer, cursor_buffer_position_end) = self
16333 .buffer
16334 .read(cx)
16335 .text_anchor_for_position(selection.tail(), cx)?;
16336 if tail_buffer != cursor_buffer {
16337 return None;
16338 }
16339
16340 let snapshot = cursor_buffer.read(cx).snapshot();
16341 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16342 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16343 let prepare_rename = provider
16344 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16345 .unwrap_or_else(|| Task::ready(Ok(None)));
16346 drop(snapshot);
16347
16348 Some(cx.spawn_in(window, async move |this, cx| {
16349 let rename_range = if let Some(range) = prepare_rename.await? {
16350 Some(range)
16351 } else {
16352 this.update(cx, |this, cx| {
16353 let buffer = this.buffer.read(cx).snapshot(cx);
16354 let mut buffer_highlights = this
16355 .document_highlights_for_position(selection.head(), &buffer)
16356 .filter(|highlight| {
16357 highlight.start.excerpt_id == selection.head().excerpt_id
16358 && highlight.end.excerpt_id == selection.head().excerpt_id
16359 });
16360 buffer_highlights
16361 .next()
16362 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16363 })?
16364 };
16365 if let Some(rename_range) = rename_range {
16366 this.update_in(cx, |this, window, cx| {
16367 let snapshot = cursor_buffer.read(cx).snapshot();
16368 let rename_buffer_range = rename_range.to_offset(&snapshot);
16369 let cursor_offset_in_rename_range =
16370 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16371 let cursor_offset_in_rename_range_end =
16372 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16373
16374 this.take_rename(false, window, cx);
16375 let buffer = this.buffer.read(cx).read(cx);
16376 let cursor_offset = selection.head().to_offset(&buffer);
16377 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16378 let rename_end = rename_start + rename_buffer_range.len();
16379 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16380 let mut old_highlight_id = None;
16381 let old_name: Arc<str> = buffer
16382 .chunks(rename_start..rename_end, true)
16383 .map(|chunk| {
16384 if old_highlight_id.is_none() {
16385 old_highlight_id = chunk.syntax_highlight_id;
16386 }
16387 chunk.text
16388 })
16389 .collect::<String>()
16390 .into();
16391
16392 drop(buffer);
16393
16394 // Position the selection in the rename editor so that it matches the current selection.
16395 this.show_local_selections = false;
16396 let rename_editor = cx.new(|cx| {
16397 let mut editor = Editor::single_line(window, cx);
16398 editor.buffer.update(cx, |buffer, cx| {
16399 buffer.edit([(0..0, old_name.clone())], None, cx)
16400 });
16401 let rename_selection_range = match cursor_offset_in_rename_range
16402 .cmp(&cursor_offset_in_rename_range_end)
16403 {
16404 Ordering::Equal => {
16405 editor.select_all(&SelectAll, window, cx);
16406 return editor;
16407 }
16408 Ordering::Less => {
16409 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16410 }
16411 Ordering::Greater => {
16412 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16413 }
16414 };
16415 if rename_selection_range.end > old_name.len() {
16416 editor.select_all(&SelectAll, window, cx);
16417 } else {
16418 editor.change_selections(Default::default(), window, cx, |s| {
16419 s.select_ranges([rename_selection_range]);
16420 });
16421 }
16422 editor
16423 });
16424 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16425 if e == &EditorEvent::Focused {
16426 cx.emit(EditorEvent::FocusedIn)
16427 }
16428 })
16429 .detach();
16430
16431 let write_highlights =
16432 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16433 let read_highlights =
16434 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16435 let ranges = write_highlights
16436 .iter()
16437 .flat_map(|(_, ranges)| ranges.iter())
16438 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16439 .cloned()
16440 .collect();
16441
16442 this.highlight_text::<Rename>(
16443 ranges,
16444 HighlightStyle {
16445 fade_out: Some(0.6),
16446 ..Default::default()
16447 },
16448 cx,
16449 );
16450 let rename_focus_handle = rename_editor.focus_handle(cx);
16451 window.focus(&rename_focus_handle);
16452 let block_id = this.insert_blocks(
16453 [BlockProperties {
16454 style: BlockStyle::Flex,
16455 placement: BlockPlacement::Below(range.start),
16456 height: Some(1),
16457 render: Arc::new({
16458 let rename_editor = rename_editor.clone();
16459 move |cx: &mut BlockContext| {
16460 let mut text_style = cx.editor_style.text.clone();
16461 if let Some(highlight_style) = old_highlight_id
16462 .and_then(|h| h.style(&cx.editor_style.syntax))
16463 {
16464 text_style = text_style.highlight(highlight_style);
16465 }
16466 div()
16467 .block_mouse_except_scroll()
16468 .pl(cx.anchor_x)
16469 .child(EditorElement::new(
16470 &rename_editor,
16471 EditorStyle {
16472 background: cx.theme().system().transparent,
16473 local_player: cx.editor_style.local_player,
16474 text: text_style,
16475 scrollbar_width: cx.editor_style.scrollbar_width,
16476 syntax: cx.editor_style.syntax.clone(),
16477 status: cx.editor_style.status.clone(),
16478 inlay_hints_style: HighlightStyle {
16479 font_weight: Some(FontWeight::BOLD),
16480 ..make_inlay_hints_style(cx.app)
16481 },
16482 edit_prediction_styles: make_suggestion_styles(
16483 cx.app,
16484 ),
16485 ..EditorStyle::default()
16486 },
16487 ))
16488 .into_any_element()
16489 }
16490 }),
16491 priority: 0,
16492 }],
16493 Some(Autoscroll::fit()),
16494 cx,
16495 )[0];
16496 this.pending_rename = Some(RenameState {
16497 range,
16498 old_name,
16499 editor: rename_editor,
16500 block_id,
16501 });
16502 })?;
16503 }
16504
16505 Ok(())
16506 }))
16507 }
16508
16509 pub fn confirm_rename(
16510 &mut self,
16511 _: &ConfirmRename,
16512 window: &mut Window,
16513 cx: &mut Context<Self>,
16514 ) -> Option<Task<Result<()>>> {
16515 let rename = self.take_rename(false, window, cx)?;
16516 let workspace = self.workspace()?.downgrade();
16517 let (buffer, start) = self
16518 .buffer
16519 .read(cx)
16520 .text_anchor_for_position(rename.range.start, cx)?;
16521 let (end_buffer, _) = self
16522 .buffer
16523 .read(cx)
16524 .text_anchor_for_position(rename.range.end, cx)?;
16525 if buffer != end_buffer {
16526 return None;
16527 }
16528
16529 let old_name = rename.old_name;
16530 let new_name = rename.editor.read(cx).text(cx);
16531
16532 let rename = self.semantics_provider.as_ref()?.perform_rename(
16533 &buffer,
16534 start,
16535 new_name.clone(),
16536 cx,
16537 )?;
16538
16539 Some(cx.spawn_in(window, async move |editor, cx| {
16540 let project_transaction = rename.await?;
16541 Self::open_project_transaction(
16542 &editor,
16543 workspace,
16544 project_transaction,
16545 format!("Rename: {} → {}", old_name, new_name),
16546 cx,
16547 )
16548 .await?;
16549
16550 editor.update(cx, |editor, cx| {
16551 editor.refresh_document_highlights(cx);
16552 })?;
16553 Ok(())
16554 }))
16555 }
16556
16557 fn take_rename(
16558 &mut self,
16559 moving_cursor: bool,
16560 window: &mut Window,
16561 cx: &mut Context<Self>,
16562 ) -> Option<RenameState> {
16563 let rename = self.pending_rename.take()?;
16564 if rename.editor.focus_handle(cx).is_focused(window) {
16565 window.focus(&self.focus_handle);
16566 }
16567
16568 self.remove_blocks(
16569 [rename.block_id].into_iter().collect(),
16570 Some(Autoscroll::fit()),
16571 cx,
16572 );
16573 self.clear_highlights::<Rename>(cx);
16574 self.show_local_selections = true;
16575
16576 if moving_cursor {
16577 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16578 editor.selections.newest::<usize>(cx).head()
16579 });
16580
16581 // Update the selection to match the position of the selection inside
16582 // the rename editor.
16583 let snapshot = self.buffer.read(cx).read(cx);
16584 let rename_range = rename.range.to_offset(&snapshot);
16585 let cursor_in_editor = snapshot
16586 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16587 .min(rename_range.end);
16588 drop(snapshot);
16589
16590 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16591 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16592 });
16593 } else {
16594 self.refresh_document_highlights(cx);
16595 }
16596
16597 Some(rename)
16598 }
16599
16600 pub fn pending_rename(&self) -> Option<&RenameState> {
16601 self.pending_rename.as_ref()
16602 }
16603
16604 fn format(
16605 &mut self,
16606 _: &Format,
16607 window: &mut Window,
16608 cx: &mut Context<Self>,
16609 ) -> Option<Task<Result<()>>> {
16610 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16611
16612 let project = match &self.project {
16613 Some(project) => project.clone(),
16614 None => return None,
16615 };
16616
16617 Some(self.perform_format(
16618 project,
16619 FormatTrigger::Manual,
16620 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16621 window,
16622 cx,
16623 ))
16624 }
16625
16626 fn format_selections(
16627 &mut self,
16628 _: &FormatSelections,
16629 window: &mut Window,
16630 cx: &mut Context<Self>,
16631 ) -> Option<Task<Result<()>>> {
16632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16633
16634 let project = match &self.project {
16635 Some(project) => project.clone(),
16636 None => return None,
16637 };
16638
16639 let ranges = self
16640 .selections
16641 .all_adjusted(cx)
16642 .into_iter()
16643 .map(|selection| selection.range())
16644 .collect_vec();
16645
16646 Some(self.perform_format(
16647 project,
16648 FormatTrigger::Manual,
16649 FormatTarget::Ranges(ranges),
16650 window,
16651 cx,
16652 ))
16653 }
16654
16655 fn perform_format(
16656 &mut self,
16657 project: Entity<Project>,
16658 trigger: FormatTrigger,
16659 target: FormatTarget,
16660 window: &mut Window,
16661 cx: &mut Context<Self>,
16662 ) -> Task<Result<()>> {
16663 let buffer = self.buffer.clone();
16664 let (buffers, target) = match target {
16665 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16666 FormatTarget::Ranges(selection_ranges) => {
16667 let multi_buffer = buffer.read(cx);
16668 let snapshot = multi_buffer.read(cx);
16669 let mut buffers = HashSet::default();
16670 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16671 BTreeMap::new();
16672 for selection_range in selection_ranges {
16673 for (buffer, buffer_range, _) in
16674 snapshot.range_to_buffer_ranges(selection_range)
16675 {
16676 let buffer_id = buffer.remote_id();
16677 let start = buffer.anchor_before(buffer_range.start);
16678 let end = buffer.anchor_after(buffer_range.end);
16679 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16680 buffer_id_to_ranges
16681 .entry(buffer_id)
16682 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16683 .or_insert_with(|| vec![start..end]);
16684 }
16685 }
16686 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16687 }
16688 };
16689
16690 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16691 let selections_prev = transaction_id_prev
16692 .and_then(|transaction_id_prev| {
16693 // default to selections as they were after the last edit, if we have them,
16694 // instead of how they are now.
16695 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16696 // will take you back to where you made the last edit, instead of staying where you scrolled
16697 self.selection_history
16698 .transaction(transaction_id_prev)
16699 .map(|t| t.0.clone())
16700 })
16701 .unwrap_or_else(|| self.selections.disjoint_anchors());
16702
16703 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16704 let format = project.update(cx, |project, cx| {
16705 project.format(buffers, target, true, trigger, cx)
16706 });
16707
16708 cx.spawn_in(window, async move |editor, cx| {
16709 let transaction = futures::select_biased! {
16710 transaction = format.log_err().fuse() => transaction,
16711 () = timeout => {
16712 log::warn!("timed out waiting for formatting");
16713 None
16714 }
16715 };
16716
16717 buffer
16718 .update(cx, |buffer, cx| {
16719 if let Some(transaction) = transaction
16720 && !buffer.is_singleton()
16721 {
16722 buffer.push_transaction(&transaction.0, cx);
16723 }
16724 cx.notify();
16725 })
16726 .ok();
16727
16728 if let Some(transaction_id_now) =
16729 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16730 {
16731 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16732 if has_new_transaction {
16733 _ = editor.update(cx, |editor, _| {
16734 editor
16735 .selection_history
16736 .insert_transaction(transaction_id_now, selections_prev);
16737 });
16738 }
16739 }
16740
16741 Ok(())
16742 })
16743 }
16744
16745 fn organize_imports(
16746 &mut self,
16747 _: &OrganizeImports,
16748 window: &mut Window,
16749 cx: &mut Context<Self>,
16750 ) -> Option<Task<Result<()>>> {
16751 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16752 let project = match &self.project {
16753 Some(project) => project.clone(),
16754 None => return None,
16755 };
16756 Some(self.perform_code_action_kind(
16757 project,
16758 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16759 window,
16760 cx,
16761 ))
16762 }
16763
16764 fn perform_code_action_kind(
16765 &mut self,
16766 project: Entity<Project>,
16767 kind: CodeActionKind,
16768 window: &mut Window,
16769 cx: &mut Context<Self>,
16770 ) -> Task<Result<()>> {
16771 let buffer = self.buffer.clone();
16772 let buffers = buffer.read(cx).all_buffers();
16773 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16774 let apply_action = project.update(cx, |project, cx| {
16775 project.apply_code_action_kind(buffers, kind, true, cx)
16776 });
16777 cx.spawn_in(window, async move |_, cx| {
16778 let transaction = futures::select_biased! {
16779 () = timeout => {
16780 log::warn!("timed out waiting for executing code action");
16781 None
16782 }
16783 transaction = apply_action.log_err().fuse() => transaction,
16784 };
16785 buffer
16786 .update(cx, |buffer, cx| {
16787 // check if we need this
16788 if let Some(transaction) = transaction
16789 && !buffer.is_singleton()
16790 {
16791 buffer.push_transaction(&transaction.0, cx);
16792 }
16793 cx.notify();
16794 })
16795 .ok();
16796 Ok(())
16797 })
16798 }
16799
16800 pub fn restart_language_server(
16801 &mut self,
16802 _: &RestartLanguageServer,
16803 _: &mut Window,
16804 cx: &mut Context<Self>,
16805 ) {
16806 if let Some(project) = self.project.clone() {
16807 self.buffer.update(cx, |multi_buffer, cx| {
16808 project.update(cx, |project, cx| {
16809 project.restart_language_servers_for_buffers(
16810 multi_buffer.all_buffers().into_iter().collect(),
16811 HashSet::default(),
16812 cx,
16813 );
16814 });
16815 })
16816 }
16817 }
16818
16819 pub fn stop_language_server(
16820 &mut self,
16821 _: &StopLanguageServer,
16822 _: &mut Window,
16823 cx: &mut Context<Self>,
16824 ) {
16825 if let Some(project) = self.project.clone() {
16826 self.buffer.update(cx, |multi_buffer, cx| {
16827 project.update(cx, |project, cx| {
16828 project.stop_language_servers_for_buffers(
16829 multi_buffer.all_buffers().into_iter().collect(),
16830 HashSet::default(),
16831 cx,
16832 );
16833 cx.emit(project::Event::RefreshInlayHints);
16834 });
16835 });
16836 }
16837 }
16838
16839 fn cancel_language_server_work(
16840 workspace: &mut Workspace,
16841 _: &actions::CancelLanguageServerWork,
16842 _: &mut Window,
16843 cx: &mut Context<Workspace>,
16844 ) {
16845 let project = workspace.project();
16846 let buffers = workspace
16847 .active_item(cx)
16848 .and_then(|item| item.act_as::<Editor>(cx))
16849 .map_or(HashSet::default(), |editor| {
16850 editor.read(cx).buffer.read(cx).all_buffers()
16851 });
16852 project.update(cx, |project, cx| {
16853 project.cancel_language_server_work_for_buffers(buffers, cx);
16854 });
16855 }
16856
16857 fn show_character_palette(
16858 &mut self,
16859 _: &ShowCharacterPalette,
16860 window: &mut Window,
16861 _: &mut Context<Self>,
16862 ) {
16863 window.show_character_palette();
16864 }
16865
16866 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16867 if !self.diagnostics_enabled() {
16868 return;
16869 }
16870
16871 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16872 let buffer = self.buffer.read(cx).snapshot(cx);
16873 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16874 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16875 let is_valid = buffer
16876 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16877 .any(|entry| {
16878 entry.diagnostic.is_primary
16879 && !entry.range.is_empty()
16880 && entry.range.start == primary_range_start
16881 && entry.diagnostic.message == active_diagnostics.active_message
16882 });
16883
16884 if !is_valid {
16885 self.dismiss_diagnostics(cx);
16886 }
16887 }
16888 }
16889
16890 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16891 match &self.active_diagnostics {
16892 ActiveDiagnostic::Group(group) => Some(group),
16893 _ => None,
16894 }
16895 }
16896
16897 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16898 if !self.diagnostics_enabled() {
16899 return;
16900 }
16901 self.dismiss_diagnostics(cx);
16902 self.active_diagnostics = ActiveDiagnostic::All;
16903 }
16904
16905 fn activate_diagnostics(
16906 &mut self,
16907 buffer_id: BufferId,
16908 diagnostic: DiagnosticEntry<usize>,
16909 window: &mut Window,
16910 cx: &mut Context<Self>,
16911 ) {
16912 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16913 return;
16914 }
16915 self.dismiss_diagnostics(cx);
16916 let snapshot = self.snapshot(window, cx);
16917 let buffer = self.buffer.read(cx).snapshot(cx);
16918 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16919 return;
16920 };
16921
16922 let diagnostic_group = buffer
16923 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16924 .collect::<Vec<_>>();
16925
16926 let blocks =
16927 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16928
16929 let blocks = self.display_map.update(cx, |display_map, cx| {
16930 display_map.insert_blocks(blocks, cx).into_iter().collect()
16931 });
16932 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16933 active_range: buffer.anchor_before(diagnostic.range.start)
16934 ..buffer.anchor_after(diagnostic.range.end),
16935 active_message: diagnostic.diagnostic.message.clone(),
16936 group_id: diagnostic.diagnostic.group_id,
16937 blocks,
16938 });
16939 cx.notify();
16940 }
16941
16942 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16943 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16944 return;
16945 };
16946
16947 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16948 if let ActiveDiagnostic::Group(group) = prev {
16949 self.display_map.update(cx, |display_map, cx| {
16950 display_map.remove_blocks(group.blocks, cx);
16951 });
16952 cx.notify();
16953 }
16954 }
16955
16956 /// Disable inline diagnostics rendering for this editor.
16957 pub fn disable_inline_diagnostics(&mut self) {
16958 self.inline_diagnostics_enabled = false;
16959 self.inline_diagnostics_update = Task::ready(());
16960 self.inline_diagnostics.clear();
16961 }
16962
16963 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16964 self.diagnostics_enabled = false;
16965 self.dismiss_diagnostics(cx);
16966 self.inline_diagnostics_update = Task::ready(());
16967 self.inline_diagnostics.clear();
16968 }
16969
16970 pub fn diagnostics_enabled(&self) -> bool {
16971 self.diagnostics_enabled && self.mode.is_full()
16972 }
16973
16974 pub fn inline_diagnostics_enabled(&self) -> bool {
16975 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16976 }
16977
16978 pub fn show_inline_diagnostics(&self) -> bool {
16979 self.show_inline_diagnostics
16980 }
16981
16982 pub fn toggle_inline_diagnostics(
16983 &mut self,
16984 _: &ToggleInlineDiagnostics,
16985 window: &mut Window,
16986 cx: &mut Context<Editor>,
16987 ) {
16988 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16989 self.refresh_inline_diagnostics(false, window, cx);
16990 }
16991
16992 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16993 self.diagnostics_max_severity = severity;
16994 self.display_map.update(cx, |display_map, _| {
16995 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16996 });
16997 }
16998
16999 pub fn toggle_diagnostics(
17000 &mut self,
17001 _: &ToggleDiagnostics,
17002 window: &mut Window,
17003 cx: &mut Context<Editor>,
17004 ) {
17005 if !self.diagnostics_enabled() {
17006 return;
17007 }
17008
17009 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17010 EditorSettings::get_global(cx)
17011 .diagnostics_max_severity
17012 .filter(|severity| severity != &DiagnosticSeverity::Off)
17013 .unwrap_or(DiagnosticSeverity::Hint)
17014 } else {
17015 DiagnosticSeverity::Off
17016 };
17017 self.set_max_diagnostics_severity(new_severity, cx);
17018 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17019 self.active_diagnostics = ActiveDiagnostic::None;
17020 self.inline_diagnostics_update = Task::ready(());
17021 self.inline_diagnostics.clear();
17022 } else {
17023 self.refresh_inline_diagnostics(false, window, cx);
17024 }
17025
17026 cx.notify();
17027 }
17028
17029 pub fn toggle_minimap(
17030 &mut self,
17031 _: &ToggleMinimap,
17032 window: &mut Window,
17033 cx: &mut Context<Editor>,
17034 ) {
17035 if self.supports_minimap(cx) {
17036 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17037 }
17038 }
17039
17040 fn refresh_inline_diagnostics(
17041 &mut self,
17042 debounce: bool,
17043 window: &mut Window,
17044 cx: &mut Context<Self>,
17045 ) {
17046 let max_severity = ProjectSettings::get_global(cx)
17047 .diagnostics
17048 .inline
17049 .max_severity
17050 .unwrap_or(self.diagnostics_max_severity);
17051
17052 if !self.inline_diagnostics_enabled()
17053 || !self.show_inline_diagnostics
17054 || max_severity == DiagnosticSeverity::Off
17055 {
17056 self.inline_diagnostics_update = Task::ready(());
17057 self.inline_diagnostics.clear();
17058 return;
17059 }
17060
17061 let debounce_ms = ProjectSettings::get_global(cx)
17062 .diagnostics
17063 .inline
17064 .update_debounce_ms;
17065 let debounce = if debounce && debounce_ms > 0 {
17066 Some(Duration::from_millis(debounce_ms))
17067 } else {
17068 None
17069 };
17070 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17071 if let Some(debounce) = debounce {
17072 cx.background_executor().timer(debounce).await;
17073 }
17074 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17075 editor
17076 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17077 .ok()
17078 }) else {
17079 return;
17080 };
17081
17082 let new_inline_diagnostics = cx
17083 .background_spawn(async move {
17084 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17085 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17086 let message = diagnostic_entry
17087 .diagnostic
17088 .message
17089 .split_once('\n')
17090 .map(|(line, _)| line)
17091 .map(SharedString::new)
17092 .unwrap_or_else(|| {
17093 SharedString::from(diagnostic_entry.diagnostic.message)
17094 });
17095 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17096 let (Ok(i) | Err(i)) = inline_diagnostics
17097 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17098 inline_diagnostics.insert(
17099 i,
17100 (
17101 start_anchor,
17102 InlineDiagnostic {
17103 message,
17104 group_id: diagnostic_entry.diagnostic.group_id,
17105 start: diagnostic_entry.range.start.to_point(&snapshot),
17106 is_primary: diagnostic_entry.diagnostic.is_primary,
17107 severity: diagnostic_entry.diagnostic.severity,
17108 },
17109 ),
17110 );
17111 }
17112 inline_diagnostics
17113 })
17114 .await;
17115
17116 editor
17117 .update(cx, |editor, cx| {
17118 editor.inline_diagnostics = new_inline_diagnostics;
17119 cx.notify();
17120 })
17121 .ok();
17122 });
17123 }
17124
17125 fn pull_diagnostics(
17126 &mut self,
17127 buffer_id: Option<BufferId>,
17128 window: &Window,
17129 cx: &mut Context<Self>,
17130 ) -> Option<()> {
17131 if !self.mode().is_full() {
17132 return None;
17133 }
17134 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17135 .diagnostics
17136 .lsp_pull_diagnostics;
17137 if !pull_diagnostics_settings.enabled {
17138 return None;
17139 }
17140 let project = self.project()?.downgrade();
17141 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17142 let mut buffers = self.buffer.read(cx).all_buffers();
17143 if let Some(buffer_id) = buffer_id {
17144 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17145 }
17146
17147 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17148 cx.background_executor().timer(debounce).await;
17149
17150 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17151 buffers
17152 .into_iter()
17153 .filter_map(|buffer| {
17154 project
17155 .update(cx, |project, cx| {
17156 project.lsp_store().update(cx, |lsp_store, cx| {
17157 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17158 })
17159 })
17160 .ok()
17161 })
17162 .collect::<FuturesUnordered<_>>()
17163 }) else {
17164 return;
17165 };
17166
17167 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17168 match pull_task {
17169 Ok(()) => {
17170 if editor
17171 .update_in(cx, |editor, window, cx| {
17172 editor.update_diagnostics_state(window, cx);
17173 })
17174 .is_err()
17175 {
17176 return;
17177 }
17178 }
17179 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17180 }
17181 }
17182 });
17183
17184 Some(())
17185 }
17186
17187 pub fn set_selections_from_remote(
17188 &mut self,
17189 selections: Vec<Selection<Anchor>>,
17190 pending_selection: Option<Selection<Anchor>>,
17191 window: &mut Window,
17192 cx: &mut Context<Self>,
17193 ) {
17194 let old_cursor_position = self.selections.newest_anchor().head();
17195 self.selections.change_with(cx, |s| {
17196 s.select_anchors(selections);
17197 if let Some(pending_selection) = pending_selection {
17198 s.set_pending(pending_selection, SelectMode::Character);
17199 } else {
17200 s.clear_pending();
17201 }
17202 });
17203 self.selections_did_change(
17204 false,
17205 &old_cursor_position,
17206 SelectionEffects::default(),
17207 window,
17208 cx,
17209 );
17210 }
17211
17212 pub fn transact(
17213 &mut self,
17214 window: &mut Window,
17215 cx: &mut Context<Self>,
17216 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17217 ) -> Option<TransactionId> {
17218 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17219 this.start_transaction_at(Instant::now(), window, cx);
17220 update(this, window, cx);
17221 this.end_transaction_at(Instant::now(), cx)
17222 })
17223 }
17224
17225 pub fn start_transaction_at(
17226 &mut self,
17227 now: Instant,
17228 window: &mut Window,
17229 cx: &mut Context<Self>,
17230 ) -> Option<TransactionId> {
17231 self.end_selection(window, cx);
17232 if let Some(tx_id) = self
17233 .buffer
17234 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17235 {
17236 self.selection_history
17237 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17238 cx.emit(EditorEvent::TransactionBegun {
17239 transaction_id: tx_id,
17240 });
17241 Some(tx_id)
17242 } else {
17243 None
17244 }
17245 }
17246
17247 pub fn end_transaction_at(
17248 &mut self,
17249 now: Instant,
17250 cx: &mut Context<Self>,
17251 ) -> Option<TransactionId> {
17252 if let Some(transaction_id) = self
17253 .buffer
17254 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17255 {
17256 if let Some((_, end_selections)) =
17257 self.selection_history.transaction_mut(transaction_id)
17258 {
17259 *end_selections = Some(self.selections.disjoint_anchors());
17260 } else {
17261 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17262 }
17263
17264 cx.emit(EditorEvent::Edited { transaction_id });
17265 Some(transaction_id)
17266 } else {
17267 None
17268 }
17269 }
17270
17271 pub fn modify_transaction_selection_history(
17272 &mut self,
17273 transaction_id: TransactionId,
17274 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17275 ) -> bool {
17276 self.selection_history
17277 .transaction_mut(transaction_id)
17278 .map(modify)
17279 .is_some()
17280 }
17281
17282 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17283 if self.selection_mark_mode {
17284 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17285 s.move_with(|_, sel| {
17286 sel.collapse_to(sel.head(), SelectionGoal::None);
17287 });
17288 })
17289 }
17290 self.selection_mark_mode = true;
17291 cx.notify();
17292 }
17293
17294 pub fn swap_selection_ends(
17295 &mut self,
17296 _: &actions::SwapSelectionEnds,
17297 window: &mut Window,
17298 cx: &mut Context<Self>,
17299 ) {
17300 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17301 s.move_with(|_, sel| {
17302 if sel.start != sel.end {
17303 sel.reversed = !sel.reversed
17304 }
17305 });
17306 });
17307 self.request_autoscroll(Autoscroll::newest(), cx);
17308 cx.notify();
17309 }
17310
17311 pub fn toggle_focus(
17312 workspace: &mut Workspace,
17313 _: &actions::ToggleFocus,
17314 window: &mut Window,
17315 cx: &mut Context<Workspace>,
17316 ) {
17317 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17318 return;
17319 };
17320 workspace.activate_item(&item, true, true, window, cx);
17321 }
17322
17323 pub fn toggle_fold(
17324 &mut self,
17325 _: &actions::ToggleFold,
17326 window: &mut Window,
17327 cx: &mut Context<Self>,
17328 ) {
17329 if self.is_singleton(cx) {
17330 let selection = self.selections.newest::<Point>(cx);
17331
17332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17333 let range = if selection.is_empty() {
17334 let point = selection.head().to_display_point(&display_map);
17335 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17336 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17337 .to_point(&display_map);
17338 start..end
17339 } else {
17340 selection.range()
17341 };
17342 if display_map.folds_in_range(range).next().is_some() {
17343 self.unfold_lines(&Default::default(), window, cx)
17344 } else {
17345 self.fold(&Default::default(), window, cx)
17346 }
17347 } else {
17348 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17349 let buffer_ids: HashSet<_> = self
17350 .selections
17351 .disjoint_anchor_ranges()
17352 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17353 .collect();
17354
17355 let should_unfold = buffer_ids
17356 .iter()
17357 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17358
17359 for buffer_id in buffer_ids {
17360 if should_unfold {
17361 self.unfold_buffer(buffer_id, cx);
17362 } else {
17363 self.fold_buffer(buffer_id, cx);
17364 }
17365 }
17366 }
17367 }
17368
17369 pub fn toggle_fold_recursive(
17370 &mut self,
17371 _: &actions::ToggleFoldRecursive,
17372 window: &mut Window,
17373 cx: &mut Context<Self>,
17374 ) {
17375 let selection = self.selections.newest::<Point>(cx);
17376
17377 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17378 let range = if selection.is_empty() {
17379 let point = selection.head().to_display_point(&display_map);
17380 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17381 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17382 .to_point(&display_map);
17383 start..end
17384 } else {
17385 selection.range()
17386 };
17387 if display_map.folds_in_range(range).next().is_some() {
17388 self.unfold_recursive(&Default::default(), window, cx)
17389 } else {
17390 self.fold_recursive(&Default::default(), window, cx)
17391 }
17392 }
17393
17394 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17395 if self.is_singleton(cx) {
17396 let mut to_fold = Vec::new();
17397 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17398 let selections = self.selections.all_adjusted(cx);
17399
17400 for selection in selections {
17401 let range = selection.range().sorted();
17402 let buffer_start_row = range.start.row;
17403
17404 if range.start.row != range.end.row {
17405 let mut found = false;
17406 let mut row = range.start.row;
17407 while row <= range.end.row {
17408 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17409 {
17410 found = true;
17411 row = crease.range().end.row + 1;
17412 to_fold.push(crease);
17413 } else {
17414 row += 1
17415 }
17416 }
17417 if found {
17418 continue;
17419 }
17420 }
17421
17422 for row in (0..=range.start.row).rev() {
17423 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17424 && crease.range().end.row >= buffer_start_row
17425 {
17426 to_fold.push(crease);
17427 if row <= range.start.row {
17428 break;
17429 }
17430 }
17431 }
17432 }
17433
17434 self.fold_creases(to_fold, true, window, cx);
17435 } else {
17436 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17437 let buffer_ids = self
17438 .selections
17439 .disjoint_anchor_ranges()
17440 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17441 .collect::<HashSet<_>>();
17442 for buffer_id in buffer_ids {
17443 self.fold_buffer(buffer_id, cx);
17444 }
17445 }
17446 }
17447
17448 pub fn toggle_fold_all(
17449 &mut self,
17450 _: &actions::ToggleFoldAll,
17451 window: &mut Window,
17452 cx: &mut Context<Self>,
17453 ) {
17454 if self.buffer.read(cx).is_singleton() {
17455 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17456 let has_folds = display_map
17457 .folds_in_range(0..display_map.buffer_snapshot.len())
17458 .next()
17459 .is_some();
17460
17461 if has_folds {
17462 self.unfold_all(&actions::UnfoldAll, window, cx);
17463 } else {
17464 self.fold_all(&actions::FoldAll, window, cx);
17465 }
17466 } else {
17467 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17468 let should_unfold = buffer_ids
17469 .iter()
17470 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17471
17472 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17473 editor
17474 .update_in(cx, |editor, _, cx| {
17475 for buffer_id in buffer_ids {
17476 if should_unfold {
17477 editor.unfold_buffer(buffer_id, cx);
17478 } else {
17479 editor.fold_buffer(buffer_id, cx);
17480 }
17481 }
17482 })
17483 .ok();
17484 });
17485 }
17486 }
17487
17488 fn fold_at_level(
17489 &mut self,
17490 fold_at: &FoldAtLevel,
17491 window: &mut Window,
17492 cx: &mut Context<Self>,
17493 ) {
17494 if !self.buffer.read(cx).is_singleton() {
17495 return;
17496 }
17497
17498 let fold_at_level = fold_at.0;
17499 let snapshot = self.buffer.read(cx).snapshot(cx);
17500 let mut to_fold = Vec::new();
17501 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17502
17503 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17504 while start_row < end_row {
17505 match self
17506 .snapshot(window, cx)
17507 .crease_for_buffer_row(MultiBufferRow(start_row))
17508 {
17509 Some(crease) => {
17510 let nested_start_row = crease.range().start.row + 1;
17511 let nested_end_row = crease.range().end.row;
17512
17513 if current_level < fold_at_level {
17514 stack.push((nested_start_row, nested_end_row, current_level + 1));
17515 } else if current_level == fold_at_level {
17516 to_fold.push(crease);
17517 }
17518
17519 start_row = nested_end_row + 1;
17520 }
17521 None => start_row += 1,
17522 }
17523 }
17524 }
17525
17526 self.fold_creases(to_fold, true, window, cx);
17527 }
17528
17529 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17530 if self.buffer.read(cx).is_singleton() {
17531 let mut fold_ranges = Vec::new();
17532 let snapshot = self.buffer.read(cx).snapshot(cx);
17533
17534 for row in 0..snapshot.max_row().0 {
17535 if let Some(foldable_range) = self
17536 .snapshot(window, cx)
17537 .crease_for_buffer_row(MultiBufferRow(row))
17538 {
17539 fold_ranges.push(foldable_range);
17540 }
17541 }
17542
17543 self.fold_creases(fold_ranges, true, window, cx);
17544 } else {
17545 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17546 editor
17547 .update_in(cx, |editor, _, cx| {
17548 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17549 editor.fold_buffer(buffer_id, cx);
17550 }
17551 })
17552 .ok();
17553 });
17554 }
17555 }
17556
17557 pub fn fold_function_bodies(
17558 &mut self,
17559 _: &actions::FoldFunctionBodies,
17560 window: &mut Window,
17561 cx: &mut Context<Self>,
17562 ) {
17563 let snapshot = self.buffer.read(cx).snapshot(cx);
17564
17565 let ranges = snapshot
17566 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17567 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17568 .collect::<Vec<_>>();
17569
17570 let creases = ranges
17571 .into_iter()
17572 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17573 .collect();
17574
17575 self.fold_creases(creases, true, window, cx);
17576 }
17577
17578 pub fn fold_recursive(
17579 &mut self,
17580 _: &actions::FoldRecursive,
17581 window: &mut Window,
17582 cx: &mut Context<Self>,
17583 ) {
17584 let mut to_fold = Vec::new();
17585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17586 let selections = self.selections.all_adjusted(cx);
17587
17588 for selection in selections {
17589 let range = selection.range().sorted();
17590 let buffer_start_row = range.start.row;
17591
17592 if range.start.row != range.end.row {
17593 let mut found = false;
17594 for row in range.start.row..=range.end.row {
17595 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17596 found = true;
17597 to_fold.push(crease);
17598 }
17599 }
17600 if found {
17601 continue;
17602 }
17603 }
17604
17605 for row in (0..=range.start.row).rev() {
17606 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17607 if crease.range().end.row >= buffer_start_row {
17608 to_fold.push(crease);
17609 } else {
17610 break;
17611 }
17612 }
17613 }
17614 }
17615
17616 self.fold_creases(to_fold, true, window, cx);
17617 }
17618
17619 pub fn fold_at(
17620 &mut self,
17621 buffer_row: MultiBufferRow,
17622 window: &mut Window,
17623 cx: &mut Context<Self>,
17624 ) {
17625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17626
17627 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17628 let autoscroll = self
17629 .selections
17630 .all::<Point>(cx)
17631 .iter()
17632 .any(|selection| crease.range().overlaps(&selection.range()));
17633
17634 self.fold_creases(vec![crease], autoscroll, window, cx);
17635 }
17636 }
17637
17638 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17639 if self.is_singleton(cx) {
17640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17641 let buffer = &display_map.buffer_snapshot;
17642 let selections = self.selections.all::<Point>(cx);
17643 let ranges = selections
17644 .iter()
17645 .map(|s| {
17646 let range = s.display_range(&display_map).sorted();
17647 let mut start = range.start.to_point(&display_map);
17648 let mut end = range.end.to_point(&display_map);
17649 start.column = 0;
17650 end.column = buffer.line_len(MultiBufferRow(end.row));
17651 start..end
17652 })
17653 .collect::<Vec<_>>();
17654
17655 self.unfold_ranges(&ranges, true, true, cx);
17656 } else {
17657 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17658 let buffer_ids = self
17659 .selections
17660 .disjoint_anchor_ranges()
17661 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17662 .collect::<HashSet<_>>();
17663 for buffer_id in buffer_ids {
17664 self.unfold_buffer(buffer_id, cx);
17665 }
17666 }
17667 }
17668
17669 pub fn unfold_recursive(
17670 &mut self,
17671 _: &UnfoldRecursive,
17672 _window: &mut Window,
17673 cx: &mut Context<Self>,
17674 ) {
17675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17676 let selections = self.selections.all::<Point>(cx);
17677 let ranges = selections
17678 .iter()
17679 .map(|s| {
17680 let mut range = s.display_range(&display_map).sorted();
17681 *range.start.column_mut() = 0;
17682 *range.end.column_mut() = display_map.line_len(range.end.row());
17683 let start = range.start.to_point(&display_map);
17684 let end = range.end.to_point(&display_map);
17685 start..end
17686 })
17687 .collect::<Vec<_>>();
17688
17689 self.unfold_ranges(&ranges, true, true, cx);
17690 }
17691
17692 pub fn unfold_at(
17693 &mut self,
17694 buffer_row: MultiBufferRow,
17695 _window: &mut Window,
17696 cx: &mut Context<Self>,
17697 ) {
17698 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17699
17700 let intersection_range = Point::new(buffer_row.0, 0)
17701 ..Point::new(
17702 buffer_row.0,
17703 display_map.buffer_snapshot.line_len(buffer_row),
17704 );
17705
17706 let autoscroll = self
17707 .selections
17708 .all::<Point>(cx)
17709 .iter()
17710 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17711
17712 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17713 }
17714
17715 pub fn unfold_all(
17716 &mut self,
17717 _: &actions::UnfoldAll,
17718 _window: &mut Window,
17719 cx: &mut Context<Self>,
17720 ) {
17721 if self.buffer.read(cx).is_singleton() {
17722 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17723 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17724 } else {
17725 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17726 editor
17727 .update(cx, |editor, cx| {
17728 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17729 editor.unfold_buffer(buffer_id, cx);
17730 }
17731 })
17732 .ok();
17733 });
17734 }
17735 }
17736
17737 pub fn fold_selected_ranges(
17738 &mut self,
17739 _: &FoldSelectedRanges,
17740 window: &mut Window,
17741 cx: &mut Context<Self>,
17742 ) {
17743 let selections = self.selections.all_adjusted(cx);
17744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17745 let ranges = selections
17746 .into_iter()
17747 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17748 .collect::<Vec<_>>();
17749 self.fold_creases(ranges, true, window, cx);
17750 }
17751
17752 pub fn fold_ranges<T: ToOffset + Clone>(
17753 &mut self,
17754 ranges: Vec<Range<T>>,
17755 auto_scroll: bool,
17756 window: &mut Window,
17757 cx: &mut Context<Self>,
17758 ) {
17759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17760 let ranges = ranges
17761 .into_iter()
17762 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17763 .collect::<Vec<_>>();
17764 self.fold_creases(ranges, auto_scroll, window, cx);
17765 }
17766
17767 pub fn fold_creases<T: ToOffset + Clone>(
17768 &mut self,
17769 creases: Vec<Crease<T>>,
17770 auto_scroll: bool,
17771 _window: &mut Window,
17772 cx: &mut Context<Self>,
17773 ) {
17774 if creases.is_empty() {
17775 return;
17776 }
17777
17778 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17779
17780 if auto_scroll {
17781 self.request_autoscroll(Autoscroll::fit(), cx);
17782 }
17783
17784 cx.notify();
17785
17786 self.scrollbar_marker_state.dirty = true;
17787 self.folds_did_change(cx);
17788 }
17789
17790 /// Removes any folds whose ranges intersect any of the given ranges.
17791 pub fn unfold_ranges<T: ToOffset + Clone>(
17792 &mut self,
17793 ranges: &[Range<T>],
17794 inclusive: bool,
17795 auto_scroll: bool,
17796 cx: &mut Context<Self>,
17797 ) {
17798 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17799 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17800 });
17801 self.folds_did_change(cx);
17802 }
17803
17804 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17805 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17806 return;
17807 }
17808 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17809 self.display_map.update(cx, |display_map, cx| {
17810 display_map.fold_buffers([buffer_id], cx)
17811 });
17812 cx.emit(EditorEvent::BufferFoldToggled {
17813 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17814 folded: true,
17815 });
17816 cx.notify();
17817 }
17818
17819 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17820 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17821 return;
17822 }
17823 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17824 self.display_map.update(cx, |display_map, cx| {
17825 display_map.unfold_buffers([buffer_id], cx);
17826 });
17827 cx.emit(EditorEvent::BufferFoldToggled {
17828 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17829 folded: false,
17830 });
17831 cx.notify();
17832 }
17833
17834 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17835 self.display_map.read(cx).is_buffer_folded(buffer)
17836 }
17837
17838 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17839 self.display_map.read(cx).folded_buffers()
17840 }
17841
17842 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17843 self.display_map.update(cx, |display_map, cx| {
17844 display_map.disable_header_for_buffer(buffer_id, cx);
17845 });
17846 cx.notify();
17847 }
17848
17849 /// Removes any folds with the given ranges.
17850 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17851 &mut self,
17852 ranges: &[Range<T>],
17853 type_id: TypeId,
17854 auto_scroll: bool,
17855 cx: &mut Context<Self>,
17856 ) {
17857 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17858 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17859 });
17860 self.folds_did_change(cx);
17861 }
17862
17863 fn remove_folds_with<T: ToOffset + Clone>(
17864 &mut self,
17865 ranges: &[Range<T>],
17866 auto_scroll: bool,
17867 cx: &mut Context<Self>,
17868 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17869 ) {
17870 if ranges.is_empty() {
17871 return;
17872 }
17873
17874 let mut buffers_affected = HashSet::default();
17875 let multi_buffer = self.buffer().read(cx);
17876 for range in ranges {
17877 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17878 buffers_affected.insert(buffer.read(cx).remote_id());
17879 };
17880 }
17881
17882 self.display_map.update(cx, update);
17883
17884 if auto_scroll {
17885 self.request_autoscroll(Autoscroll::fit(), cx);
17886 }
17887
17888 cx.notify();
17889 self.scrollbar_marker_state.dirty = true;
17890 self.active_indent_guides_state.dirty = true;
17891 }
17892
17893 pub fn update_renderer_widths(
17894 &mut self,
17895 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17896 cx: &mut Context<Self>,
17897 ) -> bool {
17898 self.display_map
17899 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17900 }
17901
17902 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17903 self.display_map.read(cx).fold_placeholder.clone()
17904 }
17905
17906 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17907 self.buffer.update(cx, |buffer, cx| {
17908 buffer.set_all_diff_hunks_expanded(cx);
17909 });
17910 }
17911
17912 pub fn expand_all_diff_hunks(
17913 &mut self,
17914 _: &ExpandAllDiffHunks,
17915 _window: &mut Window,
17916 cx: &mut Context<Self>,
17917 ) {
17918 self.buffer.update(cx, |buffer, cx| {
17919 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17920 });
17921 }
17922
17923 pub fn toggle_selected_diff_hunks(
17924 &mut self,
17925 _: &ToggleSelectedDiffHunks,
17926 _window: &mut Window,
17927 cx: &mut Context<Self>,
17928 ) {
17929 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17930 self.toggle_diff_hunks_in_ranges(ranges, cx);
17931 }
17932
17933 pub fn diff_hunks_in_ranges<'a>(
17934 &'a self,
17935 ranges: &'a [Range<Anchor>],
17936 buffer: &'a MultiBufferSnapshot,
17937 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17938 ranges.iter().flat_map(move |range| {
17939 let end_excerpt_id = range.end.excerpt_id;
17940 let range = range.to_point(buffer);
17941 let mut peek_end = range.end;
17942 if range.end.row < buffer.max_row().0 {
17943 peek_end = Point::new(range.end.row + 1, 0);
17944 }
17945 buffer
17946 .diff_hunks_in_range(range.start..peek_end)
17947 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17948 })
17949 }
17950
17951 pub fn has_stageable_diff_hunks_in_ranges(
17952 &self,
17953 ranges: &[Range<Anchor>],
17954 snapshot: &MultiBufferSnapshot,
17955 ) -> bool {
17956 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17957 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17958 }
17959
17960 pub fn toggle_staged_selected_diff_hunks(
17961 &mut self,
17962 _: &::git::ToggleStaged,
17963 _: &mut Window,
17964 cx: &mut Context<Self>,
17965 ) {
17966 let snapshot = self.buffer.read(cx).snapshot(cx);
17967 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17968 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17969 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17970 }
17971
17972 pub fn set_render_diff_hunk_controls(
17973 &mut self,
17974 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17975 cx: &mut Context<Self>,
17976 ) {
17977 self.render_diff_hunk_controls = render_diff_hunk_controls;
17978 cx.notify();
17979 }
17980
17981 pub fn stage_and_next(
17982 &mut self,
17983 _: &::git::StageAndNext,
17984 window: &mut Window,
17985 cx: &mut Context<Self>,
17986 ) {
17987 self.do_stage_or_unstage_and_next(true, window, cx);
17988 }
17989
17990 pub fn unstage_and_next(
17991 &mut self,
17992 _: &::git::UnstageAndNext,
17993 window: &mut Window,
17994 cx: &mut Context<Self>,
17995 ) {
17996 self.do_stage_or_unstage_and_next(false, window, cx);
17997 }
17998
17999 pub fn stage_or_unstage_diff_hunks(
18000 &mut self,
18001 stage: bool,
18002 ranges: Vec<Range<Anchor>>,
18003 cx: &mut Context<Self>,
18004 ) {
18005 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18006 cx.spawn(async move |this, cx| {
18007 task.await?;
18008 this.update(cx, |this, cx| {
18009 let snapshot = this.buffer.read(cx).snapshot(cx);
18010 let chunk_by = this
18011 .diff_hunks_in_ranges(&ranges, &snapshot)
18012 .chunk_by(|hunk| hunk.buffer_id);
18013 for (buffer_id, hunks) in &chunk_by {
18014 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18015 }
18016 })
18017 })
18018 .detach_and_log_err(cx);
18019 }
18020
18021 fn save_buffers_for_ranges_if_needed(
18022 &mut self,
18023 ranges: &[Range<Anchor>],
18024 cx: &mut Context<Editor>,
18025 ) -> Task<Result<()>> {
18026 let multibuffer = self.buffer.read(cx);
18027 let snapshot = multibuffer.read(cx);
18028 let buffer_ids: HashSet<_> = ranges
18029 .iter()
18030 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18031 .collect();
18032 drop(snapshot);
18033
18034 let mut buffers = HashSet::default();
18035 for buffer_id in buffer_ids {
18036 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18037 let buffer = buffer_entity.read(cx);
18038 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18039 {
18040 buffers.insert(buffer_entity);
18041 }
18042 }
18043 }
18044
18045 if let Some(project) = &self.project {
18046 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18047 } else {
18048 Task::ready(Ok(()))
18049 }
18050 }
18051
18052 fn do_stage_or_unstage_and_next(
18053 &mut self,
18054 stage: bool,
18055 window: &mut Window,
18056 cx: &mut Context<Self>,
18057 ) {
18058 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18059
18060 if ranges.iter().any(|range| range.start != range.end) {
18061 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18062 return;
18063 }
18064
18065 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18066 let snapshot = self.snapshot(window, cx);
18067 let position = self.selections.newest::<Point>(cx).head();
18068 let mut row = snapshot
18069 .buffer_snapshot
18070 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18071 .find(|hunk| hunk.row_range.start.0 > position.row)
18072 .map(|hunk| hunk.row_range.start);
18073
18074 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18075 // Outside of the project diff editor, wrap around to the beginning.
18076 if !all_diff_hunks_expanded {
18077 row = row.or_else(|| {
18078 snapshot
18079 .buffer_snapshot
18080 .diff_hunks_in_range(Point::zero()..position)
18081 .find(|hunk| hunk.row_range.end.0 < position.row)
18082 .map(|hunk| hunk.row_range.start)
18083 });
18084 }
18085
18086 if let Some(row) = row {
18087 let destination = Point::new(row.0, 0);
18088 let autoscroll = Autoscroll::center();
18089
18090 self.unfold_ranges(&[destination..destination], false, false, cx);
18091 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18092 s.select_ranges([destination..destination]);
18093 });
18094 }
18095 }
18096
18097 fn do_stage_or_unstage(
18098 &self,
18099 stage: bool,
18100 buffer_id: BufferId,
18101 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18102 cx: &mut App,
18103 ) -> Option<()> {
18104 let project = self.project()?;
18105 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18106 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18107 let buffer_snapshot = buffer.read(cx).snapshot();
18108 let file_exists = buffer_snapshot
18109 .file()
18110 .is_some_and(|file| file.disk_state().exists());
18111 diff.update(cx, |diff, cx| {
18112 diff.stage_or_unstage_hunks(
18113 stage,
18114 &hunks
18115 .map(|hunk| buffer_diff::DiffHunk {
18116 buffer_range: hunk.buffer_range,
18117 diff_base_byte_range: hunk.diff_base_byte_range,
18118 secondary_status: hunk.secondary_status,
18119 range: Point::zero()..Point::zero(), // unused
18120 })
18121 .collect::<Vec<_>>(),
18122 &buffer_snapshot,
18123 file_exists,
18124 cx,
18125 )
18126 });
18127 None
18128 }
18129
18130 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18131 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18132 self.buffer
18133 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18134 }
18135
18136 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18137 self.buffer.update(cx, |buffer, cx| {
18138 let ranges = vec![Anchor::min()..Anchor::max()];
18139 if !buffer.all_diff_hunks_expanded()
18140 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18141 {
18142 buffer.collapse_diff_hunks(ranges, cx);
18143 true
18144 } else {
18145 false
18146 }
18147 })
18148 }
18149
18150 fn toggle_diff_hunks_in_ranges(
18151 &mut self,
18152 ranges: Vec<Range<Anchor>>,
18153 cx: &mut Context<Editor>,
18154 ) {
18155 self.buffer.update(cx, |buffer, cx| {
18156 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18157 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18158 })
18159 }
18160
18161 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18162 self.buffer.update(cx, |buffer, cx| {
18163 let snapshot = buffer.snapshot(cx);
18164 let excerpt_id = range.end.excerpt_id;
18165 let point_range = range.to_point(&snapshot);
18166 let expand = !buffer.single_hunk_is_expanded(range, cx);
18167 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18168 })
18169 }
18170
18171 pub(crate) fn apply_all_diff_hunks(
18172 &mut self,
18173 _: &ApplyAllDiffHunks,
18174 window: &mut Window,
18175 cx: &mut Context<Self>,
18176 ) {
18177 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18178
18179 let buffers = self.buffer.read(cx).all_buffers();
18180 for branch_buffer in buffers {
18181 branch_buffer.update(cx, |branch_buffer, cx| {
18182 branch_buffer.merge_into_base(Vec::new(), cx);
18183 });
18184 }
18185
18186 if let Some(project) = self.project.clone() {
18187 self.save(
18188 SaveOptions {
18189 format: true,
18190 autosave: false,
18191 },
18192 project,
18193 window,
18194 cx,
18195 )
18196 .detach_and_log_err(cx);
18197 }
18198 }
18199
18200 pub(crate) fn apply_selected_diff_hunks(
18201 &mut self,
18202 _: &ApplyDiffHunk,
18203 window: &mut Window,
18204 cx: &mut Context<Self>,
18205 ) {
18206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18207 let snapshot = self.snapshot(window, cx);
18208 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18209 let mut ranges_by_buffer = HashMap::default();
18210 self.transact(window, cx, |editor, _window, cx| {
18211 for hunk in hunks {
18212 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18213 ranges_by_buffer
18214 .entry(buffer.clone())
18215 .or_insert_with(Vec::new)
18216 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18217 }
18218 }
18219
18220 for (buffer, ranges) in ranges_by_buffer {
18221 buffer.update(cx, |buffer, cx| {
18222 buffer.merge_into_base(ranges, cx);
18223 });
18224 }
18225 });
18226
18227 if let Some(project) = self.project.clone() {
18228 self.save(
18229 SaveOptions {
18230 format: true,
18231 autosave: false,
18232 },
18233 project,
18234 window,
18235 cx,
18236 )
18237 .detach_and_log_err(cx);
18238 }
18239 }
18240
18241 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18242 if hovered != self.gutter_hovered {
18243 self.gutter_hovered = hovered;
18244 cx.notify();
18245 }
18246 }
18247
18248 pub fn insert_blocks(
18249 &mut self,
18250 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18251 autoscroll: Option<Autoscroll>,
18252 cx: &mut Context<Self>,
18253 ) -> Vec<CustomBlockId> {
18254 let blocks = self
18255 .display_map
18256 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18257 if let Some(autoscroll) = autoscroll {
18258 self.request_autoscroll(autoscroll, cx);
18259 }
18260 cx.notify();
18261 blocks
18262 }
18263
18264 pub fn resize_blocks(
18265 &mut self,
18266 heights: HashMap<CustomBlockId, u32>,
18267 autoscroll: Option<Autoscroll>,
18268 cx: &mut Context<Self>,
18269 ) {
18270 self.display_map
18271 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18272 if let Some(autoscroll) = autoscroll {
18273 self.request_autoscroll(autoscroll, cx);
18274 }
18275 cx.notify();
18276 }
18277
18278 pub fn replace_blocks(
18279 &mut self,
18280 renderers: HashMap<CustomBlockId, RenderBlock>,
18281 autoscroll: Option<Autoscroll>,
18282 cx: &mut Context<Self>,
18283 ) {
18284 self.display_map
18285 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18286 if let Some(autoscroll) = autoscroll {
18287 self.request_autoscroll(autoscroll, cx);
18288 }
18289 cx.notify();
18290 }
18291
18292 pub fn remove_blocks(
18293 &mut self,
18294 block_ids: HashSet<CustomBlockId>,
18295 autoscroll: Option<Autoscroll>,
18296 cx: &mut Context<Self>,
18297 ) {
18298 self.display_map.update(cx, |display_map, cx| {
18299 display_map.remove_blocks(block_ids, cx)
18300 });
18301 if let Some(autoscroll) = autoscroll {
18302 self.request_autoscroll(autoscroll, cx);
18303 }
18304 cx.notify();
18305 }
18306
18307 pub fn row_for_block(
18308 &self,
18309 block_id: CustomBlockId,
18310 cx: &mut Context<Self>,
18311 ) -> Option<DisplayRow> {
18312 self.display_map
18313 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18314 }
18315
18316 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18317 self.focused_block = Some(focused_block);
18318 }
18319
18320 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18321 self.focused_block.take()
18322 }
18323
18324 pub fn insert_creases(
18325 &mut self,
18326 creases: impl IntoIterator<Item = Crease<Anchor>>,
18327 cx: &mut Context<Self>,
18328 ) -> Vec<CreaseId> {
18329 self.display_map
18330 .update(cx, |map, cx| map.insert_creases(creases, cx))
18331 }
18332
18333 pub fn remove_creases(
18334 &mut self,
18335 ids: impl IntoIterator<Item = CreaseId>,
18336 cx: &mut Context<Self>,
18337 ) -> Vec<(CreaseId, Range<Anchor>)> {
18338 self.display_map
18339 .update(cx, |map, cx| map.remove_creases(ids, cx))
18340 }
18341
18342 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18343 self.display_map
18344 .update(cx, |map, cx| map.snapshot(cx))
18345 .longest_row()
18346 }
18347
18348 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18349 self.display_map
18350 .update(cx, |map, cx| map.snapshot(cx))
18351 .max_point()
18352 }
18353
18354 pub fn text(&self, cx: &App) -> String {
18355 self.buffer.read(cx).read(cx).text()
18356 }
18357
18358 pub fn is_empty(&self, cx: &App) -> bool {
18359 self.buffer.read(cx).read(cx).is_empty()
18360 }
18361
18362 pub fn text_option(&self, cx: &App) -> Option<String> {
18363 let text = self.text(cx);
18364 let text = text.trim();
18365
18366 if text.is_empty() {
18367 return None;
18368 }
18369
18370 Some(text.to_string())
18371 }
18372
18373 pub fn set_text(
18374 &mut self,
18375 text: impl Into<Arc<str>>,
18376 window: &mut Window,
18377 cx: &mut Context<Self>,
18378 ) {
18379 self.transact(window, cx, |this, _, cx| {
18380 this.buffer
18381 .read(cx)
18382 .as_singleton()
18383 .expect("you can only call set_text on editors for singleton buffers")
18384 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18385 });
18386 }
18387
18388 pub fn display_text(&self, cx: &mut App) -> String {
18389 self.display_map
18390 .update(cx, |map, cx| map.snapshot(cx))
18391 .text()
18392 }
18393
18394 fn create_minimap(
18395 &self,
18396 minimap_settings: MinimapSettings,
18397 window: &mut Window,
18398 cx: &mut Context<Self>,
18399 ) -> Option<Entity<Self>> {
18400 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18401 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18402 }
18403
18404 fn initialize_new_minimap(
18405 &self,
18406 minimap_settings: MinimapSettings,
18407 window: &mut Window,
18408 cx: &mut Context<Self>,
18409 ) -> Entity<Self> {
18410 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18411
18412 let mut minimap = Editor::new_internal(
18413 EditorMode::Minimap {
18414 parent: cx.weak_entity(),
18415 },
18416 self.buffer.clone(),
18417 None,
18418 Some(self.display_map.clone()),
18419 window,
18420 cx,
18421 );
18422 minimap.scroll_manager.clone_state(&self.scroll_manager);
18423 minimap.set_text_style_refinement(TextStyleRefinement {
18424 font_size: Some(MINIMAP_FONT_SIZE),
18425 font_weight: Some(MINIMAP_FONT_WEIGHT),
18426 ..Default::default()
18427 });
18428 minimap.update_minimap_configuration(minimap_settings, cx);
18429 cx.new(|_| minimap)
18430 }
18431
18432 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18433 let current_line_highlight = minimap_settings
18434 .current_line_highlight
18435 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18436 self.set_current_line_highlight(Some(current_line_highlight));
18437 }
18438
18439 pub fn minimap(&self) -> Option<&Entity<Self>> {
18440 self.minimap
18441 .as_ref()
18442 .filter(|_| self.minimap_visibility.visible())
18443 }
18444
18445 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18446 let mut wrap_guides = smallvec![];
18447
18448 if self.show_wrap_guides == Some(false) {
18449 return wrap_guides;
18450 }
18451
18452 let settings = self.buffer.read(cx).language_settings(cx);
18453 if settings.show_wrap_guides {
18454 match self.soft_wrap_mode(cx) {
18455 SoftWrap::Column(soft_wrap) => {
18456 wrap_guides.push((soft_wrap as usize, true));
18457 }
18458 SoftWrap::Bounded(soft_wrap) => {
18459 wrap_guides.push((soft_wrap as usize, true));
18460 }
18461 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18462 }
18463 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18464 }
18465
18466 wrap_guides
18467 }
18468
18469 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18470 let settings = self.buffer.read(cx).language_settings(cx);
18471 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18472 match mode {
18473 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18474 SoftWrap::None
18475 }
18476 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18477 language_settings::SoftWrap::PreferredLineLength => {
18478 SoftWrap::Column(settings.preferred_line_length)
18479 }
18480 language_settings::SoftWrap::Bounded => {
18481 SoftWrap::Bounded(settings.preferred_line_length)
18482 }
18483 }
18484 }
18485
18486 pub fn set_soft_wrap_mode(
18487 &mut self,
18488 mode: language_settings::SoftWrap,
18489
18490 cx: &mut Context<Self>,
18491 ) {
18492 self.soft_wrap_mode_override = Some(mode);
18493 cx.notify();
18494 }
18495
18496 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18497 self.hard_wrap = hard_wrap;
18498 cx.notify();
18499 }
18500
18501 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18502 self.text_style_refinement = Some(style);
18503 }
18504
18505 /// called by the Element so we know what style we were most recently rendered with.
18506 pub(crate) fn set_style(
18507 &mut self,
18508 style: EditorStyle,
18509 window: &mut Window,
18510 cx: &mut Context<Self>,
18511 ) {
18512 // We intentionally do not inform the display map about the minimap style
18513 // so that wrapping is not recalculated and stays consistent for the editor
18514 // and its linked minimap.
18515 if !self.mode.is_minimap() {
18516 let rem_size = window.rem_size();
18517 self.display_map.update(cx, |map, cx| {
18518 map.set_font(
18519 style.text.font(),
18520 style.text.font_size.to_pixels(rem_size),
18521 cx,
18522 )
18523 });
18524 }
18525 self.style = Some(style);
18526 }
18527
18528 pub fn style(&self) -> Option<&EditorStyle> {
18529 self.style.as_ref()
18530 }
18531
18532 // Called by the element. This method is not designed to be called outside of the editor
18533 // element's layout code because it does not notify when rewrapping is computed synchronously.
18534 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18535 self.display_map
18536 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18537 }
18538
18539 pub fn set_soft_wrap(&mut self) {
18540 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18541 }
18542
18543 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18544 if self.soft_wrap_mode_override.is_some() {
18545 self.soft_wrap_mode_override.take();
18546 } else {
18547 let soft_wrap = match self.soft_wrap_mode(cx) {
18548 SoftWrap::GitDiff => return,
18549 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18550 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18551 language_settings::SoftWrap::None
18552 }
18553 };
18554 self.soft_wrap_mode_override = Some(soft_wrap);
18555 }
18556 cx.notify();
18557 }
18558
18559 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18560 let Some(workspace) = self.workspace() else {
18561 return;
18562 };
18563 let fs = workspace.read(cx).app_state().fs.clone();
18564 let current_show = TabBarSettings::get_global(cx).show;
18565 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18566 setting.show = Some(!current_show);
18567 });
18568 }
18569
18570 pub fn toggle_indent_guides(
18571 &mut self,
18572 _: &ToggleIndentGuides,
18573 _: &mut Window,
18574 cx: &mut Context<Self>,
18575 ) {
18576 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18577 self.buffer
18578 .read(cx)
18579 .language_settings(cx)
18580 .indent_guides
18581 .enabled
18582 });
18583 self.show_indent_guides = Some(!currently_enabled);
18584 cx.notify();
18585 }
18586
18587 fn should_show_indent_guides(&self) -> Option<bool> {
18588 self.show_indent_guides
18589 }
18590
18591 pub fn toggle_line_numbers(
18592 &mut self,
18593 _: &ToggleLineNumbers,
18594 _: &mut Window,
18595 cx: &mut Context<Self>,
18596 ) {
18597 let mut editor_settings = EditorSettings::get_global(cx).clone();
18598 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18599 EditorSettings::override_global(editor_settings, cx);
18600 }
18601
18602 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18603 if let Some(show_line_numbers) = self.show_line_numbers {
18604 return show_line_numbers;
18605 }
18606 EditorSettings::get_global(cx).gutter.line_numbers
18607 }
18608
18609 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18610 self.use_relative_line_numbers
18611 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18612 }
18613
18614 pub fn toggle_relative_line_numbers(
18615 &mut self,
18616 _: &ToggleRelativeLineNumbers,
18617 _: &mut Window,
18618 cx: &mut Context<Self>,
18619 ) {
18620 let is_relative = self.should_use_relative_line_numbers(cx);
18621 self.set_relative_line_number(Some(!is_relative), cx)
18622 }
18623
18624 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18625 self.use_relative_line_numbers = is_relative;
18626 cx.notify();
18627 }
18628
18629 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18630 self.show_gutter = show_gutter;
18631 cx.notify();
18632 }
18633
18634 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18635 self.show_scrollbars = ScrollbarAxes {
18636 horizontal: show,
18637 vertical: show,
18638 };
18639 cx.notify();
18640 }
18641
18642 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18643 self.show_scrollbars.vertical = show;
18644 cx.notify();
18645 }
18646
18647 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18648 self.show_scrollbars.horizontal = show;
18649 cx.notify();
18650 }
18651
18652 pub fn set_minimap_visibility(
18653 &mut self,
18654 minimap_visibility: MinimapVisibility,
18655 window: &mut Window,
18656 cx: &mut Context<Self>,
18657 ) {
18658 if self.minimap_visibility != minimap_visibility {
18659 if minimap_visibility.visible() && self.minimap.is_none() {
18660 let minimap_settings = EditorSettings::get_global(cx).minimap;
18661 self.minimap =
18662 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18663 }
18664 self.minimap_visibility = minimap_visibility;
18665 cx.notify();
18666 }
18667 }
18668
18669 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18670 self.set_show_scrollbars(false, cx);
18671 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18672 }
18673
18674 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18675 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18676 }
18677
18678 /// Normally the text in full mode and auto height editors is padded on the
18679 /// left side by roughly half a character width for improved hit testing.
18680 ///
18681 /// Use this method to disable this for cases where this is not wanted (e.g.
18682 /// if you want to align the editor text with some other text above or below)
18683 /// or if you want to add this padding to single-line editors.
18684 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18685 self.offset_content = offset_content;
18686 cx.notify();
18687 }
18688
18689 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18690 self.show_line_numbers = Some(show_line_numbers);
18691 cx.notify();
18692 }
18693
18694 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18695 self.disable_expand_excerpt_buttons = true;
18696 cx.notify();
18697 }
18698
18699 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18700 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18701 cx.notify();
18702 }
18703
18704 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18705 self.show_code_actions = Some(show_code_actions);
18706 cx.notify();
18707 }
18708
18709 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18710 self.show_runnables = Some(show_runnables);
18711 cx.notify();
18712 }
18713
18714 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18715 self.show_breakpoints = Some(show_breakpoints);
18716 cx.notify();
18717 }
18718
18719 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18720 if self.display_map.read(cx).masked != masked {
18721 self.display_map.update(cx, |map, _| map.masked = masked);
18722 }
18723 cx.notify()
18724 }
18725
18726 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18727 self.show_wrap_guides = Some(show_wrap_guides);
18728 cx.notify();
18729 }
18730
18731 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18732 self.show_indent_guides = Some(show_indent_guides);
18733 cx.notify();
18734 }
18735
18736 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18737 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18738 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18739 && let Some(dir) = file.abs_path(cx).parent()
18740 {
18741 return Some(dir.to_owned());
18742 }
18743
18744 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18745 return Some(project_path.path.to_path_buf());
18746 }
18747 }
18748
18749 None
18750 }
18751
18752 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18753 self.active_excerpt(cx)?
18754 .1
18755 .read(cx)
18756 .file()
18757 .and_then(|f| f.as_local())
18758 }
18759
18760 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18761 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18762 let buffer = buffer.read(cx);
18763 if let Some(project_path) = buffer.project_path(cx) {
18764 let project = self.project()?.read(cx);
18765 project.absolute_path(&project_path, cx)
18766 } else {
18767 buffer
18768 .file()
18769 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18770 }
18771 })
18772 }
18773
18774 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18775 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18776 let project_path = buffer.read(cx).project_path(cx)?;
18777 let project = self.project()?.read(cx);
18778 let entry = project.entry_for_path(&project_path, cx)?;
18779 let path = entry.path.to_path_buf();
18780 Some(path)
18781 })
18782 }
18783
18784 pub fn reveal_in_finder(
18785 &mut self,
18786 _: &RevealInFileManager,
18787 _window: &mut Window,
18788 cx: &mut Context<Self>,
18789 ) {
18790 if let Some(target) = self.target_file(cx) {
18791 cx.reveal_path(&target.abs_path(cx));
18792 }
18793 }
18794
18795 pub fn copy_path(
18796 &mut self,
18797 _: &zed_actions::workspace::CopyPath,
18798 _window: &mut Window,
18799 cx: &mut Context<Self>,
18800 ) {
18801 if let Some(path) = self.target_file_abs_path(cx)
18802 && let Some(path) = path.to_str()
18803 {
18804 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18805 }
18806 }
18807
18808 pub fn copy_relative_path(
18809 &mut self,
18810 _: &zed_actions::workspace::CopyRelativePath,
18811 _window: &mut Window,
18812 cx: &mut Context<Self>,
18813 ) {
18814 if let Some(path) = self.target_file_path(cx)
18815 && let Some(path) = path.to_str()
18816 {
18817 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18818 }
18819 }
18820
18821 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18822 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18823 buffer.read(cx).project_path(cx)
18824 } else {
18825 None
18826 }
18827 }
18828
18829 // Returns true if the editor handled a go-to-line request
18830 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18831 maybe!({
18832 let breakpoint_store = self.breakpoint_store.as_ref()?;
18833
18834 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18835 else {
18836 self.clear_row_highlights::<ActiveDebugLine>();
18837 return None;
18838 };
18839
18840 let position = active_stack_frame.position;
18841 let buffer_id = position.buffer_id?;
18842 let snapshot = self
18843 .project
18844 .as_ref()?
18845 .read(cx)
18846 .buffer_for_id(buffer_id, cx)?
18847 .read(cx)
18848 .snapshot();
18849
18850 let mut handled = false;
18851 for (id, ExcerptRange { context, .. }) in
18852 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18853 {
18854 if context.start.cmp(&position, &snapshot).is_ge()
18855 || context.end.cmp(&position, &snapshot).is_lt()
18856 {
18857 continue;
18858 }
18859 let snapshot = self.buffer.read(cx).snapshot(cx);
18860 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18861
18862 handled = true;
18863 self.clear_row_highlights::<ActiveDebugLine>();
18864
18865 self.go_to_line::<ActiveDebugLine>(
18866 multibuffer_anchor,
18867 Some(cx.theme().colors().editor_debugger_active_line_background),
18868 window,
18869 cx,
18870 );
18871
18872 cx.notify();
18873 }
18874
18875 handled.then_some(())
18876 })
18877 .is_some()
18878 }
18879
18880 pub fn copy_file_name_without_extension(
18881 &mut self,
18882 _: &CopyFileNameWithoutExtension,
18883 _: &mut Window,
18884 cx: &mut Context<Self>,
18885 ) {
18886 if let Some(file) = self.target_file(cx)
18887 && let Some(file_stem) = file.path().file_stem()
18888 && let Some(name) = file_stem.to_str()
18889 {
18890 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18891 }
18892 }
18893
18894 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18895 if let Some(file) = self.target_file(cx)
18896 && let Some(file_name) = file.path().file_name()
18897 && let Some(name) = file_name.to_str()
18898 {
18899 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18900 }
18901 }
18902
18903 pub fn toggle_git_blame(
18904 &mut self,
18905 _: &::git::Blame,
18906 window: &mut Window,
18907 cx: &mut Context<Self>,
18908 ) {
18909 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18910
18911 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18912 self.start_git_blame(true, window, cx);
18913 }
18914
18915 cx.notify();
18916 }
18917
18918 pub fn toggle_git_blame_inline(
18919 &mut self,
18920 _: &ToggleGitBlameInline,
18921 window: &mut Window,
18922 cx: &mut Context<Self>,
18923 ) {
18924 self.toggle_git_blame_inline_internal(true, window, cx);
18925 cx.notify();
18926 }
18927
18928 pub fn open_git_blame_commit(
18929 &mut self,
18930 _: &OpenGitBlameCommit,
18931 window: &mut Window,
18932 cx: &mut Context<Self>,
18933 ) {
18934 self.open_git_blame_commit_internal(window, cx);
18935 }
18936
18937 fn open_git_blame_commit_internal(
18938 &mut self,
18939 window: &mut Window,
18940 cx: &mut Context<Self>,
18941 ) -> Option<()> {
18942 let blame = self.blame.as_ref()?;
18943 let snapshot = self.snapshot(window, cx);
18944 let cursor = self.selections.newest::<Point>(cx).head();
18945 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18946 let blame_entry = blame
18947 .update(cx, |blame, cx| {
18948 blame
18949 .blame_for_rows(
18950 &[RowInfo {
18951 buffer_id: Some(buffer.remote_id()),
18952 buffer_row: Some(point.row),
18953 ..Default::default()
18954 }],
18955 cx,
18956 )
18957 .next()
18958 })
18959 .flatten()?;
18960 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18961 let repo = blame.read(cx).repository(cx)?;
18962 let workspace = self.workspace()?.downgrade();
18963 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18964 None
18965 }
18966
18967 pub fn git_blame_inline_enabled(&self) -> bool {
18968 self.git_blame_inline_enabled
18969 }
18970
18971 pub fn toggle_selection_menu(
18972 &mut self,
18973 _: &ToggleSelectionMenu,
18974 _: &mut Window,
18975 cx: &mut Context<Self>,
18976 ) {
18977 self.show_selection_menu = self
18978 .show_selection_menu
18979 .map(|show_selections_menu| !show_selections_menu)
18980 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18981
18982 cx.notify();
18983 }
18984
18985 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18986 self.show_selection_menu
18987 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18988 }
18989
18990 fn start_git_blame(
18991 &mut self,
18992 user_triggered: bool,
18993 window: &mut Window,
18994 cx: &mut Context<Self>,
18995 ) {
18996 if let Some(project) = self.project() {
18997 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18998 return;
18999 };
19000
19001 if buffer.read(cx).file().is_none() {
19002 return;
19003 }
19004
19005 let focused = self.focus_handle(cx).contains_focused(window, cx);
19006
19007 let project = project.clone();
19008 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
19009 self.blame_subscription =
19010 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19011 self.blame = Some(blame);
19012 }
19013 }
19014
19015 fn toggle_git_blame_inline_internal(
19016 &mut self,
19017 user_triggered: bool,
19018 window: &mut Window,
19019 cx: &mut Context<Self>,
19020 ) {
19021 if self.git_blame_inline_enabled {
19022 self.git_blame_inline_enabled = false;
19023 self.show_git_blame_inline = false;
19024 self.show_git_blame_inline_delay_task.take();
19025 } else {
19026 self.git_blame_inline_enabled = true;
19027 self.start_git_blame_inline(user_triggered, window, cx);
19028 }
19029
19030 cx.notify();
19031 }
19032
19033 fn start_git_blame_inline(
19034 &mut self,
19035 user_triggered: bool,
19036 window: &mut Window,
19037 cx: &mut Context<Self>,
19038 ) {
19039 self.start_git_blame(user_triggered, window, cx);
19040
19041 if ProjectSettings::get_global(cx)
19042 .git
19043 .inline_blame_delay()
19044 .is_some()
19045 {
19046 self.start_inline_blame_timer(window, cx);
19047 } else {
19048 self.show_git_blame_inline = true
19049 }
19050 }
19051
19052 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19053 self.blame.as_ref()
19054 }
19055
19056 pub fn show_git_blame_gutter(&self) -> bool {
19057 self.show_git_blame_gutter
19058 }
19059
19060 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19061 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19062 }
19063
19064 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19065 self.show_git_blame_inline
19066 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19067 && !self.newest_selection_head_on_empty_line(cx)
19068 && self.has_blame_entries(cx)
19069 }
19070
19071 fn has_blame_entries(&self, cx: &App) -> bool {
19072 self.blame()
19073 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19074 }
19075
19076 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19077 let cursor_anchor = self.selections.newest_anchor().head();
19078
19079 let snapshot = self.buffer.read(cx).snapshot(cx);
19080 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19081
19082 snapshot.line_len(buffer_row) == 0
19083 }
19084
19085 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19086 let buffer_and_selection = maybe!({
19087 let selection = self.selections.newest::<Point>(cx);
19088 let selection_range = selection.range();
19089
19090 let multi_buffer = self.buffer().read(cx);
19091 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19092 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19093
19094 let (buffer, range, _) = if selection.reversed {
19095 buffer_ranges.first()
19096 } else {
19097 buffer_ranges.last()
19098 }?;
19099
19100 let selection = text::ToPoint::to_point(&range.start, buffer).row
19101 ..text::ToPoint::to_point(&range.end, buffer).row;
19102 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19103 });
19104
19105 let Some((buffer, selection)) = buffer_and_selection else {
19106 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19107 };
19108
19109 let Some(project) = self.project() else {
19110 return Task::ready(Err(anyhow!("editor does not have project")));
19111 };
19112
19113 project.update(cx, |project, cx| {
19114 project.get_permalink_to_line(&buffer, selection, cx)
19115 })
19116 }
19117
19118 pub fn copy_permalink_to_line(
19119 &mut self,
19120 _: &CopyPermalinkToLine,
19121 window: &mut Window,
19122 cx: &mut Context<Self>,
19123 ) {
19124 let permalink_task = self.get_permalink_to_line(cx);
19125 let workspace = self.workspace();
19126
19127 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19128 Ok(permalink) => {
19129 cx.update(|_, cx| {
19130 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19131 })
19132 .ok();
19133 }
19134 Err(err) => {
19135 let message = format!("Failed to copy permalink: {err}");
19136
19137 anyhow::Result::<()>::Err(err).log_err();
19138
19139 if let Some(workspace) = workspace {
19140 workspace
19141 .update_in(cx, |workspace, _, cx| {
19142 struct CopyPermalinkToLine;
19143
19144 workspace.show_toast(
19145 Toast::new(
19146 NotificationId::unique::<CopyPermalinkToLine>(),
19147 message,
19148 ),
19149 cx,
19150 )
19151 })
19152 .ok();
19153 }
19154 }
19155 })
19156 .detach();
19157 }
19158
19159 pub fn copy_file_location(
19160 &mut self,
19161 _: &CopyFileLocation,
19162 _: &mut Window,
19163 cx: &mut Context<Self>,
19164 ) {
19165 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19166 if let Some(file) = self.target_file(cx)
19167 && let Some(path) = file.path().to_str()
19168 {
19169 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19170 }
19171 }
19172
19173 pub fn open_permalink_to_line(
19174 &mut self,
19175 _: &OpenPermalinkToLine,
19176 window: &mut Window,
19177 cx: &mut Context<Self>,
19178 ) {
19179 let permalink_task = self.get_permalink_to_line(cx);
19180 let workspace = self.workspace();
19181
19182 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19183 Ok(permalink) => {
19184 cx.update(|_, cx| {
19185 cx.open_url(permalink.as_ref());
19186 })
19187 .ok();
19188 }
19189 Err(err) => {
19190 let message = format!("Failed to open permalink: {err}");
19191
19192 anyhow::Result::<()>::Err(err).log_err();
19193
19194 if let Some(workspace) = workspace {
19195 workspace
19196 .update(cx, |workspace, cx| {
19197 struct OpenPermalinkToLine;
19198
19199 workspace.show_toast(
19200 Toast::new(
19201 NotificationId::unique::<OpenPermalinkToLine>(),
19202 message,
19203 ),
19204 cx,
19205 )
19206 })
19207 .ok();
19208 }
19209 }
19210 })
19211 .detach();
19212 }
19213
19214 pub fn insert_uuid_v4(
19215 &mut self,
19216 _: &InsertUuidV4,
19217 window: &mut Window,
19218 cx: &mut Context<Self>,
19219 ) {
19220 self.insert_uuid(UuidVersion::V4, window, cx);
19221 }
19222
19223 pub fn insert_uuid_v7(
19224 &mut self,
19225 _: &InsertUuidV7,
19226 window: &mut Window,
19227 cx: &mut Context<Self>,
19228 ) {
19229 self.insert_uuid(UuidVersion::V7, window, cx);
19230 }
19231
19232 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19233 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19234 self.transact(window, cx, |this, window, cx| {
19235 let edits = this
19236 .selections
19237 .all::<Point>(cx)
19238 .into_iter()
19239 .map(|selection| {
19240 let uuid = match version {
19241 UuidVersion::V4 => uuid::Uuid::new_v4(),
19242 UuidVersion::V7 => uuid::Uuid::now_v7(),
19243 };
19244
19245 (selection.range(), uuid.to_string())
19246 });
19247 this.edit(edits, cx);
19248 this.refresh_edit_prediction(true, false, window, cx);
19249 });
19250 }
19251
19252 pub fn open_selections_in_multibuffer(
19253 &mut self,
19254 _: &OpenSelectionsInMultibuffer,
19255 window: &mut Window,
19256 cx: &mut Context<Self>,
19257 ) {
19258 let multibuffer = self.buffer.read(cx);
19259
19260 let Some(buffer) = multibuffer.as_singleton() else {
19261 return;
19262 };
19263
19264 let Some(workspace) = self.workspace() else {
19265 return;
19266 };
19267
19268 let title = multibuffer.title(cx).to_string();
19269
19270 let locations = self
19271 .selections
19272 .all_anchors(cx)
19273 .iter()
19274 .map(|selection| Location {
19275 buffer: buffer.clone(),
19276 range: selection.start.text_anchor..selection.end.text_anchor,
19277 })
19278 .collect::<Vec<_>>();
19279
19280 cx.spawn_in(window, async move |_, cx| {
19281 workspace.update_in(cx, |workspace, window, cx| {
19282 Self::open_locations_in_multibuffer(
19283 workspace,
19284 locations,
19285 format!("Selections for '{title}'"),
19286 false,
19287 MultibufferSelectionMode::All,
19288 window,
19289 cx,
19290 );
19291 })
19292 })
19293 .detach();
19294 }
19295
19296 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19297 /// last highlight added will be used.
19298 ///
19299 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19300 pub fn highlight_rows<T: 'static>(
19301 &mut self,
19302 range: Range<Anchor>,
19303 color: Hsla,
19304 options: RowHighlightOptions,
19305 cx: &mut Context<Self>,
19306 ) {
19307 let snapshot = self.buffer().read(cx).snapshot(cx);
19308 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19309 let ix = row_highlights.binary_search_by(|highlight| {
19310 Ordering::Equal
19311 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19312 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19313 });
19314
19315 if let Err(mut ix) = ix {
19316 let index = post_inc(&mut self.highlight_order);
19317
19318 // If this range intersects with the preceding highlight, then merge it with
19319 // the preceding highlight. Otherwise insert a new highlight.
19320 let mut merged = false;
19321 if ix > 0 {
19322 let prev_highlight = &mut row_highlights[ix - 1];
19323 if prev_highlight
19324 .range
19325 .end
19326 .cmp(&range.start, &snapshot)
19327 .is_ge()
19328 {
19329 ix -= 1;
19330 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19331 prev_highlight.range.end = range.end;
19332 }
19333 merged = true;
19334 prev_highlight.index = index;
19335 prev_highlight.color = color;
19336 prev_highlight.options = options;
19337 }
19338 }
19339
19340 if !merged {
19341 row_highlights.insert(
19342 ix,
19343 RowHighlight {
19344 range,
19345 index,
19346 color,
19347 options,
19348 type_id: TypeId::of::<T>(),
19349 },
19350 );
19351 }
19352
19353 // If any of the following highlights intersect with this one, merge them.
19354 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19355 let highlight = &row_highlights[ix];
19356 if next_highlight
19357 .range
19358 .start
19359 .cmp(&highlight.range.end, &snapshot)
19360 .is_le()
19361 {
19362 if next_highlight
19363 .range
19364 .end
19365 .cmp(&highlight.range.end, &snapshot)
19366 .is_gt()
19367 {
19368 row_highlights[ix].range.end = next_highlight.range.end;
19369 }
19370 row_highlights.remove(ix + 1);
19371 } else {
19372 break;
19373 }
19374 }
19375 }
19376 }
19377
19378 /// Remove any highlighted row ranges of the given type that intersect the
19379 /// given ranges.
19380 pub fn remove_highlighted_rows<T: 'static>(
19381 &mut self,
19382 ranges_to_remove: Vec<Range<Anchor>>,
19383 cx: &mut Context<Self>,
19384 ) {
19385 let snapshot = self.buffer().read(cx).snapshot(cx);
19386 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19387 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19388 row_highlights.retain(|highlight| {
19389 while let Some(range_to_remove) = ranges_to_remove.peek() {
19390 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19391 Ordering::Less | Ordering::Equal => {
19392 ranges_to_remove.next();
19393 }
19394 Ordering::Greater => {
19395 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19396 Ordering::Less | Ordering::Equal => {
19397 return false;
19398 }
19399 Ordering::Greater => break,
19400 }
19401 }
19402 }
19403 }
19404
19405 true
19406 })
19407 }
19408
19409 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19410 pub fn clear_row_highlights<T: 'static>(&mut self) {
19411 self.highlighted_rows.remove(&TypeId::of::<T>());
19412 }
19413
19414 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19415 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19416 self.highlighted_rows
19417 .get(&TypeId::of::<T>())
19418 .map_or(&[] as &[_], |vec| vec.as_slice())
19419 .iter()
19420 .map(|highlight| (highlight.range.clone(), highlight.color))
19421 }
19422
19423 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19424 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19425 /// Allows to ignore certain kinds of highlights.
19426 pub fn highlighted_display_rows(
19427 &self,
19428 window: &mut Window,
19429 cx: &mut App,
19430 ) -> BTreeMap<DisplayRow, LineHighlight> {
19431 let snapshot = self.snapshot(window, cx);
19432 let mut used_highlight_orders = HashMap::default();
19433 self.highlighted_rows
19434 .iter()
19435 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19436 .fold(
19437 BTreeMap::<DisplayRow, LineHighlight>::new(),
19438 |mut unique_rows, highlight| {
19439 let start = highlight.range.start.to_display_point(&snapshot);
19440 let end = highlight.range.end.to_display_point(&snapshot);
19441 let start_row = start.row().0;
19442 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19443 && end.column() == 0
19444 {
19445 end.row().0.saturating_sub(1)
19446 } else {
19447 end.row().0
19448 };
19449 for row in start_row..=end_row {
19450 let used_index =
19451 used_highlight_orders.entry(row).or_insert(highlight.index);
19452 if highlight.index >= *used_index {
19453 *used_index = highlight.index;
19454 unique_rows.insert(
19455 DisplayRow(row),
19456 LineHighlight {
19457 include_gutter: highlight.options.include_gutter,
19458 border: None,
19459 background: highlight.color.into(),
19460 type_id: Some(highlight.type_id),
19461 },
19462 );
19463 }
19464 }
19465 unique_rows
19466 },
19467 )
19468 }
19469
19470 pub fn highlighted_display_row_for_autoscroll(
19471 &self,
19472 snapshot: &DisplaySnapshot,
19473 ) -> Option<DisplayRow> {
19474 self.highlighted_rows
19475 .values()
19476 .flat_map(|highlighted_rows| highlighted_rows.iter())
19477 .filter_map(|highlight| {
19478 if highlight.options.autoscroll {
19479 Some(highlight.range.start.to_display_point(snapshot).row())
19480 } else {
19481 None
19482 }
19483 })
19484 .min()
19485 }
19486
19487 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19488 self.highlight_background::<SearchWithinRange>(
19489 ranges,
19490 |colors| colors.colors().editor_document_highlight_read_background,
19491 cx,
19492 )
19493 }
19494
19495 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19496 self.breadcrumb_header = Some(new_header);
19497 }
19498
19499 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19500 self.clear_background_highlights::<SearchWithinRange>(cx);
19501 }
19502
19503 pub fn highlight_background<T: 'static>(
19504 &mut self,
19505 ranges: &[Range<Anchor>],
19506 color_fetcher: fn(&Theme) -> Hsla,
19507 cx: &mut Context<Self>,
19508 ) {
19509 self.background_highlights.insert(
19510 HighlightKey::Type(TypeId::of::<T>()),
19511 (color_fetcher, Arc::from(ranges)),
19512 );
19513 self.scrollbar_marker_state.dirty = true;
19514 cx.notify();
19515 }
19516
19517 pub fn highlight_background_key<T: 'static>(
19518 &mut self,
19519 key: usize,
19520 ranges: &[Range<Anchor>],
19521 color_fetcher: fn(&Theme) -> Hsla,
19522 cx: &mut Context<Self>,
19523 ) {
19524 self.background_highlights.insert(
19525 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19526 (color_fetcher, Arc::from(ranges)),
19527 );
19528 self.scrollbar_marker_state.dirty = true;
19529 cx.notify();
19530 }
19531
19532 pub fn clear_background_highlights<T: 'static>(
19533 &mut self,
19534 cx: &mut Context<Self>,
19535 ) -> Option<BackgroundHighlight> {
19536 let text_highlights = self
19537 .background_highlights
19538 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19539 if !text_highlights.1.is_empty() {
19540 self.scrollbar_marker_state.dirty = true;
19541 cx.notify();
19542 }
19543 Some(text_highlights)
19544 }
19545
19546 pub fn highlight_gutter<T: 'static>(
19547 &mut self,
19548 ranges: impl Into<Vec<Range<Anchor>>>,
19549 color_fetcher: fn(&App) -> Hsla,
19550 cx: &mut Context<Self>,
19551 ) {
19552 self.gutter_highlights
19553 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19554 cx.notify();
19555 }
19556
19557 pub fn clear_gutter_highlights<T: 'static>(
19558 &mut self,
19559 cx: &mut Context<Self>,
19560 ) -> Option<GutterHighlight> {
19561 cx.notify();
19562 self.gutter_highlights.remove(&TypeId::of::<T>())
19563 }
19564
19565 pub fn insert_gutter_highlight<T: 'static>(
19566 &mut self,
19567 range: Range<Anchor>,
19568 color_fetcher: fn(&App) -> Hsla,
19569 cx: &mut Context<Self>,
19570 ) {
19571 let snapshot = self.buffer().read(cx).snapshot(cx);
19572 let mut highlights = self
19573 .gutter_highlights
19574 .remove(&TypeId::of::<T>())
19575 .map(|(_, highlights)| highlights)
19576 .unwrap_or_default();
19577 let ix = highlights.binary_search_by(|highlight| {
19578 Ordering::Equal
19579 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19580 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19581 });
19582 if let Err(ix) = ix {
19583 highlights.insert(ix, range);
19584 }
19585 self.gutter_highlights
19586 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19587 }
19588
19589 pub fn remove_gutter_highlights<T: 'static>(
19590 &mut self,
19591 ranges_to_remove: Vec<Range<Anchor>>,
19592 cx: &mut Context<Self>,
19593 ) {
19594 let snapshot = self.buffer().read(cx).snapshot(cx);
19595 let Some((color_fetcher, mut gutter_highlights)) =
19596 self.gutter_highlights.remove(&TypeId::of::<T>())
19597 else {
19598 return;
19599 };
19600 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19601 gutter_highlights.retain(|highlight| {
19602 while let Some(range_to_remove) = ranges_to_remove.peek() {
19603 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19604 Ordering::Less | Ordering::Equal => {
19605 ranges_to_remove.next();
19606 }
19607 Ordering::Greater => {
19608 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19609 Ordering::Less | Ordering::Equal => {
19610 return false;
19611 }
19612 Ordering::Greater => break,
19613 }
19614 }
19615 }
19616 }
19617
19618 true
19619 });
19620 self.gutter_highlights
19621 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19622 }
19623
19624 #[cfg(feature = "test-support")]
19625 pub fn all_text_highlights(
19626 &self,
19627 window: &mut Window,
19628 cx: &mut Context<Self>,
19629 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19630 let snapshot = self.snapshot(window, cx);
19631 self.display_map.update(cx, |display_map, _| {
19632 display_map
19633 .all_text_highlights()
19634 .map(|highlight| {
19635 let (style, ranges) = highlight.as_ref();
19636 (
19637 *style,
19638 ranges
19639 .iter()
19640 .map(|range| range.clone().to_display_points(&snapshot))
19641 .collect(),
19642 )
19643 })
19644 .collect()
19645 })
19646 }
19647
19648 #[cfg(feature = "test-support")]
19649 pub fn all_text_background_highlights(
19650 &self,
19651 window: &mut Window,
19652 cx: &mut Context<Self>,
19653 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19654 let snapshot = self.snapshot(window, cx);
19655 let buffer = &snapshot.buffer_snapshot;
19656 let start = buffer.anchor_before(0);
19657 let end = buffer.anchor_after(buffer.len());
19658 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19659 }
19660
19661 #[cfg(feature = "test-support")]
19662 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19663 let snapshot = self.buffer().read(cx).snapshot(cx);
19664
19665 let highlights = self
19666 .background_highlights
19667 .get(&HighlightKey::Type(TypeId::of::<
19668 items::BufferSearchHighlights,
19669 >()));
19670
19671 if let Some((_color, ranges)) = highlights {
19672 ranges
19673 .iter()
19674 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19675 .collect_vec()
19676 } else {
19677 vec![]
19678 }
19679 }
19680
19681 fn document_highlights_for_position<'a>(
19682 &'a self,
19683 position: Anchor,
19684 buffer: &'a MultiBufferSnapshot,
19685 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19686 let read_highlights = self
19687 .background_highlights
19688 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19689 .map(|h| &h.1);
19690 let write_highlights = self
19691 .background_highlights
19692 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19693 .map(|h| &h.1);
19694 let left_position = position.bias_left(buffer);
19695 let right_position = position.bias_right(buffer);
19696 read_highlights
19697 .into_iter()
19698 .chain(write_highlights)
19699 .flat_map(move |ranges| {
19700 let start_ix = match ranges.binary_search_by(|probe| {
19701 let cmp = probe.end.cmp(&left_position, buffer);
19702 if cmp.is_ge() {
19703 Ordering::Greater
19704 } else {
19705 Ordering::Less
19706 }
19707 }) {
19708 Ok(i) | Err(i) => i,
19709 };
19710
19711 ranges[start_ix..]
19712 .iter()
19713 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19714 })
19715 }
19716
19717 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19718 self.background_highlights
19719 .get(&HighlightKey::Type(TypeId::of::<T>()))
19720 .is_some_and(|(_, highlights)| !highlights.is_empty())
19721 }
19722
19723 pub fn background_highlights_in_range(
19724 &self,
19725 search_range: Range<Anchor>,
19726 display_snapshot: &DisplaySnapshot,
19727 theme: &Theme,
19728 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19729 let mut results = Vec::new();
19730 for (color_fetcher, ranges) in self.background_highlights.values() {
19731 let color = color_fetcher(theme);
19732 let start_ix = match ranges.binary_search_by(|probe| {
19733 let cmp = probe
19734 .end
19735 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19736 if cmp.is_gt() {
19737 Ordering::Greater
19738 } else {
19739 Ordering::Less
19740 }
19741 }) {
19742 Ok(i) | Err(i) => i,
19743 };
19744 for range in &ranges[start_ix..] {
19745 if range
19746 .start
19747 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19748 .is_ge()
19749 {
19750 break;
19751 }
19752
19753 let start = range.start.to_display_point(display_snapshot);
19754 let end = range.end.to_display_point(display_snapshot);
19755 results.push((start..end, color))
19756 }
19757 }
19758 results
19759 }
19760
19761 pub fn background_highlight_row_ranges<T: 'static>(
19762 &self,
19763 search_range: Range<Anchor>,
19764 display_snapshot: &DisplaySnapshot,
19765 count: usize,
19766 ) -> Vec<RangeInclusive<DisplayPoint>> {
19767 let mut results = Vec::new();
19768 let Some((_, ranges)) = self
19769 .background_highlights
19770 .get(&HighlightKey::Type(TypeId::of::<T>()))
19771 else {
19772 return vec![];
19773 };
19774
19775 let start_ix = match ranges.binary_search_by(|probe| {
19776 let cmp = probe
19777 .end
19778 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19779 if cmp.is_gt() {
19780 Ordering::Greater
19781 } else {
19782 Ordering::Less
19783 }
19784 }) {
19785 Ok(i) | Err(i) => i,
19786 };
19787 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19788 if let (Some(start_display), Some(end_display)) = (start, end) {
19789 results.push(
19790 start_display.to_display_point(display_snapshot)
19791 ..=end_display.to_display_point(display_snapshot),
19792 );
19793 }
19794 };
19795 let mut start_row: Option<Point> = None;
19796 let mut end_row: Option<Point> = None;
19797 if ranges.len() > count {
19798 return Vec::new();
19799 }
19800 for range in &ranges[start_ix..] {
19801 if range
19802 .start
19803 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19804 .is_ge()
19805 {
19806 break;
19807 }
19808 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19809 if let Some(current_row) = &end_row
19810 && end.row == current_row.row
19811 {
19812 continue;
19813 }
19814 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19815 if start_row.is_none() {
19816 assert_eq!(end_row, None);
19817 start_row = Some(start);
19818 end_row = Some(end);
19819 continue;
19820 }
19821 if let Some(current_end) = end_row.as_mut() {
19822 if start.row > current_end.row + 1 {
19823 push_region(start_row, end_row);
19824 start_row = Some(start);
19825 end_row = Some(end);
19826 } else {
19827 // Merge two hunks.
19828 *current_end = end;
19829 }
19830 } else {
19831 unreachable!();
19832 }
19833 }
19834 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19835 push_region(start_row, end_row);
19836 results
19837 }
19838
19839 pub fn gutter_highlights_in_range(
19840 &self,
19841 search_range: Range<Anchor>,
19842 display_snapshot: &DisplaySnapshot,
19843 cx: &App,
19844 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19845 let mut results = Vec::new();
19846 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19847 let color = color_fetcher(cx);
19848 let start_ix = match ranges.binary_search_by(|probe| {
19849 let cmp = probe
19850 .end
19851 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19852 if cmp.is_gt() {
19853 Ordering::Greater
19854 } else {
19855 Ordering::Less
19856 }
19857 }) {
19858 Ok(i) | Err(i) => i,
19859 };
19860 for range in &ranges[start_ix..] {
19861 if range
19862 .start
19863 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19864 .is_ge()
19865 {
19866 break;
19867 }
19868
19869 let start = range.start.to_display_point(display_snapshot);
19870 let end = range.end.to_display_point(display_snapshot);
19871 results.push((start..end, color))
19872 }
19873 }
19874 results
19875 }
19876
19877 /// Get the text ranges corresponding to the redaction query
19878 pub fn redacted_ranges(
19879 &self,
19880 search_range: Range<Anchor>,
19881 display_snapshot: &DisplaySnapshot,
19882 cx: &App,
19883 ) -> Vec<Range<DisplayPoint>> {
19884 display_snapshot
19885 .buffer_snapshot
19886 .redacted_ranges(search_range, |file| {
19887 if let Some(file) = file {
19888 file.is_private()
19889 && EditorSettings::get(
19890 Some(SettingsLocation {
19891 worktree_id: file.worktree_id(cx),
19892 path: file.path().as_ref(),
19893 }),
19894 cx,
19895 )
19896 .redact_private_values
19897 } else {
19898 false
19899 }
19900 })
19901 .map(|range| {
19902 range.start.to_display_point(display_snapshot)
19903 ..range.end.to_display_point(display_snapshot)
19904 })
19905 .collect()
19906 }
19907
19908 pub fn highlight_text_key<T: 'static>(
19909 &mut self,
19910 key: usize,
19911 ranges: Vec<Range<Anchor>>,
19912 style: HighlightStyle,
19913 cx: &mut Context<Self>,
19914 ) {
19915 self.display_map.update(cx, |map, _| {
19916 map.highlight_text(
19917 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19918 ranges,
19919 style,
19920 );
19921 });
19922 cx.notify();
19923 }
19924
19925 pub fn highlight_text<T: 'static>(
19926 &mut self,
19927 ranges: Vec<Range<Anchor>>,
19928 style: HighlightStyle,
19929 cx: &mut Context<Self>,
19930 ) {
19931 self.display_map.update(cx, |map, _| {
19932 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19933 });
19934 cx.notify();
19935 }
19936
19937 pub(crate) fn highlight_inlays<T: 'static>(
19938 &mut self,
19939 highlights: Vec<InlayHighlight>,
19940 style: HighlightStyle,
19941 cx: &mut Context<Self>,
19942 ) {
19943 self.display_map.update(cx, |map, _| {
19944 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19945 });
19946 cx.notify();
19947 }
19948
19949 pub fn text_highlights<'a, T: 'static>(
19950 &'a self,
19951 cx: &'a App,
19952 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19953 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19954 }
19955
19956 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19957 let cleared = self
19958 .display_map
19959 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19960 if cleared {
19961 cx.notify();
19962 }
19963 }
19964
19965 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19966 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19967 && self.focus_handle.is_focused(window)
19968 }
19969
19970 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19971 self.show_cursor_when_unfocused = is_enabled;
19972 cx.notify();
19973 }
19974
19975 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19976 cx.notify();
19977 }
19978
19979 fn on_debug_session_event(
19980 &mut self,
19981 _session: Entity<Session>,
19982 event: &SessionEvent,
19983 cx: &mut Context<Self>,
19984 ) {
19985 if let SessionEvent::InvalidateInlineValue = event {
19986 self.refresh_inline_values(cx);
19987 }
19988 }
19989
19990 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19991 let Some(project) = self.project.clone() else {
19992 return;
19993 };
19994
19995 if !self.inline_value_cache.enabled {
19996 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19997 self.splice_inlays(&inlays, Vec::new(), cx);
19998 return;
19999 }
20000
20001 let current_execution_position = self
20002 .highlighted_rows
20003 .get(&TypeId::of::<ActiveDebugLine>())
20004 .and_then(|lines| lines.last().map(|line| line.range.end));
20005
20006 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20007 let inline_values = editor
20008 .update(cx, |editor, cx| {
20009 let Some(current_execution_position) = current_execution_position else {
20010 return Some(Task::ready(Ok(Vec::new())));
20011 };
20012
20013 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20014 let snapshot = buffer.snapshot(cx);
20015
20016 let excerpt = snapshot.excerpt_containing(
20017 current_execution_position..current_execution_position,
20018 )?;
20019
20020 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20021 })?;
20022
20023 let range =
20024 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20025
20026 project.inline_values(buffer, range, cx)
20027 })
20028 .ok()
20029 .flatten()?
20030 .await
20031 .context("refreshing debugger inlays")
20032 .log_err()?;
20033
20034 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20035
20036 for (buffer_id, inline_value) in inline_values
20037 .into_iter()
20038 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20039 {
20040 buffer_inline_values
20041 .entry(buffer_id)
20042 .or_default()
20043 .push(inline_value);
20044 }
20045
20046 editor
20047 .update(cx, |editor, cx| {
20048 let snapshot = editor.buffer.read(cx).snapshot(cx);
20049 let mut new_inlays = Vec::default();
20050
20051 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20052 let buffer_id = buffer_snapshot.remote_id();
20053 buffer_inline_values
20054 .get(&buffer_id)
20055 .into_iter()
20056 .flatten()
20057 .for_each(|hint| {
20058 let inlay = Inlay::debugger(
20059 post_inc(&mut editor.next_inlay_id),
20060 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20061 hint.text(),
20062 );
20063 if !inlay.text.chars().contains(&'\n') {
20064 new_inlays.push(inlay);
20065 }
20066 });
20067 }
20068
20069 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20070 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20071
20072 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20073 })
20074 .ok()?;
20075 Some(())
20076 });
20077 }
20078
20079 fn on_buffer_event(
20080 &mut self,
20081 multibuffer: &Entity<MultiBuffer>,
20082 event: &multi_buffer::Event,
20083 window: &mut Window,
20084 cx: &mut Context<Self>,
20085 ) {
20086 match event {
20087 multi_buffer::Event::Edited {
20088 singleton_buffer_edited,
20089 edited_buffer,
20090 } => {
20091 self.scrollbar_marker_state.dirty = true;
20092 self.active_indent_guides_state.dirty = true;
20093 self.refresh_active_diagnostics(cx);
20094 self.refresh_code_actions(window, cx);
20095 self.refresh_selected_text_highlights(true, window, cx);
20096 self.refresh_single_line_folds(window, cx);
20097 refresh_matching_bracket_highlights(self, window, cx);
20098 if self.has_active_edit_prediction() {
20099 self.update_visible_edit_prediction(window, cx);
20100 }
20101 if let Some(project) = self.project.as_ref()
20102 && let Some(edited_buffer) = edited_buffer
20103 {
20104 project.update(cx, |project, cx| {
20105 self.registered_buffers
20106 .entry(edited_buffer.read(cx).remote_id())
20107 .or_insert_with(|| {
20108 project.register_buffer_with_language_servers(edited_buffer, cx)
20109 });
20110 });
20111 }
20112 cx.emit(EditorEvent::BufferEdited);
20113 cx.emit(SearchEvent::MatchesInvalidated);
20114
20115 if let Some(buffer) = edited_buffer {
20116 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20117 }
20118
20119 if *singleton_buffer_edited {
20120 if let Some(buffer) = edited_buffer
20121 && buffer.read(cx).file().is_none()
20122 {
20123 cx.emit(EditorEvent::TitleChanged);
20124 }
20125 if let Some(project) = &self.project {
20126 #[allow(clippy::mutable_key_type)]
20127 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20128 multibuffer
20129 .all_buffers()
20130 .into_iter()
20131 .filter_map(|buffer| {
20132 buffer.update(cx, |buffer, cx| {
20133 let language = buffer.language()?;
20134 let should_discard = project.update(cx, |project, cx| {
20135 project.is_local()
20136 && !project.has_language_servers_for(buffer, cx)
20137 });
20138 should_discard.not().then_some(language.clone())
20139 })
20140 })
20141 .collect::<HashSet<_>>()
20142 });
20143 if !languages_affected.is_empty() {
20144 self.refresh_inlay_hints(
20145 InlayHintRefreshReason::BufferEdited(languages_affected),
20146 cx,
20147 );
20148 }
20149 }
20150 }
20151
20152 let Some(project) = &self.project else { return };
20153 let (telemetry, is_via_ssh) = {
20154 let project = project.read(cx);
20155 let telemetry = project.client().telemetry().clone();
20156 let is_via_ssh = project.is_via_remote_server();
20157 (telemetry, is_via_ssh)
20158 };
20159 refresh_linked_ranges(self, window, cx);
20160 telemetry.log_edit_event("editor", is_via_ssh);
20161 }
20162 multi_buffer::Event::ExcerptsAdded {
20163 buffer,
20164 predecessor,
20165 excerpts,
20166 } => {
20167 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20168 let buffer_id = buffer.read(cx).remote_id();
20169 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20170 && let Some(project) = &self.project
20171 {
20172 update_uncommitted_diff_for_buffer(
20173 cx.entity(),
20174 project,
20175 [buffer.clone()],
20176 self.buffer.clone(),
20177 cx,
20178 )
20179 .detach();
20180 }
20181 self.update_lsp_data(false, Some(buffer_id), window, cx);
20182 cx.emit(EditorEvent::ExcerptsAdded {
20183 buffer: buffer.clone(),
20184 predecessor: *predecessor,
20185 excerpts: excerpts.clone(),
20186 });
20187 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20188 }
20189 multi_buffer::Event::ExcerptsRemoved {
20190 ids,
20191 removed_buffer_ids,
20192 } => {
20193 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20194 let buffer = self.buffer.read(cx);
20195 self.registered_buffers
20196 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20197 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20198 cx.emit(EditorEvent::ExcerptsRemoved {
20199 ids: ids.clone(),
20200 removed_buffer_ids: removed_buffer_ids.clone(),
20201 });
20202 }
20203 multi_buffer::Event::ExcerptsEdited {
20204 excerpt_ids,
20205 buffer_ids,
20206 } => {
20207 self.display_map.update(cx, |map, cx| {
20208 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20209 });
20210 cx.emit(EditorEvent::ExcerptsEdited {
20211 ids: excerpt_ids.clone(),
20212 });
20213 }
20214 multi_buffer::Event::ExcerptsExpanded { ids } => {
20215 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20216 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20217 }
20218 multi_buffer::Event::Reparsed(buffer_id) => {
20219 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20220 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20221
20222 cx.emit(EditorEvent::Reparsed(*buffer_id));
20223 }
20224 multi_buffer::Event::DiffHunksToggled => {
20225 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20226 }
20227 multi_buffer::Event::LanguageChanged(buffer_id) => {
20228 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20229 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20230 cx.emit(EditorEvent::Reparsed(*buffer_id));
20231 cx.notify();
20232 }
20233 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20234 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20235 multi_buffer::Event::FileHandleChanged
20236 | multi_buffer::Event::Reloaded
20237 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20238 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20239 multi_buffer::Event::DiagnosticsUpdated => {
20240 self.update_diagnostics_state(window, cx);
20241 }
20242 _ => {}
20243 };
20244 }
20245
20246 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20247 if !self.diagnostics_enabled() {
20248 return;
20249 }
20250 self.refresh_active_diagnostics(cx);
20251 self.refresh_inline_diagnostics(true, window, cx);
20252 self.scrollbar_marker_state.dirty = true;
20253 cx.notify();
20254 }
20255
20256 pub fn start_temporary_diff_override(&mut self) {
20257 self.load_diff_task.take();
20258 self.temporary_diff_override = true;
20259 }
20260
20261 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20262 self.temporary_diff_override = false;
20263 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20264 self.buffer.update(cx, |buffer, cx| {
20265 buffer.set_all_diff_hunks_collapsed(cx);
20266 });
20267
20268 if let Some(project) = self.project.clone() {
20269 self.load_diff_task = Some(
20270 update_uncommitted_diff_for_buffer(
20271 cx.entity(),
20272 &project,
20273 self.buffer.read(cx).all_buffers(),
20274 self.buffer.clone(),
20275 cx,
20276 )
20277 .shared(),
20278 );
20279 }
20280 }
20281
20282 fn on_display_map_changed(
20283 &mut self,
20284 _: Entity<DisplayMap>,
20285 _: &mut Window,
20286 cx: &mut Context<Self>,
20287 ) {
20288 cx.notify();
20289 }
20290
20291 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20292 if self.diagnostics_enabled() {
20293 let new_severity = EditorSettings::get_global(cx)
20294 .diagnostics_max_severity
20295 .unwrap_or(DiagnosticSeverity::Hint);
20296 self.set_max_diagnostics_severity(new_severity, cx);
20297 }
20298 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20299 self.update_edit_prediction_settings(cx);
20300 self.refresh_edit_prediction(true, false, window, cx);
20301 self.refresh_inline_values(cx);
20302 self.refresh_inlay_hints(
20303 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20304 self.selections.newest_anchor().head(),
20305 &self.buffer.read(cx).snapshot(cx),
20306 cx,
20307 )),
20308 cx,
20309 );
20310
20311 let old_cursor_shape = self.cursor_shape;
20312 let old_show_breadcrumbs = self.show_breadcrumbs;
20313
20314 {
20315 let editor_settings = EditorSettings::get_global(cx);
20316 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20317 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20318 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20319 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20320 }
20321
20322 if old_cursor_shape != self.cursor_shape {
20323 cx.emit(EditorEvent::CursorShapeChanged);
20324 }
20325
20326 if old_show_breadcrumbs != self.show_breadcrumbs {
20327 cx.emit(EditorEvent::BreadcrumbsChanged);
20328 }
20329
20330 let project_settings = ProjectSettings::get_global(cx);
20331 self.serialize_dirty_buffers =
20332 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20333
20334 if self.mode.is_full() {
20335 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20336 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20337 if self.show_inline_diagnostics != show_inline_diagnostics {
20338 self.show_inline_diagnostics = show_inline_diagnostics;
20339 self.refresh_inline_diagnostics(false, window, cx);
20340 }
20341
20342 if self.git_blame_inline_enabled != inline_blame_enabled {
20343 self.toggle_git_blame_inline_internal(false, window, cx);
20344 }
20345
20346 let minimap_settings = EditorSettings::get_global(cx).minimap;
20347 if self.minimap_visibility != MinimapVisibility::Disabled {
20348 if self.minimap_visibility.settings_visibility()
20349 != minimap_settings.minimap_enabled()
20350 {
20351 self.set_minimap_visibility(
20352 MinimapVisibility::for_mode(self.mode(), cx),
20353 window,
20354 cx,
20355 );
20356 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20357 minimap_entity.update(cx, |minimap_editor, cx| {
20358 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20359 })
20360 }
20361 }
20362 }
20363
20364 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20365 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20366 }) {
20367 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20368 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20369 }
20370 self.refresh_colors(false, None, window, cx);
20371 }
20372
20373 cx.notify();
20374 }
20375
20376 pub fn set_searchable(&mut self, searchable: bool) {
20377 self.searchable = searchable;
20378 }
20379
20380 pub fn searchable(&self) -> bool {
20381 self.searchable
20382 }
20383
20384 fn open_proposed_changes_editor(
20385 &mut self,
20386 _: &OpenProposedChangesEditor,
20387 window: &mut Window,
20388 cx: &mut Context<Self>,
20389 ) {
20390 let Some(workspace) = self.workspace() else {
20391 cx.propagate();
20392 return;
20393 };
20394
20395 let selections = self.selections.all::<usize>(cx);
20396 let multi_buffer = self.buffer.read(cx);
20397 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20398 let mut new_selections_by_buffer = HashMap::default();
20399 for selection in selections {
20400 for (buffer, range, _) in
20401 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20402 {
20403 let mut range = range.to_point(buffer);
20404 range.start.column = 0;
20405 range.end.column = buffer.line_len(range.end.row);
20406 new_selections_by_buffer
20407 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20408 .or_insert(Vec::new())
20409 .push(range)
20410 }
20411 }
20412
20413 let proposed_changes_buffers = new_selections_by_buffer
20414 .into_iter()
20415 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20416 .collect::<Vec<_>>();
20417 let proposed_changes_editor = cx.new(|cx| {
20418 ProposedChangesEditor::new(
20419 "Proposed changes",
20420 proposed_changes_buffers,
20421 self.project.clone(),
20422 window,
20423 cx,
20424 )
20425 });
20426
20427 window.defer(cx, move |window, cx| {
20428 workspace.update(cx, |workspace, cx| {
20429 workspace.active_pane().update(cx, |pane, cx| {
20430 pane.add_item(
20431 Box::new(proposed_changes_editor),
20432 true,
20433 true,
20434 None,
20435 window,
20436 cx,
20437 );
20438 });
20439 });
20440 });
20441 }
20442
20443 pub fn open_excerpts_in_split(
20444 &mut self,
20445 _: &OpenExcerptsSplit,
20446 window: &mut Window,
20447 cx: &mut Context<Self>,
20448 ) {
20449 self.open_excerpts_common(None, true, window, cx)
20450 }
20451
20452 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20453 self.open_excerpts_common(None, false, window, cx)
20454 }
20455
20456 fn open_excerpts_common(
20457 &mut self,
20458 jump_data: Option<JumpData>,
20459 split: bool,
20460 window: &mut Window,
20461 cx: &mut Context<Self>,
20462 ) {
20463 let Some(workspace) = self.workspace() else {
20464 cx.propagate();
20465 return;
20466 };
20467
20468 if self.buffer.read(cx).is_singleton() {
20469 cx.propagate();
20470 return;
20471 }
20472
20473 let mut new_selections_by_buffer = HashMap::default();
20474 match &jump_data {
20475 Some(JumpData::MultiBufferPoint {
20476 excerpt_id,
20477 position,
20478 anchor,
20479 line_offset_from_top,
20480 }) => {
20481 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20482 if let Some(buffer) = multi_buffer_snapshot
20483 .buffer_id_for_excerpt(*excerpt_id)
20484 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20485 {
20486 let buffer_snapshot = buffer.read(cx).snapshot();
20487 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20488 language::ToPoint::to_point(anchor, &buffer_snapshot)
20489 } else {
20490 buffer_snapshot.clip_point(*position, Bias::Left)
20491 };
20492 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20493 new_selections_by_buffer.insert(
20494 buffer,
20495 (
20496 vec![jump_to_offset..jump_to_offset],
20497 Some(*line_offset_from_top),
20498 ),
20499 );
20500 }
20501 }
20502 Some(JumpData::MultiBufferRow {
20503 row,
20504 line_offset_from_top,
20505 }) => {
20506 let point = MultiBufferPoint::new(row.0, 0);
20507 if let Some((buffer, buffer_point, _)) =
20508 self.buffer.read(cx).point_to_buffer_point(point, cx)
20509 {
20510 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20511 new_selections_by_buffer
20512 .entry(buffer)
20513 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20514 .0
20515 .push(buffer_offset..buffer_offset)
20516 }
20517 }
20518 None => {
20519 let selections = self.selections.all::<usize>(cx);
20520 let multi_buffer = self.buffer.read(cx);
20521 for selection in selections {
20522 for (snapshot, range, _, anchor) in multi_buffer
20523 .snapshot(cx)
20524 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20525 {
20526 if let Some(anchor) = anchor {
20527 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20528 else {
20529 continue;
20530 };
20531 let offset = text::ToOffset::to_offset(
20532 &anchor.text_anchor,
20533 &buffer_handle.read(cx).snapshot(),
20534 );
20535 let range = offset..offset;
20536 new_selections_by_buffer
20537 .entry(buffer_handle)
20538 .or_insert((Vec::new(), None))
20539 .0
20540 .push(range)
20541 } else {
20542 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20543 else {
20544 continue;
20545 };
20546 new_selections_by_buffer
20547 .entry(buffer_handle)
20548 .or_insert((Vec::new(), None))
20549 .0
20550 .push(range)
20551 }
20552 }
20553 }
20554 }
20555 }
20556
20557 new_selections_by_buffer
20558 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20559
20560 if new_selections_by_buffer.is_empty() {
20561 return;
20562 }
20563
20564 // We defer the pane interaction because we ourselves are a workspace item
20565 // and activating a new item causes the pane to call a method on us reentrantly,
20566 // which panics if we're on the stack.
20567 window.defer(cx, move |window, cx| {
20568 workspace.update(cx, |workspace, cx| {
20569 let pane = if split {
20570 workspace.adjacent_pane(window, cx)
20571 } else {
20572 workspace.active_pane().clone()
20573 };
20574
20575 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20576 let editor = buffer
20577 .read(cx)
20578 .file()
20579 .is_none()
20580 .then(|| {
20581 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20582 // so `workspace.open_project_item` will never find them, always opening a new editor.
20583 // Instead, we try to activate the existing editor in the pane first.
20584 let (editor, pane_item_index) =
20585 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20586 let editor = item.downcast::<Editor>()?;
20587 let singleton_buffer =
20588 editor.read(cx).buffer().read(cx).as_singleton()?;
20589 if singleton_buffer == buffer {
20590 Some((editor, i))
20591 } else {
20592 None
20593 }
20594 })?;
20595 pane.update(cx, |pane, cx| {
20596 pane.activate_item(pane_item_index, true, true, window, cx)
20597 });
20598 Some(editor)
20599 })
20600 .flatten()
20601 .unwrap_or_else(|| {
20602 workspace.open_project_item::<Self>(
20603 pane.clone(),
20604 buffer,
20605 true,
20606 true,
20607 window,
20608 cx,
20609 )
20610 });
20611
20612 editor.update(cx, |editor, cx| {
20613 let autoscroll = match scroll_offset {
20614 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20615 None => Autoscroll::newest(),
20616 };
20617 let nav_history = editor.nav_history.take();
20618 editor.change_selections(
20619 SelectionEffects::scroll(autoscroll),
20620 window,
20621 cx,
20622 |s| {
20623 s.select_ranges(ranges);
20624 },
20625 );
20626 editor.nav_history = nav_history;
20627 });
20628 }
20629 })
20630 });
20631 }
20632
20633 // For now, don't allow opening excerpts in buffers that aren't backed by
20634 // regular project files.
20635 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20636 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20637 }
20638
20639 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20640 let snapshot = self.buffer.read(cx).read(cx);
20641 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20642 Some(
20643 ranges
20644 .iter()
20645 .map(move |range| {
20646 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20647 })
20648 .collect(),
20649 )
20650 }
20651
20652 fn selection_replacement_ranges(
20653 &self,
20654 range: Range<OffsetUtf16>,
20655 cx: &mut App,
20656 ) -> Vec<Range<OffsetUtf16>> {
20657 let selections = self.selections.all::<OffsetUtf16>(cx);
20658 let newest_selection = selections
20659 .iter()
20660 .max_by_key(|selection| selection.id)
20661 .unwrap();
20662 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20663 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20664 let snapshot = self.buffer.read(cx).read(cx);
20665 selections
20666 .into_iter()
20667 .map(|mut selection| {
20668 selection.start.0 =
20669 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20670 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20671 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20672 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20673 })
20674 .collect()
20675 }
20676
20677 fn report_editor_event(
20678 &self,
20679 reported_event: ReportEditorEvent,
20680 file_extension: Option<String>,
20681 cx: &App,
20682 ) {
20683 if cfg!(any(test, feature = "test-support")) {
20684 return;
20685 }
20686
20687 let Some(project) = &self.project else { return };
20688
20689 // If None, we are in a file without an extension
20690 let file = self
20691 .buffer
20692 .read(cx)
20693 .as_singleton()
20694 .and_then(|b| b.read(cx).file());
20695 let file_extension = file_extension.or(file
20696 .as_ref()
20697 .and_then(|file| Path::new(file.file_name(cx)).extension())
20698 .and_then(|e| e.to_str())
20699 .map(|a| a.to_string()));
20700
20701 let vim_mode = vim_enabled(cx);
20702
20703 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20704 let copilot_enabled = edit_predictions_provider
20705 == language::language_settings::EditPredictionProvider::Copilot;
20706 let copilot_enabled_for_language = self
20707 .buffer
20708 .read(cx)
20709 .language_settings(cx)
20710 .show_edit_predictions;
20711
20712 let project = project.read(cx);
20713 let event_type = reported_event.event_type();
20714
20715 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20716 telemetry::event!(
20717 event_type,
20718 type = if auto_saved {"autosave"} else {"manual"},
20719 file_extension,
20720 vim_mode,
20721 copilot_enabled,
20722 copilot_enabled_for_language,
20723 edit_predictions_provider,
20724 is_via_ssh = project.is_via_remote_server(),
20725 );
20726 } else {
20727 telemetry::event!(
20728 event_type,
20729 file_extension,
20730 vim_mode,
20731 copilot_enabled,
20732 copilot_enabled_for_language,
20733 edit_predictions_provider,
20734 is_via_ssh = project.is_via_remote_server(),
20735 );
20736 };
20737 }
20738
20739 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20740 /// with each line being an array of {text, highlight} objects.
20741 fn copy_highlight_json(
20742 &mut self,
20743 _: &CopyHighlightJson,
20744 window: &mut Window,
20745 cx: &mut Context<Self>,
20746 ) {
20747 #[derive(Serialize)]
20748 struct Chunk<'a> {
20749 text: String,
20750 highlight: Option<&'a str>,
20751 }
20752
20753 let snapshot = self.buffer.read(cx).snapshot(cx);
20754 let range = self
20755 .selected_text_range(false, window, cx)
20756 .and_then(|selection| {
20757 if selection.range.is_empty() {
20758 None
20759 } else {
20760 Some(selection.range)
20761 }
20762 })
20763 .unwrap_or_else(|| 0..snapshot.len());
20764
20765 let chunks = snapshot.chunks(range, true);
20766 let mut lines = Vec::new();
20767 let mut line: VecDeque<Chunk> = VecDeque::new();
20768
20769 let Some(style) = self.style.as_ref() else {
20770 return;
20771 };
20772
20773 for chunk in chunks {
20774 let highlight = chunk
20775 .syntax_highlight_id
20776 .and_then(|id| id.name(&style.syntax));
20777 let mut chunk_lines = chunk.text.split('\n').peekable();
20778 while let Some(text) = chunk_lines.next() {
20779 let mut merged_with_last_token = false;
20780 if let Some(last_token) = line.back_mut()
20781 && last_token.highlight == highlight
20782 {
20783 last_token.text.push_str(text);
20784 merged_with_last_token = true;
20785 }
20786
20787 if !merged_with_last_token {
20788 line.push_back(Chunk {
20789 text: text.into(),
20790 highlight,
20791 });
20792 }
20793
20794 if chunk_lines.peek().is_some() {
20795 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20796 line.pop_front();
20797 }
20798 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20799 line.pop_back();
20800 }
20801
20802 lines.push(mem::take(&mut line));
20803 }
20804 }
20805 }
20806
20807 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20808 return;
20809 };
20810 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20811 }
20812
20813 pub fn open_context_menu(
20814 &mut self,
20815 _: &OpenContextMenu,
20816 window: &mut Window,
20817 cx: &mut Context<Self>,
20818 ) {
20819 self.request_autoscroll(Autoscroll::newest(), cx);
20820 let position = self.selections.newest_display(cx).start;
20821 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20822 }
20823
20824 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20825 &self.inlay_hint_cache
20826 }
20827
20828 pub fn replay_insert_event(
20829 &mut self,
20830 text: &str,
20831 relative_utf16_range: Option<Range<isize>>,
20832 window: &mut Window,
20833 cx: &mut Context<Self>,
20834 ) {
20835 if !self.input_enabled {
20836 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20837 return;
20838 }
20839 if let Some(relative_utf16_range) = relative_utf16_range {
20840 let selections = self.selections.all::<OffsetUtf16>(cx);
20841 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20842 let new_ranges = selections.into_iter().map(|range| {
20843 let start = OffsetUtf16(
20844 range
20845 .head()
20846 .0
20847 .saturating_add_signed(relative_utf16_range.start),
20848 );
20849 let end = OffsetUtf16(
20850 range
20851 .head()
20852 .0
20853 .saturating_add_signed(relative_utf16_range.end),
20854 );
20855 start..end
20856 });
20857 s.select_ranges(new_ranges);
20858 });
20859 }
20860
20861 self.handle_input(text, window, cx);
20862 }
20863
20864 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20865 let Some(provider) = self.semantics_provider.as_ref() else {
20866 return false;
20867 };
20868
20869 let mut supports = false;
20870 self.buffer().update(cx, |this, cx| {
20871 this.for_each_buffer(|buffer| {
20872 supports |= provider.supports_inlay_hints(buffer, cx);
20873 });
20874 });
20875
20876 supports
20877 }
20878
20879 pub fn is_focused(&self, window: &Window) -> bool {
20880 self.focus_handle.is_focused(window)
20881 }
20882
20883 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20884 cx.emit(EditorEvent::Focused);
20885
20886 if let Some(descendant) = self
20887 .last_focused_descendant
20888 .take()
20889 .and_then(|descendant| descendant.upgrade())
20890 {
20891 window.focus(&descendant);
20892 } else {
20893 if let Some(blame) = self.blame.as_ref() {
20894 blame.update(cx, GitBlame::focus)
20895 }
20896
20897 self.blink_manager.update(cx, BlinkManager::enable);
20898 self.show_cursor_names(window, cx);
20899 self.buffer.update(cx, |buffer, cx| {
20900 buffer.finalize_last_transaction(cx);
20901 if self.leader_id.is_none() {
20902 buffer.set_active_selections(
20903 &self.selections.disjoint_anchors(),
20904 self.selections.line_mode,
20905 self.cursor_shape,
20906 cx,
20907 );
20908 }
20909 });
20910 }
20911 }
20912
20913 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20914 cx.emit(EditorEvent::FocusedIn)
20915 }
20916
20917 fn handle_focus_out(
20918 &mut self,
20919 event: FocusOutEvent,
20920 _window: &mut Window,
20921 cx: &mut Context<Self>,
20922 ) {
20923 if event.blurred != self.focus_handle {
20924 self.last_focused_descendant = Some(event.blurred);
20925 }
20926 self.selection_drag_state = SelectionDragState::None;
20927 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20928 }
20929
20930 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20931 self.blink_manager.update(cx, BlinkManager::disable);
20932 self.buffer
20933 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20934
20935 if let Some(blame) = self.blame.as_ref() {
20936 blame.update(cx, GitBlame::blur)
20937 }
20938 if !self.hover_state.focused(window, cx) {
20939 hide_hover(self, cx);
20940 }
20941 if !self
20942 .context_menu
20943 .borrow()
20944 .as_ref()
20945 .is_some_and(|context_menu| context_menu.focused(window, cx))
20946 {
20947 self.hide_context_menu(window, cx);
20948 }
20949 self.discard_edit_prediction(false, cx);
20950 cx.emit(EditorEvent::Blurred);
20951 cx.notify();
20952 }
20953
20954 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20955 let mut pending: String = window
20956 .pending_input_keystrokes()
20957 .into_iter()
20958 .flatten()
20959 .filter_map(|keystroke| {
20960 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20961 keystroke.key_char.clone()
20962 } else {
20963 None
20964 }
20965 })
20966 .collect();
20967
20968 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20969 pending = "".to_string();
20970 }
20971
20972 let existing_pending = self
20973 .text_highlights::<PendingInput>(cx)
20974 .map(|(_, ranges)| ranges.to_vec());
20975 if existing_pending.is_none() && pending.is_empty() {
20976 return;
20977 }
20978 let transaction =
20979 self.transact(window, cx, |this, window, cx| {
20980 let selections = this.selections.all::<usize>(cx);
20981 let edits = selections
20982 .iter()
20983 .map(|selection| (selection.end..selection.end, pending.clone()));
20984 this.edit(edits, cx);
20985 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20986 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20987 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20988 }));
20989 });
20990 if let Some(existing_ranges) = existing_pending {
20991 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20992 this.edit(edits, cx);
20993 }
20994 });
20995
20996 let snapshot = self.snapshot(window, cx);
20997 let ranges = self
20998 .selections
20999 .all::<usize>(cx)
21000 .into_iter()
21001 .map(|selection| {
21002 snapshot.buffer_snapshot.anchor_after(selection.end)
21003 ..snapshot
21004 .buffer_snapshot
21005 .anchor_before(selection.end + pending.len())
21006 })
21007 .collect();
21008
21009 if pending.is_empty() {
21010 self.clear_highlights::<PendingInput>(cx);
21011 } else {
21012 self.highlight_text::<PendingInput>(
21013 ranges,
21014 HighlightStyle {
21015 underline: Some(UnderlineStyle {
21016 thickness: px(1.),
21017 color: None,
21018 wavy: false,
21019 }),
21020 ..Default::default()
21021 },
21022 cx,
21023 );
21024 }
21025
21026 self.ime_transaction = self.ime_transaction.or(transaction);
21027 if let Some(transaction) = self.ime_transaction {
21028 self.buffer.update(cx, |buffer, cx| {
21029 buffer.group_until_transaction(transaction, cx);
21030 });
21031 }
21032
21033 if self.text_highlights::<PendingInput>(cx).is_none() {
21034 self.ime_transaction.take();
21035 }
21036 }
21037
21038 pub fn register_action_renderer(
21039 &mut self,
21040 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21041 ) -> Subscription {
21042 let id = self.next_editor_action_id.post_inc();
21043 self.editor_actions
21044 .borrow_mut()
21045 .insert(id, Box::new(listener));
21046
21047 let editor_actions = self.editor_actions.clone();
21048 Subscription::new(move || {
21049 editor_actions.borrow_mut().remove(&id);
21050 })
21051 }
21052
21053 pub fn register_action<A: Action>(
21054 &mut self,
21055 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21056 ) -> Subscription {
21057 let id = self.next_editor_action_id.post_inc();
21058 let listener = Arc::new(listener);
21059 self.editor_actions.borrow_mut().insert(
21060 id,
21061 Box::new(move |_, window, _| {
21062 let listener = listener.clone();
21063 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21064 let action = action.downcast_ref().unwrap();
21065 if phase == DispatchPhase::Bubble {
21066 listener(action, window, cx)
21067 }
21068 })
21069 }),
21070 );
21071
21072 let editor_actions = self.editor_actions.clone();
21073 Subscription::new(move || {
21074 editor_actions.borrow_mut().remove(&id);
21075 })
21076 }
21077
21078 pub fn file_header_size(&self) -> u32 {
21079 FILE_HEADER_HEIGHT
21080 }
21081
21082 pub fn restore(
21083 &mut self,
21084 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21085 window: &mut Window,
21086 cx: &mut Context<Self>,
21087 ) {
21088 let workspace = self.workspace();
21089 let project = self.project();
21090 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21091 let mut tasks = Vec::new();
21092 for (buffer_id, changes) in revert_changes {
21093 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21094 buffer.update(cx, |buffer, cx| {
21095 buffer.edit(
21096 changes
21097 .into_iter()
21098 .map(|(range, text)| (range, text.to_string())),
21099 None,
21100 cx,
21101 );
21102 });
21103
21104 if let Some(project) =
21105 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21106 {
21107 project.update(cx, |project, cx| {
21108 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21109 })
21110 }
21111 }
21112 }
21113 tasks
21114 });
21115 cx.spawn_in(window, async move |_, cx| {
21116 for (buffer, task) in save_tasks {
21117 let result = task.await;
21118 if result.is_err() {
21119 let Some(path) = buffer
21120 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21121 .ok()
21122 else {
21123 continue;
21124 };
21125 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21126 let Some(task) = cx
21127 .update_window_entity(workspace, |workspace, window, cx| {
21128 workspace
21129 .open_path_preview(path, None, false, false, false, window, cx)
21130 })
21131 .ok()
21132 else {
21133 continue;
21134 };
21135 task.await.log_err();
21136 }
21137 }
21138 }
21139 })
21140 .detach();
21141 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21142 selections.refresh()
21143 });
21144 }
21145
21146 pub fn to_pixel_point(
21147 &self,
21148 source: multi_buffer::Anchor,
21149 editor_snapshot: &EditorSnapshot,
21150 window: &mut Window,
21151 ) -> Option<gpui::Point<Pixels>> {
21152 let source_point = source.to_display_point(editor_snapshot);
21153 self.display_to_pixel_point(source_point, editor_snapshot, window)
21154 }
21155
21156 pub fn display_to_pixel_point(
21157 &self,
21158 source: DisplayPoint,
21159 editor_snapshot: &EditorSnapshot,
21160 window: &mut Window,
21161 ) -> Option<gpui::Point<Pixels>> {
21162 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21163 let text_layout_details = self.text_layout_details(window);
21164 let scroll_top = text_layout_details
21165 .scroll_anchor
21166 .scroll_position(editor_snapshot)
21167 .y;
21168
21169 if source.row().as_f32() < scroll_top.floor() {
21170 return None;
21171 }
21172 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21173 let source_y = line_height * (source.row().as_f32() - scroll_top);
21174 Some(gpui::Point::new(source_x, source_y))
21175 }
21176
21177 pub fn has_visible_completions_menu(&self) -> bool {
21178 !self.edit_prediction_preview_is_active()
21179 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21180 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21181 })
21182 }
21183
21184 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21185 if self.mode.is_minimap() {
21186 return;
21187 }
21188 self.addons
21189 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21190 }
21191
21192 pub fn unregister_addon<T: Addon>(&mut self) {
21193 self.addons.remove(&std::any::TypeId::of::<T>());
21194 }
21195
21196 pub fn addon<T: Addon>(&self) -> Option<&T> {
21197 let type_id = std::any::TypeId::of::<T>();
21198 self.addons
21199 .get(&type_id)
21200 .and_then(|item| item.to_any().downcast_ref::<T>())
21201 }
21202
21203 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21204 let type_id = std::any::TypeId::of::<T>();
21205 self.addons
21206 .get_mut(&type_id)
21207 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21208 }
21209
21210 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21211 let text_layout_details = self.text_layout_details(window);
21212 let style = &text_layout_details.editor_style;
21213 let font_id = window.text_system().resolve_font(&style.text.font());
21214 let font_size = style.text.font_size.to_pixels(window.rem_size());
21215 let line_height = style.text.line_height_in_pixels(window.rem_size());
21216 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21217 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21218
21219 CharacterDimensions {
21220 em_width,
21221 em_advance,
21222 line_height,
21223 }
21224 }
21225
21226 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21227 self.load_diff_task.clone()
21228 }
21229
21230 fn read_metadata_from_db(
21231 &mut self,
21232 item_id: u64,
21233 workspace_id: WorkspaceId,
21234 window: &mut Window,
21235 cx: &mut Context<Editor>,
21236 ) {
21237 if self.is_singleton(cx)
21238 && !self.mode.is_minimap()
21239 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21240 {
21241 let buffer_snapshot = OnceCell::new();
21242
21243 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21244 && !folds.is_empty()
21245 {
21246 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21247 self.fold_ranges(
21248 folds
21249 .into_iter()
21250 .map(|(start, end)| {
21251 snapshot.clip_offset(start, Bias::Left)
21252 ..snapshot.clip_offset(end, Bias::Right)
21253 })
21254 .collect(),
21255 false,
21256 window,
21257 cx,
21258 );
21259 }
21260
21261 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21262 && !selections.is_empty()
21263 {
21264 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21265 // skip adding the initial selection to selection history
21266 self.selection_history.mode = SelectionHistoryMode::Skipping;
21267 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21268 s.select_ranges(selections.into_iter().map(|(start, end)| {
21269 snapshot.clip_offset(start, Bias::Left)
21270 ..snapshot.clip_offset(end, Bias::Right)
21271 }));
21272 });
21273 self.selection_history.mode = SelectionHistoryMode::Normal;
21274 };
21275 }
21276
21277 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21278 }
21279
21280 fn update_lsp_data(
21281 &mut self,
21282 ignore_cache: bool,
21283 for_buffer: Option<BufferId>,
21284 window: &mut Window,
21285 cx: &mut Context<'_, Self>,
21286 ) {
21287 self.pull_diagnostics(for_buffer, window, cx);
21288 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21289 }
21290}
21291
21292fn vim_enabled(cx: &App) -> bool {
21293 cx.global::<SettingsStore>()
21294 .raw_user_settings()
21295 .get("vim_mode")
21296 == Some(&serde_json::Value::Bool(true))
21297}
21298
21299fn process_completion_for_edit(
21300 completion: &Completion,
21301 intent: CompletionIntent,
21302 buffer: &Entity<Buffer>,
21303 cursor_position: &text::Anchor,
21304 cx: &mut Context<Editor>,
21305) -> CompletionEdit {
21306 let buffer = buffer.read(cx);
21307 let buffer_snapshot = buffer.snapshot();
21308 let (snippet, new_text) = if completion.is_snippet() {
21309 // Workaround for typescript language server issues so that methods don't expand within
21310 // strings and functions with type expressions. The previous point is used because the query
21311 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21312 let mut snippet_source = completion.new_text.clone();
21313 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21314 previous_point.column = previous_point.column.saturating_sub(1);
21315 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21316 && scope.prefers_label_for_snippet_in_completion()
21317 && let Some(label) = completion.label()
21318 && matches!(
21319 completion.kind(),
21320 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21321 )
21322 {
21323 snippet_source = label;
21324 }
21325 match Snippet::parse(&snippet_source).log_err() {
21326 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21327 None => (None, completion.new_text.clone()),
21328 }
21329 } else {
21330 (None, completion.new_text.clone())
21331 };
21332
21333 let mut range_to_replace = {
21334 let replace_range = &completion.replace_range;
21335 if let CompletionSource::Lsp {
21336 insert_range: Some(insert_range),
21337 ..
21338 } = &completion.source
21339 {
21340 debug_assert_eq!(
21341 insert_range.start, replace_range.start,
21342 "insert_range and replace_range should start at the same position"
21343 );
21344 debug_assert!(
21345 insert_range
21346 .start
21347 .cmp(cursor_position, &buffer_snapshot)
21348 .is_le(),
21349 "insert_range should start before or at cursor position"
21350 );
21351 debug_assert!(
21352 replace_range
21353 .start
21354 .cmp(cursor_position, &buffer_snapshot)
21355 .is_le(),
21356 "replace_range should start before or at cursor position"
21357 );
21358
21359 let should_replace = match intent {
21360 CompletionIntent::CompleteWithInsert => false,
21361 CompletionIntent::CompleteWithReplace => true,
21362 CompletionIntent::Complete | CompletionIntent::Compose => {
21363 let insert_mode =
21364 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21365 .completions
21366 .lsp_insert_mode;
21367 match insert_mode {
21368 LspInsertMode::Insert => false,
21369 LspInsertMode::Replace => true,
21370 LspInsertMode::ReplaceSubsequence => {
21371 let mut text_to_replace = buffer.chars_for_range(
21372 buffer.anchor_before(replace_range.start)
21373 ..buffer.anchor_after(replace_range.end),
21374 );
21375 let mut current_needle = text_to_replace.next();
21376 for haystack_ch in completion.label.text.chars() {
21377 if let Some(needle_ch) = current_needle
21378 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21379 {
21380 current_needle = text_to_replace.next();
21381 }
21382 }
21383 current_needle.is_none()
21384 }
21385 LspInsertMode::ReplaceSuffix => {
21386 if replace_range
21387 .end
21388 .cmp(cursor_position, &buffer_snapshot)
21389 .is_gt()
21390 {
21391 let range_after_cursor = *cursor_position..replace_range.end;
21392 let text_after_cursor = buffer
21393 .text_for_range(
21394 buffer.anchor_before(range_after_cursor.start)
21395 ..buffer.anchor_after(range_after_cursor.end),
21396 )
21397 .collect::<String>()
21398 .to_ascii_lowercase();
21399 completion
21400 .label
21401 .text
21402 .to_ascii_lowercase()
21403 .ends_with(&text_after_cursor)
21404 } else {
21405 true
21406 }
21407 }
21408 }
21409 }
21410 };
21411
21412 if should_replace {
21413 replace_range.clone()
21414 } else {
21415 insert_range.clone()
21416 }
21417 } else {
21418 replace_range.clone()
21419 }
21420 };
21421
21422 if range_to_replace
21423 .end
21424 .cmp(cursor_position, &buffer_snapshot)
21425 .is_lt()
21426 {
21427 range_to_replace.end = *cursor_position;
21428 }
21429
21430 CompletionEdit {
21431 new_text,
21432 replace_range: range_to_replace.to_offset(buffer),
21433 snippet,
21434 }
21435}
21436
21437struct CompletionEdit {
21438 new_text: String,
21439 replace_range: Range<usize>,
21440 snippet: Option<Snippet>,
21441}
21442
21443fn insert_extra_newline_brackets(
21444 buffer: &MultiBufferSnapshot,
21445 range: Range<usize>,
21446 language: &language::LanguageScope,
21447) -> bool {
21448 let leading_whitespace_len = buffer
21449 .reversed_chars_at(range.start)
21450 .take_while(|c| c.is_whitespace() && *c != '\n')
21451 .map(|c| c.len_utf8())
21452 .sum::<usize>();
21453 let trailing_whitespace_len = buffer
21454 .chars_at(range.end)
21455 .take_while(|c| c.is_whitespace() && *c != '\n')
21456 .map(|c| c.len_utf8())
21457 .sum::<usize>();
21458 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21459
21460 language.brackets().any(|(pair, enabled)| {
21461 let pair_start = pair.start.trim_end();
21462 let pair_end = pair.end.trim_start();
21463
21464 enabled
21465 && pair.newline
21466 && buffer.contains_str_at(range.end, pair_end)
21467 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21468 })
21469}
21470
21471fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21472 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21473 [(buffer, range, _)] => (*buffer, range.clone()),
21474 _ => return false,
21475 };
21476 let pair = {
21477 let mut result: Option<BracketMatch> = None;
21478
21479 for pair in buffer
21480 .all_bracket_ranges(range.clone())
21481 .filter(move |pair| {
21482 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21483 })
21484 {
21485 let len = pair.close_range.end - pair.open_range.start;
21486
21487 if let Some(existing) = &result {
21488 let existing_len = existing.close_range.end - existing.open_range.start;
21489 if len > existing_len {
21490 continue;
21491 }
21492 }
21493
21494 result = Some(pair);
21495 }
21496
21497 result
21498 };
21499 let Some(pair) = pair else {
21500 return false;
21501 };
21502 pair.newline_only
21503 && buffer
21504 .chars_for_range(pair.open_range.end..range.start)
21505 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21506 .all(|c| c.is_whitespace() && c != '\n')
21507}
21508
21509fn update_uncommitted_diff_for_buffer(
21510 editor: Entity<Editor>,
21511 project: &Entity<Project>,
21512 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21513 buffer: Entity<MultiBuffer>,
21514 cx: &mut App,
21515) -> Task<()> {
21516 let mut tasks = Vec::new();
21517 project.update(cx, |project, cx| {
21518 for buffer in buffers {
21519 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21520 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21521 }
21522 }
21523 });
21524 cx.spawn(async move |cx| {
21525 let diffs = future::join_all(tasks).await;
21526 if editor
21527 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21528 .unwrap_or(false)
21529 {
21530 return;
21531 }
21532
21533 buffer
21534 .update(cx, |buffer, cx| {
21535 for diff in diffs.into_iter().flatten() {
21536 buffer.add_diff(diff, cx);
21537 }
21538 })
21539 .ok();
21540 })
21541}
21542
21543fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21544 let tab_size = tab_size.get() as usize;
21545 let mut width = offset;
21546
21547 for ch in text.chars() {
21548 width += if ch == '\t' {
21549 tab_size - (width % tab_size)
21550 } else {
21551 1
21552 };
21553 }
21554
21555 width - offset
21556}
21557
21558#[cfg(test)]
21559mod tests {
21560 use super::*;
21561
21562 #[test]
21563 fn test_string_size_with_expanded_tabs() {
21564 let nz = |val| NonZeroU32::new(val).unwrap();
21565 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21566 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21567 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21568 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21569 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21570 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21571 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21572 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21573 }
21574}
21575
21576/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21577struct WordBreakingTokenizer<'a> {
21578 input: &'a str,
21579}
21580
21581impl<'a> WordBreakingTokenizer<'a> {
21582 fn new(input: &'a str) -> Self {
21583 Self { input }
21584 }
21585}
21586
21587fn is_char_ideographic(ch: char) -> bool {
21588 use unicode_script::Script::*;
21589 use unicode_script::UnicodeScript;
21590 matches!(ch.script(), Han | Tangut | Yi)
21591}
21592
21593fn is_grapheme_ideographic(text: &str) -> bool {
21594 text.chars().any(is_char_ideographic)
21595}
21596
21597fn is_grapheme_whitespace(text: &str) -> bool {
21598 text.chars().any(|x| x.is_whitespace())
21599}
21600
21601fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21602 text.chars()
21603 .next()
21604 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21605}
21606
21607#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21608enum WordBreakToken<'a> {
21609 Word { token: &'a str, grapheme_len: usize },
21610 InlineWhitespace { token: &'a str, grapheme_len: usize },
21611 Newline,
21612}
21613
21614impl<'a> Iterator for WordBreakingTokenizer<'a> {
21615 /// Yields a span, the count of graphemes in the token, and whether it was
21616 /// whitespace. Note that it also breaks at word boundaries.
21617 type Item = WordBreakToken<'a>;
21618
21619 fn next(&mut self) -> Option<Self::Item> {
21620 use unicode_segmentation::UnicodeSegmentation;
21621 if self.input.is_empty() {
21622 return None;
21623 }
21624
21625 let mut iter = self.input.graphemes(true).peekable();
21626 let mut offset = 0;
21627 let mut grapheme_len = 0;
21628 if let Some(first_grapheme) = iter.next() {
21629 let is_newline = first_grapheme == "\n";
21630 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21631 offset += first_grapheme.len();
21632 grapheme_len += 1;
21633 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21634 if let Some(grapheme) = iter.peek().copied()
21635 && should_stay_with_preceding_ideograph(grapheme)
21636 {
21637 offset += grapheme.len();
21638 grapheme_len += 1;
21639 }
21640 } else {
21641 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21642 let mut next_word_bound = words.peek().copied();
21643 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21644 next_word_bound = words.next();
21645 }
21646 while let Some(grapheme) = iter.peek().copied() {
21647 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21648 break;
21649 };
21650 if is_grapheme_whitespace(grapheme) != is_whitespace
21651 || (grapheme == "\n") != is_newline
21652 {
21653 break;
21654 };
21655 offset += grapheme.len();
21656 grapheme_len += 1;
21657 iter.next();
21658 }
21659 }
21660 let token = &self.input[..offset];
21661 self.input = &self.input[offset..];
21662 if token == "\n" {
21663 Some(WordBreakToken::Newline)
21664 } else if is_whitespace {
21665 Some(WordBreakToken::InlineWhitespace {
21666 token,
21667 grapheme_len,
21668 })
21669 } else {
21670 Some(WordBreakToken::Word {
21671 token,
21672 grapheme_len,
21673 })
21674 }
21675 } else {
21676 None
21677 }
21678 }
21679}
21680
21681#[test]
21682fn test_word_breaking_tokenizer() {
21683 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21684 ("", &[]),
21685 (" ", &[whitespace(" ", 2)]),
21686 ("Ʒ", &[word("Ʒ", 1)]),
21687 ("Ǽ", &[word("Ǽ", 1)]),
21688 ("⋑", &[word("⋑", 1)]),
21689 ("⋑⋑", &[word("⋑⋑", 2)]),
21690 (
21691 "原理,进而",
21692 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21693 ),
21694 (
21695 "hello world",
21696 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21697 ),
21698 (
21699 "hello, world",
21700 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21701 ),
21702 (
21703 " hello world",
21704 &[
21705 whitespace(" ", 2),
21706 word("hello", 5),
21707 whitespace(" ", 1),
21708 word("world", 5),
21709 ],
21710 ),
21711 (
21712 "这是什么 \n 钢笔",
21713 &[
21714 word("这", 1),
21715 word("是", 1),
21716 word("什", 1),
21717 word("么", 1),
21718 whitespace(" ", 1),
21719 newline(),
21720 whitespace(" ", 1),
21721 word("钢", 1),
21722 word("笔", 1),
21723 ],
21724 ),
21725 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21726 ];
21727
21728 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21729 WordBreakToken::Word {
21730 token,
21731 grapheme_len,
21732 }
21733 }
21734
21735 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21736 WordBreakToken::InlineWhitespace {
21737 token,
21738 grapheme_len,
21739 }
21740 }
21741
21742 fn newline() -> WordBreakToken<'static> {
21743 WordBreakToken::Newline
21744 }
21745
21746 for (input, result) in tests {
21747 assert_eq!(
21748 WordBreakingTokenizer::new(input)
21749 .collect::<Vec<_>>()
21750 .as_slice(),
21751 *result,
21752 );
21753 }
21754}
21755
21756fn wrap_with_prefix(
21757 first_line_prefix: String,
21758 subsequent_lines_prefix: String,
21759 unwrapped_text: String,
21760 wrap_column: usize,
21761 tab_size: NonZeroU32,
21762 preserve_existing_whitespace: bool,
21763) -> String {
21764 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21765 let subsequent_lines_prefix_len =
21766 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21767 let mut wrapped_text = String::new();
21768 let mut current_line = first_line_prefix;
21769 let mut is_first_line = true;
21770
21771 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21772 let mut current_line_len = first_line_prefix_len;
21773 let mut in_whitespace = false;
21774 for token in tokenizer {
21775 let have_preceding_whitespace = in_whitespace;
21776 match token {
21777 WordBreakToken::Word {
21778 token,
21779 grapheme_len,
21780 } => {
21781 in_whitespace = false;
21782 let current_prefix_len = if is_first_line {
21783 first_line_prefix_len
21784 } else {
21785 subsequent_lines_prefix_len
21786 };
21787 if current_line_len + grapheme_len > wrap_column
21788 && current_line_len != current_prefix_len
21789 {
21790 wrapped_text.push_str(current_line.trim_end());
21791 wrapped_text.push('\n');
21792 is_first_line = false;
21793 current_line = subsequent_lines_prefix.clone();
21794 current_line_len = subsequent_lines_prefix_len;
21795 }
21796 current_line.push_str(token);
21797 current_line_len += grapheme_len;
21798 }
21799 WordBreakToken::InlineWhitespace {
21800 mut token,
21801 mut grapheme_len,
21802 } => {
21803 in_whitespace = true;
21804 if have_preceding_whitespace && !preserve_existing_whitespace {
21805 continue;
21806 }
21807 if !preserve_existing_whitespace {
21808 token = " ";
21809 grapheme_len = 1;
21810 }
21811 let current_prefix_len = if is_first_line {
21812 first_line_prefix_len
21813 } else {
21814 subsequent_lines_prefix_len
21815 };
21816 if current_line_len + grapheme_len > wrap_column {
21817 wrapped_text.push_str(current_line.trim_end());
21818 wrapped_text.push('\n');
21819 is_first_line = false;
21820 current_line = subsequent_lines_prefix.clone();
21821 current_line_len = subsequent_lines_prefix_len;
21822 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21823 current_line.push_str(token);
21824 current_line_len += grapheme_len;
21825 }
21826 }
21827 WordBreakToken::Newline => {
21828 in_whitespace = true;
21829 let current_prefix_len = if is_first_line {
21830 first_line_prefix_len
21831 } else {
21832 subsequent_lines_prefix_len
21833 };
21834 if preserve_existing_whitespace {
21835 wrapped_text.push_str(current_line.trim_end());
21836 wrapped_text.push('\n');
21837 is_first_line = false;
21838 current_line = subsequent_lines_prefix.clone();
21839 current_line_len = subsequent_lines_prefix_len;
21840 } else if have_preceding_whitespace {
21841 continue;
21842 } else if current_line_len + 1 > wrap_column
21843 && current_line_len != current_prefix_len
21844 {
21845 wrapped_text.push_str(current_line.trim_end());
21846 wrapped_text.push('\n');
21847 is_first_line = false;
21848 current_line = subsequent_lines_prefix.clone();
21849 current_line_len = subsequent_lines_prefix_len;
21850 } else if current_line_len != current_prefix_len {
21851 current_line.push(' ');
21852 current_line_len += 1;
21853 }
21854 }
21855 }
21856 }
21857
21858 if !current_line.is_empty() {
21859 wrapped_text.push_str(¤t_line);
21860 }
21861 wrapped_text
21862}
21863
21864#[test]
21865fn test_wrap_with_prefix() {
21866 assert_eq!(
21867 wrap_with_prefix(
21868 "# ".to_string(),
21869 "# ".to_string(),
21870 "abcdefg".to_string(),
21871 4,
21872 NonZeroU32::new(4).unwrap(),
21873 false,
21874 ),
21875 "# abcdefg"
21876 );
21877 assert_eq!(
21878 wrap_with_prefix(
21879 "".to_string(),
21880 "".to_string(),
21881 "\thello world".to_string(),
21882 8,
21883 NonZeroU32::new(4).unwrap(),
21884 false,
21885 ),
21886 "hello\nworld"
21887 );
21888 assert_eq!(
21889 wrap_with_prefix(
21890 "// ".to_string(),
21891 "// ".to_string(),
21892 "xx \nyy zz aa bb cc".to_string(),
21893 12,
21894 NonZeroU32::new(4).unwrap(),
21895 false,
21896 ),
21897 "// xx yy zz\n// aa bb cc"
21898 );
21899 assert_eq!(
21900 wrap_with_prefix(
21901 String::new(),
21902 String::new(),
21903 "这是什么 \n 钢笔".to_string(),
21904 3,
21905 NonZeroU32::new(4).unwrap(),
21906 false,
21907 ),
21908 "这是什\n么 钢\n笔"
21909 );
21910}
21911
21912pub trait CollaborationHub {
21913 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21914 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21915 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21916}
21917
21918impl CollaborationHub for Entity<Project> {
21919 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21920 self.read(cx).collaborators()
21921 }
21922
21923 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21924 self.read(cx).user_store().read(cx).participant_indices()
21925 }
21926
21927 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21928 let this = self.read(cx);
21929 let user_ids = this.collaborators().values().map(|c| c.user_id);
21930 this.user_store().read(cx).participant_names(user_ids, cx)
21931 }
21932}
21933
21934pub trait SemanticsProvider {
21935 fn hover(
21936 &self,
21937 buffer: &Entity<Buffer>,
21938 position: text::Anchor,
21939 cx: &mut App,
21940 ) -> Option<Task<Option<Vec<project::Hover>>>>;
21941
21942 fn inline_values(
21943 &self,
21944 buffer_handle: Entity<Buffer>,
21945 range: Range<text::Anchor>,
21946 cx: &mut App,
21947 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21948
21949 fn inlay_hints(
21950 &self,
21951 buffer_handle: Entity<Buffer>,
21952 range: Range<text::Anchor>,
21953 cx: &mut App,
21954 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21955
21956 fn resolve_inlay_hint(
21957 &self,
21958 hint: InlayHint,
21959 buffer_handle: Entity<Buffer>,
21960 server_id: LanguageServerId,
21961 cx: &mut App,
21962 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21963
21964 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21965
21966 fn document_highlights(
21967 &self,
21968 buffer: &Entity<Buffer>,
21969 position: text::Anchor,
21970 cx: &mut App,
21971 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21972
21973 fn definitions(
21974 &self,
21975 buffer: &Entity<Buffer>,
21976 position: text::Anchor,
21977 kind: GotoDefinitionKind,
21978 cx: &mut App,
21979 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
21980
21981 fn range_for_rename(
21982 &self,
21983 buffer: &Entity<Buffer>,
21984 position: text::Anchor,
21985 cx: &mut App,
21986 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21987
21988 fn perform_rename(
21989 &self,
21990 buffer: &Entity<Buffer>,
21991 position: text::Anchor,
21992 new_name: String,
21993 cx: &mut App,
21994 ) -> Option<Task<Result<ProjectTransaction>>>;
21995}
21996
21997pub trait CompletionProvider {
21998 fn completions(
21999 &self,
22000 excerpt_id: ExcerptId,
22001 buffer: &Entity<Buffer>,
22002 buffer_position: text::Anchor,
22003 trigger: CompletionContext,
22004 window: &mut Window,
22005 cx: &mut Context<Editor>,
22006 ) -> Task<Result<Vec<CompletionResponse>>>;
22007
22008 fn resolve_completions(
22009 &self,
22010 _buffer: Entity<Buffer>,
22011 _completion_indices: Vec<usize>,
22012 _completions: Rc<RefCell<Box<[Completion]>>>,
22013 _cx: &mut Context<Editor>,
22014 ) -> Task<Result<bool>> {
22015 Task::ready(Ok(false))
22016 }
22017
22018 fn apply_additional_edits_for_completion(
22019 &self,
22020 _buffer: Entity<Buffer>,
22021 _completions: Rc<RefCell<Box<[Completion]>>>,
22022 _completion_index: usize,
22023 _push_to_history: bool,
22024 _cx: &mut Context<Editor>,
22025 ) -> Task<Result<Option<language::Transaction>>> {
22026 Task::ready(Ok(None))
22027 }
22028
22029 fn is_completion_trigger(
22030 &self,
22031 buffer: &Entity<Buffer>,
22032 position: language::Anchor,
22033 text: &str,
22034 trigger_in_words: bool,
22035 menu_is_open: bool,
22036 cx: &mut Context<Editor>,
22037 ) -> bool;
22038
22039 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22040
22041 fn sort_completions(&self) -> bool {
22042 true
22043 }
22044
22045 fn filter_completions(&self) -> bool {
22046 true
22047 }
22048}
22049
22050pub trait CodeActionProvider {
22051 fn id(&self) -> Arc<str>;
22052
22053 fn code_actions(
22054 &self,
22055 buffer: &Entity<Buffer>,
22056 range: Range<text::Anchor>,
22057 window: &mut Window,
22058 cx: &mut App,
22059 ) -> Task<Result<Vec<CodeAction>>>;
22060
22061 fn apply_code_action(
22062 &self,
22063 buffer_handle: Entity<Buffer>,
22064 action: CodeAction,
22065 excerpt_id: ExcerptId,
22066 push_to_history: bool,
22067 window: &mut Window,
22068 cx: &mut App,
22069 ) -> Task<Result<ProjectTransaction>>;
22070}
22071
22072impl CodeActionProvider for Entity<Project> {
22073 fn id(&self) -> Arc<str> {
22074 "project".into()
22075 }
22076
22077 fn code_actions(
22078 &self,
22079 buffer: &Entity<Buffer>,
22080 range: Range<text::Anchor>,
22081 _window: &mut Window,
22082 cx: &mut App,
22083 ) -> Task<Result<Vec<CodeAction>>> {
22084 self.update(cx, |project, cx| {
22085 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22086 let code_actions = project.code_actions(buffer, range, None, cx);
22087 cx.background_spawn(async move {
22088 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22089 Ok(code_lens_actions
22090 .context("code lens fetch")?
22091 .into_iter()
22092 .flatten()
22093 .chain(
22094 code_actions
22095 .context("code action fetch")?
22096 .into_iter()
22097 .flatten(),
22098 )
22099 .collect())
22100 })
22101 })
22102 }
22103
22104 fn apply_code_action(
22105 &self,
22106 buffer_handle: Entity<Buffer>,
22107 action: CodeAction,
22108 _excerpt_id: ExcerptId,
22109 push_to_history: bool,
22110 _window: &mut Window,
22111 cx: &mut App,
22112 ) -> Task<Result<ProjectTransaction>> {
22113 self.update(cx, |project, cx| {
22114 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22115 })
22116 }
22117}
22118
22119fn snippet_completions(
22120 project: &Project,
22121 buffer: &Entity<Buffer>,
22122 buffer_position: text::Anchor,
22123 cx: &mut App,
22124) -> Task<Result<CompletionResponse>> {
22125 let languages = buffer.read(cx).languages_at(buffer_position);
22126 let snippet_store = project.snippets().read(cx);
22127
22128 let scopes: Vec<_> = languages
22129 .iter()
22130 .filter_map(|language| {
22131 let language_name = language.lsp_id();
22132 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22133
22134 if snippets.is_empty() {
22135 None
22136 } else {
22137 Some((language.default_scope(), snippets))
22138 }
22139 })
22140 .collect();
22141
22142 if scopes.is_empty() {
22143 return Task::ready(Ok(CompletionResponse {
22144 completions: vec![],
22145 is_incomplete: false,
22146 }));
22147 }
22148
22149 let snapshot = buffer.read(cx).text_snapshot();
22150 let chars: String = snapshot
22151 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22152 .collect();
22153 let executor = cx.background_executor().clone();
22154
22155 cx.background_spawn(async move {
22156 let mut is_incomplete = false;
22157 let mut completions: Vec<Completion> = Vec::new();
22158 for (scope, snippets) in scopes.into_iter() {
22159 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22160 let mut last_word = chars
22161 .chars()
22162 .take_while(|c| classifier.is_word(*c))
22163 .collect::<String>();
22164 last_word = last_word.chars().rev().collect();
22165
22166 if last_word.is_empty() {
22167 return Ok(CompletionResponse {
22168 completions: vec![],
22169 is_incomplete: true,
22170 });
22171 }
22172
22173 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22174 let to_lsp = |point: &text::Anchor| {
22175 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22176 point_to_lsp(end)
22177 };
22178 let lsp_end = to_lsp(&buffer_position);
22179
22180 let candidates = snippets
22181 .iter()
22182 .enumerate()
22183 .flat_map(|(ix, snippet)| {
22184 snippet
22185 .prefix
22186 .iter()
22187 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22188 })
22189 .collect::<Vec<StringMatchCandidate>>();
22190
22191 const MAX_RESULTS: usize = 100;
22192 let mut matches = fuzzy::match_strings(
22193 &candidates,
22194 &last_word,
22195 last_word.chars().any(|c| c.is_uppercase()),
22196 true,
22197 MAX_RESULTS,
22198 &Default::default(),
22199 executor.clone(),
22200 )
22201 .await;
22202
22203 if matches.len() >= MAX_RESULTS {
22204 is_incomplete = true;
22205 }
22206
22207 // Remove all candidates where the query's start does not match the start of any word in the candidate
22208 if let Some(query_start) = last_word.chars().next() {
22209 matches.retain(|string_match| {
22210 split_words(&string_match.string).any(|word| {
22211 // Check that the first codepoint of the word as lowercase matches the first
22212 // codepoint of the query as lowercase
22213 word.chars()
22214 .flat_map(|codepoint| codepoint.to_lowercase())
22215 .zip(query_start.to_lowercase())
22216 .all(|(word_cp, query_cp)| word_cp == query_cp)
22217 })
22218 });
22219 }
22220
22221 let matched_strings = matches
22222 .into_iter()
22223 .map(|m| m.string)
22224 .collect::<HashSet<_>>();
22225
22226 completions.extend(snippets.iter().filter_map(|snippet| {
22227 let matching_prefix = snippet
22228 .prefix
22229 .iter()
22230 .find(|prefix| matched_strings.contains(*prefix))?;
22231 let start = as_offset - last_word.len();
22232 let start = snapshot.anchor_before(start);
22233 let range = start..buffer_position;
22234 let lsp_start = to_lsp(&start);
22235 let lsp_range = lsp::Range {
22236 start: lsp_start,
22237 end: lsp_end,
22238 };
22239 Some(Completion {
22240 replace_range: range,
22241 new_text: snippet.body.clone(),
22242 source: CompletionSource::Lsp {
22243 insert_range: None,
22244 server_id: LanguageServerId(usize::MAX),
22245 resolved: true,
22246 lsp_completion: Box::new(lsp::CompletionItem {
22247 label: snippet.prefix.first().unwrap().clone(),
22248 kind: Some(CompletionItemKind::SNIPPET),
22249 label_details: snippet.description.as_ref().map(|description| {
22250 lsp::CompletionItemLabelDetails {
22251 detail: Some(description.clone()),
22252 description: None,
22253 }
22254 }),
22255 insert_text_format: Some(InsertTextFormat::SNIPPET),
22256 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22257 lsp::InsertReplaceEdit {
22258 new_text: snippet.body.clone(),
22259 insert: lsp_range,
22260 replace: lsp_range,
22261 },
22262 )),
22263 filter_text: Some(snippet.body.clone()),
22264 sort_text: Some(char::MAX.to_string()),
22265 ..lsp::CompletionItem::default()
22266 }),
22267 lsp_defaults: None,
22268 },
22269 label: CodeLabel {
22270 text: matching_prefix.clone(),
22271 runs: Vec::new(),
22272 filter_range: 0..matching_prefix.len(),
22273 },
22274 icon_path: None,
22275 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22276 single_line: snippet.name.clone().into(),
22277 plain_text: snippet
22278 .description
22279 .clone()
22280 .map(|description| description.into()),
22281 }),
22282 insert_text_mode: None,
22283 confirm: None,
22284 })
22285 }))
22286 }
22287
22288 Ok(CompletionResponse {
22289 completions,
22290 is_incomplete,
22291 })
22292 })
22293}
22294
22295impl CompletionProvider for Entity<Project> {
22296 fn completions(
22297 &self,
22298 _excerpt_id: ExcerptId,
22299 buffer: &Entity<Buffer>,
22300 buffer_position: text::Anchor,
22301 options: CompletionContext,
22302 _window: &mut Window,
22303 cx: &mut Context<Editor>,
22304 ) -> Task<Result<Vec<CompletionResponse>>> {
22305 self.update(cx, |project, cx| {
22306 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22307 let project_completions = project.completions(buffer, buffer_position, options, cx);
22308 cx.background_spawn(async move {
22309 let mut responses = project_completions.await?;
22310 let snippets = snippets.await?;
22311 if !snippets.completions.is_empty() {
22312 responses.push(snippets);
22313 }
22314 Ok(responses)
22315 })
22316 })
22317 }
22318
22319 fn resolve_completions(
22320 &self,
22321 buffer: Entity<Buffer>,
22322 completion_indices: Vec<usize>,
22323 completions: Rc<RefCell<Box<[Completion]>>>,
22324 cx: &mut Context<Editor>,
22325 ) -> Task<Result<bool>> {
22326 self.update(cx, |project, cx| {
22327 project.lsp_store().update(cx, |lsp_store, cx| {
22328 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22329 })
22330 })
22331 }
22332
22333 fn apply_additional_edits_for_completion(
22334 &self,
22335 buffer: Entity<Buffer>,
22336 completions: Rc<RefCell<Box<[Completion]>>>,
22337 completion_index: usize,
22338 push_to_history: bool,
22339 cx: &mut Context<Editor>,
22340 ) -> Task<Result<Option<language::Transaction>>> {
22341 self.update(cx, |project, cx| {
22342 project.lsp_store().update(cx, |lsp_store, cx| {
22343 lsp_store.apply_additional_edits_for_completion(
22344 buffer,
22345 completions,
22346 completion_index,
22347 push_to_history,
22348 cx,
22349 )
22350 })
22351 })
22352 }
22353
22354 fn is_completion_trigger(
22355 &self,
22356 buffer: &Entity<Buffer>,
22357 position: language::Anchor,
22358 text: &str,
22359 trigger_in_words: bool,
22360 menu_is_open: bool,
22361 cx: &mut Context<Editor>,
22362 ) -> bool {
22363 let mut chars = text.chars();
22364 let char = if let Some(char) = chars.next() {
22365 char
22366 } else {
22367 return false;
22368 };
22369 if chars.next().is_some() {
22370 return false;
22371 }
22372
22373 let buffer = buffer.read(cx);
22374 let snapshot = buffer.snapshot();
22375 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22376 return false;
22377 }
22378 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22379 if trigger_in_words && classifier.is_word(char) {
22380 return true;
22381 }
22382
22383 buffer.completion_triggers().contains(text)
22384 }
22385}
22386
22387impl SemanticsProvider for Entity<Project> {
22388 fn hover(
22389 &self,
22390 buffer: &Entity<Buffer>,
22391 position: text::Anchor,
22392 cx: &mut App,
22393 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22394 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22395 }
22396
22397 fn document_highlights(
22398 &self,
22399 buffer: &Entity<Buffer>,
22400 position: text::Anchor,
22401 cx: &mut App,
22402 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22403 Some(self.update(cx, |project, cx| {
22404 project.document_highlights(buffer, position, cx)
22405 }))
22406 }
22407
22408 fn definitions(
22409 &self,
22410 buffer: &Entity<Buffer>,
22411 position: text::Anchor,
22412 kind: GotoDefinitionKind,
22413 cx: &mut App,
22414 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22415 Some(self.update(cx, |project, cx| match kind {
22416 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22417 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22418 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22419 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22420 }))
22421 }
22422
22423 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22424 self.update(cx, |project, cx| {
22425 if project
22426 .active_debug_session(cx)
22427 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22428 {
22429 return true;
22430 }
22431
22432 buffer.update(cx, |buffer, cx| {
22433 project.any_language_server_supports_inlay_hints(buffer, cx)
22434 })
22435 })
22436 }
22437
22438 fn inline_values(
22439 &self,
22440 buffer_handle: Entity<Buffer>,
22441 range: Range<text::Anchor>,
22442 cx: &mut App,
22443 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22444 self.update(cx, |project, cx| {
22445 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22446
22447 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22448 })
22449 }
22450
22451 fn inlay_hints(
22452 &self,
22453 buffer_handle: Entity<Buffer>,
22454 range: Range<text::Anchor>,
22455 cx: &mut App,
22456 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22457 Some(self.update(cx, |project, cx| {
22458 project.inlay_hints(buffer_handle, range, cx)
22459 }))
22460 }
22461
22462 fn resolve_inlay_hint(
22463 &self,
22464 hint: InlayHint,
22465 buffer_handle: Entity<Buffer>,
22466 server_id: LanguageServerId,
22467 cx: &mut App,
22468 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22469 Some(self.update(cx, |project, cx| {
22470 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22471 }))
22472 }
22473
22474 fn range_for_rename(
22475 &self,
22476 buffer: &Entity<Buffer>,
22477 position: text::Anchor,
22478 cx: &mut App,
22479 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22480 Some(self.update(cx, |project, cx| {
22481 let buffer = buffer.clone();
22482 let task = project.prepare_rename(buffer.clone(), position, cx);
22483 cx.spawn(async move |_, cx| {
22484 Ok(match task.await? {
22485 PrepareRenameResponse::Success(range) => Some(range),
22486 PrepareRenameResponse::InvalidPosition => None,
22487 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22488 // Fallback on using TreeSitter info to determine identifier range
22489 buffer.read_with(cx, |buffer, _| {
22490 let snapshot = buffer.snapshot();
22491 let (range, kind) = snapshot.surrounding_word(position, false);
22492 if kind != Some(CharKind::Word) {
22493 return None;
22494 }
22495 Some(
22496 snapshot.anchor_before(range.start)
22497 ..snapshot.anchor_after(range.end),
22498 )
22499 })?
22500 }
22501 })
22502 })
22503 }))
22504 }
22505
22506 fn perform_rename(
22507 &self,
22508 buffer: &Entity<Buffer>,
22509 position: text::Anchor,
22510 new_name: String,
22511 cx: &mut App,
22512 ) -> Option<Task<Result<ProjectTransaction>>> {
22513 Some(self.update(cx, |project, cx| {
22514 project.perform_rename(buffer.clone(), position, new_name, cx)
22515 }))
22516 }
22517}
22518
22519fn inlay_hint_settings(
22520 location: Anchor,
22521 snapshot: &MultiBufferSnapshot,
22522 cx: &mut Context<Editor>,
22523) -> InlayHintSettings {
22524 let file = snapshot.file_at(location);
22525 let language = snapshot.language_at(location).map(|l| l.name());
22526 language_settings(language, file, cx).inlay_hints
22527}
22528
22529fn consume_contiguous_rows(
22530 contiguous_row_selections: &mut Vec<Selection<Point>>,
22531 selection: &Selection<Point>,
22532 display_map: &DisplaySnapshot,
22533 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22534) -> (MultiBufferRow, MultiBufferRow) {
22535 contiguous_row_selections.push(selection.clone());
22536 let start_row = starting_row(selection, display_map);
22537 let mut end_row = ending_row(selection, display_map);
22538
22539 while let Some(next_selection) = selections.peek() {
22540 if next_selection.start.row <= end_row.0 {
22541 end_row = ending_row(next_selection, display_map);
22542 contiguous_row_selections.push(selections.next().unwrap().clone());
22543 } else {
22544 break;
22545 }
22546 }
22547 (start_row, end_row)
22548}
22549
22550fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22551 if selection.start.column > 0 {
22552 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22553 } else {
22554 MultiBufferRow(selection.start.row)
22555 }
22556}
22557
22558fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22559 if next_selection.end.column > 0 || next_selection.is_empty() {
22560 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22561 } else {
22562 MultiBufferRow(next_selection.end.row)
22563 }
22564}
22565
22566impl EditorSnapshot {
22567 pub fn remote_selections_in_range<'a>(
22568 &'a self,
22569 range: &'a Range<Anchor>,
22570 collaboration_hub: &dyn CollaborationHub,
22571 cx: &'a App,
22572 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22573 let participant_names = collaboration_hub.user_names(cx);
22574 let participant_indices = collaboration_hub.user_participant_indices(cx);
22575 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22576 let collaborators_by_replica_id = collaborators_by_peer_id
22577 .values()
22578 .map(|collaborator| (collaborator.replica_id, collaborator))
22579 .collect::<HashMap<_, _>>();
22580 self.buffer_snapshot
22581 .selections_in_range(range, false)
22582 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22583 if replica_id == AGENT_REPLICA_ID {
22584 Some(RemoteSelection {
22585 replica_id,
22586 selection,
22587 cursor_shape,
22588 line_mode,
22589 collaborator_id: CollaboratorId::Agent,
22590 user_name: Some("Agent".into()),
22591 color: cx.theme().players().agent(),
22592 })
22593 } else {
22594 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22595 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22596 let user_name = participant_names.get(&collaborator.user_id).cloned();
22597 Some(RemoteSelection {
22598 replica_id,
22599 selection,
22600 cursor_shape,
22601 line_mode,
22602 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22603 user_name,
22604 color: if let Some(index) = participant_index {
22605 cx.theme().players().color_for_participant(index.0)
22606 } else {
22607 cx.theme().players().absent()
22608 },
22609 })
22610 }
22611 })
22612 }
22613
22614 pub fn hunks_for_ranges(
22615 &self,
22616 ranges: impl IntoIterator<Item = Range<Point>>,
22617 ) -> Vec<MultiBufferDiffHunk> {
22618 let mut hunks = Vec::new();
22619 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22620 HashMap::default();
22621 for query_range in ranges {
22622 let query_rows =
22623 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22624 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22625 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22626 ) {
22627 // Include deleted hunks that are adjacent to the query range, because
22628 // otherwise they would be missed.
22629 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22630 if hunk.status().is_deleted() {
22631 intersects_range |= hunk.row_range.start == query_rows.end;
22632 intersects_range |= hunk.row_range.end == query_rows.start;
22633 }
22634 if intersects_range {
22635 if !processed_buffer_rows
22636 .entry(hunk.buffer_id)
22637 .or_default()
22638 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22639 {
22640 continue;
22641 }
22642 hunks.push(hunk);
22643 }
22644 }
22645 }
22646
22647 hunks
22648 }
22649
22650 fn display_diff_hunks_for_rows<'a>(
22651 &'a self,
22652 display_rows: Range<DisplayRow>,
22653 folded_buffers: &'a HashSet<BufferId>,
22654 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22655 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22656 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22657
22658 self.buffer_snapshot
22659 .diff_hunks_in_range(buffer_start..buffer_end)
22660 .filter_map(|hunk| {
22661 if folded_buffers.contains(&hunk.buffer_id) {
22662 return None;
22663 }
22664
22665 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22666 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22667
22668 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22669 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22670
22671 let display_hunk = if hunk_display_start.column() != 0 {
22672 DisplayDiffHunk::Folded {
22673 display_row: hunk_display_start.row(),
22674 }
22675 } else {
22676 let mut end_row = hunk_display_end.row();
22677 if hunk_display_end.column() > 0 {
22678 end_row.0 += 1;
22679 }
22680 let is_created_file = hunk.is_created_file();
22681 DisplayDiffHunk::Unfolded {
22682 status: hunk.status(),
22683 diff_base_byte_range: hunk.diff_base_byte_range,
22684 display_row_range: hunk_display_start.row()..end_row,
22685 multi_buffer_range: Anchor::range_in_buffer(
22686 hunk.excerpt_id,
22687 hunk.buffer_id,
22688 hunk.buffer_range,
22689 ),
22690 is_created_file,
22691 }
22692 };
22693
22694 Some(display_hunk)
22695 })
22696 }
22697
22698 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22699 self.display_snapshot.buffer_snapshot.language_at(position)
22700 }
22701
22702 pub fn is_focused(&self) -> bool {
22703 self.is_focused
22704 }
22705
22706 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22707 self.placeholder_text.as_ref()
22708 }
22709
22710 pub fn scroll_position(&self) -> gpui::Point<f32> {
22711 self.scroll_anchor.scroll_position(&self.display_snapshot)
22712 }
22713
22714 fn gutter_dimensions(
22715 &self,
22716 font_id: FontId,
22717 font_size: Pixels,
22718 max_line_number_width: Pixels,
22719 cx: &App,
22720 ) -> Option<GutterDimensions> {
22721 if !self.show_gutter {
22722 return None;
22723 }
22724
22725 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22726 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22727
22728 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22729 matches!(
22730 ProjectSettings::get_global(cx).git.git_gutter,
22731 Some(GitGutterSetting::TrackedFiles)
22732 )
22733 });
22734 let gutter_settings = EditorSettings::get_global(cx).gutter;
22735 let show_line_numbers = self
22736 .show_line_numbers
22737 .unwrap_or(gutter_settings.line_numbers);
22738 let line_gutter_width = if show_line_numbers {
22739 // Avoid flicker-like gutter resizes when the line number gains another digit by
22740 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22741 let min_width_for_number_on_gutter =
22742 ch_advance * gutter_settings.min_line_number_digits as f32;
22743 max_line_number_width.max(min_width_for_number_on_gutter)
22744 } else {
22745 0.0.into()
22746 };
22747
22748 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22749 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22750
22751 let git_blame_entries_width =
22752 self.git_blame_gutter_max_author_length
22753 .map(|max_author_length| {
22754 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22755 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22756
22757 /// The number of characters to dedicate to gaps and margins.
22758 const SPACING_WIDTH: usize = 4;
22759
22760 let max_char_count = max_author_length.min(renderer.max_author_length())
22761 + ::git::SHORT_SHA_LENGTH
22762 + MAX_RELATIVE_TIMESTAMP.len()
22763 + SPACING_WIDTH;
22764
22765 ch_advance * max_char_count
22766 });
22767
22768 let is_singleton = self.buffer_snapshot.is_singleton();
22769
22770 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22771 left_padding += if !is_singleton {
22772 ch_width * 4.0
22773 } else if show_runnables || show_breakpoints {
22774 ch_width * 3.0
22775 } else if show_git_gutter && show_line_numbers {
22776 ch_width * 2.0
22777 } else if show_git_gutter || show_line_numbers {
22778 ch_width
22779 } else {
22780 px(0.)
22781 };
22782
22783 let shows_folds = is_singleton && gutter_settings.folds;
22784
22785 let right_padding = if shows_folds && show_line_numbers {
22786 ch_width * 4.0
22787 } else if shows_folds || (!is_singleton && show_line_numbers) {
22788 ch_width * 3.0
22789 } else if show_line_numbers {
22790 ch_width
22791 } else {
22792 px(0.)
22793 };
22794
22795 Some(GutterDimensions {
22796 left_padding,
22797 right_padding,
22798 width: line_gutter_width + left_padding + right_padding,
22799 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22800 git_blame_entries_width,
22801 })
22802 }
22803
22804 pub fn render_crease_toggle(
22805 &self,
22806 buffer_row: MultiBufferRow,
22807 row_contains_cursor: bool,
22808 editor: Entity<Editor>,
22809 window: &mut Window,
22810 cx: &mut App,
22811 ) -> Option<AnyElement> {
22812 let folded = self.is_line_folded(buffer_row);
22813 let mut is_foldable = false;
22814
22815 if let Some(crease) = self
22816 .crease_snapshot
22817 .query_row(buffer_row, &self.buffer_snapshot)
22818 {
22819 is_foldable = true;
22820 match crease {
22821 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22822 if let Some(render_toggle) = render_toggle {
22823 let toggle_callback =
22824 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22825 if folded {
22826 editor.update(cx, |editor, cx| {
22827 editor.fold_at(buffer_row, window, cx)
22828 });
22829 } else {
22830 editor.update(cx, |editor, cx| {
22831 editor.unfold_at(buffer_row, window, cx)
22832 });
22833 }
22834 });
22835 return Some((render_toggle)(
22836 buffer_row,
22837 folded,
22838 toggle_callback,
22839 window,
22840 cx,
22841 ));
22842 }
22843 }
22844 }
22845 }
22846
22847 is_foldable |= self.starts_indent(buffer_row);
22848
22849 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22850 Some(
22851 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22852 .toggle_state(folded)
22853 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22854 if folded {
22855 this.unfold_at(buffer_row, window, cx);
22856 } else {
22857 this.fold_at(buffer_row, window, cx);
22858 }
22859 }))
22860 .into_any_element(),
22861 )
22862 } else {
22863 None
22864 }
22865 }
22866
22867 pub fn render_crease_trailer(
22868 &self,
22869 buffer_row: MultiBufferRow,
22870 window: &mut Window,
22871 cx: &mut App,
22872 ) -> Option<AnyElement> {
22873 let folded = self.is_line_folded(buffer_row);
22874 if let Crease::Inline { render_trailer, .. } = self
22875 .crease_snapshot
22876 .query_row(buffer_row, &self.buffer_snapshot)?
22877 {
22878 let render_trailer = render_trailer.as_ref()?;
22879 Some(render_trailer(buffer_row, folded, window, cx))
22880 } else {
22881 None
22882 }
22883 }
22884}
22885
22886impl Deref for EditorSnapshot {
22887 type Target = DisplaySnapshot;
22888
22889 fn deref(&self) -> &Self::Target {
22890 &self.display_snapshot
22891 }
22892}
22893
22894#[derive(Clone, Debug, PartialEq, Eq)]
22895pub enum EditorEvent {
22896 InputIgnored {
22897 text: Arc<str>,
22898 },
22899 InputHandled {
22900 utf16_range_to_replace: Option<Range<isize>>,
22901 text: Arc<str>,
22902 },
22903 ExcerptsAdded {
22904 buffer: Entity<Buffer>,
22905 predecessor: ExcerptId,
22906 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22907 },
22908 ExcerptsRemoved {
22909 ids: Vec<ExcerptId>,
22910 removed_buffer_ids: Vec<BufferId>,
22911 },
22912 BufferFoldToggled {
22913 ids: Vec<ExcerptId>,
22914 folded: bool,
22915 },
22916 ExcerptsEdited {
22917 ids: Vec<ExcerptId>,
22918 },
22919 ExcerptsExpanded {
22920 ids: Vec<ExcerptId>,
22921 },
22922 BufferEdited,
22923 Edited {
22924 transaction_id: clock::Lamport,
22925 },
22926 Reparsed(BufferId),
22927 Focused,
22928 FocusedIn,
22929 Blurred,
22930 DirtyChanged,
22931 Saved,
22932 TitleChanged,
22933 DiffBaseChanged,
22934 SelectionsChanged {
22935 local: bool,
22936 },
22937 ScrollPositionChanged {
22938 local: bool,
22939 autoscroll: bool,
22940 },
22941 Closed,
22942 TransactionUndone {
22943 transaction_id: clock::Lamport,
22944 },
22945 TransactionBegun {
22946 transaction_id: clock::Lamport,
22947 },
22948 Reloaded,
22949 CursorShapeChanged,
22950 BreadcrumbsChanged,
22951 PushedToNavHistory {
22952 anchor: Anchor,
22953 is_deactivate: bool,
22954 },
22955}
22956
22957impl EventEmitter<EditorEvent> for Editor {}
22958
22959impl Focusable for Editor {
22960 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22961 self.focus_handle.clone()
22962 }
22963}
22964
22965impl Render for Editor {
22966 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22967 let settings = ThemeSettings::get_global(cx);
22968
22969 let mut text_style = match self.mode {
22970 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
22971 color: cx.theme().colors().editor_foreground,
22972 font_family: settings.ui_font.family.clone(),
22973 font_features: settings.ui_font.features.clone(),
22974 font_fallbacks: settings.ui_font.fallbacks.clone(),
22975 font_size: rems(0.875).into(),
22976 font_weight: settings.ui_font.weight,
22977 line_height: relative(settings.buffer_line_height.value()),
22978 ..Default::default()
22979 },
22980 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22981 color: cx.theme().colors().editor_foreground,
22982 font_family: settings.buffer_font.family.clone(),
22983 font_features: settings.buffer_font.features.clone(),
22984 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22985 font_size: settings.buffer_font_size(cx).into(),
22986 font_weight: settings.buffer_font.weight,
22987 line_height: relative(settings.buffer_line_height.value()),
22988 ..Default::default()
22989 },
22990 };
22991 if let Some(text_style_refinement) = &self.text_style_refinement {
22992 text_style.refine(text_style_refinement)
22993 }
22994
22995 let background = match self.mode {
22996 EditorMode::SingleLine => cx.theme().system().transparent,
22997 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22998 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22999 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23000 };
23001
23002 EditorElement::new(
23003 &cx.entity(),
23004 EditorStyle {
23005 background,
23006 border: cx.theme().colors().border,
23007 local_player: cx.theme().players().local(),
23008 text: text_style,
23009 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23010 syntax: cx.theme().syntax().clone(),
23011 status: cx.theme().status().clone(),
23012 inlay_hints_style: make_inlay_hints_style(cx),
23013 edit_prediction_styles: make_suggestion_styles(cx),
23014 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23015 show_underlines: self.diagnostics_enabled(),
23016 },
23017 )
23018 }
23019}
23020
23021impl EntityInputHandler for Editor {
23022 fn text_for_range(
23023 &mut self,
23024 range_utf16: Range<usize>,
23025 adjusted_range: &mut Option<Range<usize>>,
23026 _: &mut Window,
23027 cx: &mut Context<Self>,
23028 ) -> Option<String> {
23029 let snapshot = self.buffer.read(cx).read(cx);
23030 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23031 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23032 if (start.0..end.0) != range_utf16 {
23033 adjusted_range.replace(start.0..end.0);
23034 }
23035 Some(snapshot.text_for_range(start..end).collect())
23036 }
23037
23038 fn selected_text_range(
23039 &mut self,
23040 ignore_disabled_input: bool,
23041 _: &mut Window,
23042 cx: &mut Context<Self>,
23043 ) -> Option<UTF16Selection> {
23044 // Prevent the IME menu from appearing when holding down an alphabetic key
23045 // while input is disabled.
23046 if !ignore_disabled_input && !self.input_enabled {
23047 return None;
23048 }
23049
23050 let selection = self.selections.newest::<OffsetUtf16>(cx);
23051 let range = selection.range();
23052
23053 Some(UTF16Selection {
23054 range: range.start.0..range.end.0,
23055 reversed: selection.reversed,
23056 })
23057 }
23058
23059 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23060 let snapshot = self.buffer.read(cx).read(cx);
23061 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23062 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23063 }
23064
23065 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23066 self.clear_highlights::<InputComposition>(cx);
23067 self.ime_transaction.take();
23068 }
23069
23070 fn replace_text_in_range(
23071 &mut self,
23072 range_utf16: Option<Range<usize>>,
23073 text: &str,
23074 window: &mut Window,
23075 cx: &mut Context<Self>,
23076 ) {
23077 if !self.input_enabled {
23078 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23079 return;
23080 }
23081
23082 self.transact(window, cx, |this, window, cx| {
23083 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23084 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23085 Some(this.selection_replacement_ranges(range_utf16, cx))
23086 } else {
23087 this.marked_text_ranges(cx)
23088 };
23089
23090 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23091 let newest_selection_id = this.selections.newest_anchor().id;
23092 this.selections
23093 .all::<OffsetUtf16>(cx)
23094 .iter()
23095 .zip(ranges_to_replace.iter())
23096 .find_map(|(selection, range)| {
23097 if selection.id == newest_selection_id {
23098 Some(
23099 (range.start.0 as isize - selection.head().0 as isize)
23100 ..(range.end.0 as isize - selection.head().0 as isize),
23101 )
23102 } else {
23103 None
23104 }
23105 })
23106 });
23107
23108 cx.emit(EditorEvent::InputHandled {
23109 utf16_range_to_replace: range_to_replace,
23110 text: text.into(),
23111 });
23112
23113 if let Some(new_selected_ranges) = new_selected_ranges {
23114 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23115 selections.select_ranges(new_selected_ranges)
23116 });
23117 this.backspace(&Default::default(), window, cx);
23118 }
23119
23120 this.handle_input(text, window, cx);
23121 });
23122
23123 if let Some(transaction) = self.ime_transaction {
23124 self.buffer.update(cx, |buffer, cx| {
23125 buffer.group_until_transaction(transaction, cx);
23126 });
23127 }
23128
23129 self.unmark_text(window, cx);
23130 }
23131
23132 fn replace_and_mark_text_in_range(
23133 &mut self,
23134 range_utf16: Option<Range<usize>>,
23135 text: &str,
23136 new_selected_range_utf16: Option<Range<usize>>,
23137 window: &mut Window,
23138 cx: &mut Context<Self>,
23139 ) {
23140 if !self.input_enabled {
23141 return;
23142 }
23143
23144 let transaction = self.transact(window, cx, |this, window, cx| {
23145 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23146 let snapshot = this.buffer.read(cx).read(cx);
23147 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23148 for marked_range in &mut marked_ranges {
23149 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23150 marked_range.start.0 += relative_range_utf16.start;
23151 marked_range.start =
23152 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23153 marked_range.end =
23154 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23155 }
23156 }
23157 Some(marked_ranges)
23158 } else if let Some(range_utf16) = range_utf16 {
23159 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23160 Some(this.selection_replacement_ranges(range_utf16, cx))
23161 } else {
23162 None
23163 };
23164
23165 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23166 let newest_selection_id = this.selections.newest_anchor().id;
23167 this.selections
23168 .all::<OffsetUtf16>(cx)
23169 .iter()
23170 .zip(ranges_to_replace.iter())
23171 .find_map(|(selection, range)| {
23172 if selection.id == newest_selection_id {
23173 Some(
23174 (range.start.0 as isize - selection.head().0 as isize)
23175 ..(range.end.0 as isize - selection.head().0 as isize),
23176 )
23177 } else {
23178 None
23179 }
23180 })
23181 });
23182
23183 cx.emit(EditorEvent::InputHandled {
23184 utf16_range_to_replace: range_to_replace,
23185 text: text.into(),
23186 });
23187
23188 if let Some(ranges) = ranges_to_replace {
23189 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23190 s.select_ranges(ranges)
23191 });
23192 }
23193
23194 let marked_ranges = {
23195 let snapshot = this.buffer.read(cx).read(cx);
23196 this.selections
23197 .disjoint_anchors()
23198 .iter()
23199 .map(|selection| {
23200 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23201 })
23202 .collect::<Vec<_>>()
23203 };
23204
23205 if text.is_empty() {
23206 this.unmark_text(window, cx);
23207 } else {
23208 this.highlight_text::<InputComposition>(
23209 marked_ranges.clone(),
23210 HighlightStyle {
23211 underline: Some(UnderlineStyle {
23212 thickness: px(1.),
23213 color: None,
23214 wavy: false,
23215 }),
23216 ..Default::default()
23217 },
23218 cx,
23219 );
23220 }
23221
23222 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23223 let use_autoclose = this.use_autoclose;
23224 let use_auto_surround = this.use_auto_surround;
23225 this.set_use_autoclose(false);
23226 this.set_use_auto_surround(false);
23227 this.handle_input(text, window, cx);
23228 this.set_use_autoclose(use_autoclose);
23229 this.set_use_auto_surround(use_auto_surround);
23230
23231 if let Some(new_selected_range) = new_selected_range_utf16 {
23232 let snapshot = this.buffer.read(cx).read(cx);
23233 let new_selected_ranges = marked_ranges
23234 .into_iter()
23235 .map(|marked_range| {
23236 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23237 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23238 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23239 snapshot.clip_offset_utf16(new_start, Bias::Left)
23240 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23241 })
23242 .collect::<Vec<_>>();
23243
23244 drop(snapshot);
23245 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23246 selections.select_ranges(new_selected_ranges)
23247 });
23248 }
23249 });
23250
23251 self.ime_transaction = self.ime_transaction.or(transaction);
23252 if let Some(transaction) = self.ime_transaction {
23253 self.buffer.update(cx, |buffer, cx| {
23254 buffer.group_until_transaction(transaction, cx);
23255 });
23256 }
23257
23258 if self.text_highlights::<InputComposition>(cx).is_none() {
23259 self.ime_transaction.take();
23260 }
23261 }
23262
23263 fn bounds_for_range(
23264 &mut self,
23265 range_utf16: Range<usize>,
23266 element_bounds: gpui::Bounds<Pixels>,
23267 window: &mut Window,
23268 cx: &mut Context<Self>,
23269 ) -> Option<gpui::Bounds<Pixels>> {
23270 let text_layout_details = self.text_layout_details(window);
23271 let CharacterDimensions {
23272 em_width,
23273 em_advance,
23274 line_height,
23275 } = self.character_dimensions(window);
23276
23277 let snapshot = self.snapshot(window, cx);
23278 let scroll_position = snapshot.scroll_position();
23279 let scroll_left = scroll_position.x * em_advance;
23280
23281 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23282 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23283 + self.gutter_dimensions.full_width();
23284 let y = line_height * (start.row().as_f32() - scroll_position.y);
23285
23286 Some(Bounds {
23287 origin: element_bounds.origin + point(x, y),
23288 size: size(em_width, line_height),
23289 })
23290 }
23291
23292 fn character_index_for_point(
23293 &mut self,
23294 point: gpui::Point<Pixels>,
23295 _window: &mut Window,
23296 _cx: &mut Context<Self>,
23297 ) -> Option<usize> {
23298 let position_map = self.last_position_map.as_ref()?;
23299 if !position_map.text_hitbox.contains(&point) {
23300 return None;
23301 }
23302 let display_point = position_map.point_for_position(point).previous_valid;
23303 let anchor = position_map
23304 .snapshot
23305 .display_point_to_anchor(display_point, Bias::Left);
23306 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23307 Some(utf16_offset.0)
23308 }
23309}
23310
23311trait SelectionExt {
23312 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23313 fn spanned_rows(
23314 &self,
23315 include_end_if_at_line_start: bool,
23316 map: &DisplaySnapshot,
23317 ) -> Range<MultiBufferRow>;
23318}
23319
23320impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23321 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23322 let start = self
23323 .start
23324 .to_point(&map.buffer_snapshot)
23325 .to_display_point(map);
23326 let end = self
23327 .end
23328 .to_point(&map.buffer_snapshot)
23329 .to_display_point(map);
23330 if self.reversed {
23331 end..start
23332 } else {
23333 start..end
23334 }
23335 }
23336
23337 fn spanned_rows(
23338 &self,
23339 include_end_if_at_line_start: bool,
23340 map: &DisplaySnapshot,
23341 ) -> Range<MultiBufferRow> {
23342 let start = self.start.to_point(&map.buffer_snapshot);
23343 let mut end = self.end.to_point(&map.buffer_snapshot);
23344 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23345 end.row -= 1;
23346 }
23347
23348 let buffer_start = map.prev_line_boundary(start).0;
23349 let buffer_end = map.next_line_boundary(end).0;
23350 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23351 }
23352}
23353
23354impl<T: InvalidationRegion> InvalidationStack<T> {
23355 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23356 where
23357 S: Clone + ToOffset,
23358 {
23359 while let Some(region) = self.last() {
23360 let all_selections_inside_invalidation_ranges =
23361 if selections.len() == region.ranges().len() {
23362 selections
23363 .iter()
23364 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23365 .all(|(selection, invalidation_range)| {
23366 let head = selection.head().to_offset(buffer);
23367 invalidation_range.start <= head && invalidation_range.end >= head
23368 })
23369 } else {
23370 false
23371 };
23372
23373 if all_selections_inside_invalidation_ranges {
23374 break;
23375 } else {
23376 self.pop();
23377 }
23378 }
23379 }
23380}
23381
23382impl<T> Default for InvalidationStack<T> {
23383 fn default() -> Self {
23384 Self(Default::default())
23385 }
23386}
23387
23388impl<T> Deref for InvalidationStack<T> {
23389 type Target = Vec<T>;
23390
23391 fn deref(&self) -> &Self::Target {
23392 &self.0
23393 }
23394}
23395
23396impl<T> DerefMut for InvalidationStack<T> {
23397 fn deref_mut(&mut self) -> &mut Self::Target {
23398 &mut self.0
23399 }
23400}
23401
23402impl InvalidationRegion for SnippetState {
23403 fn ranges(&self) -> &[Range<Anchor>] {
23404 &self.ranges[self.active_index]
23405 }
23406}
23407
23408fn edit_prediction_edit_text(
23409 current_snapshot: &BufferSnapshot,
23410 edits: &[(Range<Anchor>, String)],
23411 edit_preview: &EditPreview,
23412 include_deletions: bool,
23413 cx: &App,
23414) -> HighlightedText {
23415 let edits = edits
23416 .iter()
23417 .map(|(anchor, text)| {
23418 (
23419 anchor.start.text_anchor..anchor.end.text_anchor,
23420 text.clone(),
23421 )
23422 })
23423 .collect::<Vec<_>>();
23424
23425 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23426}
23427
23428fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23429 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23430 // Just show the raw edit text with basic styling
23431 let mut text = String::new();
23432 let mut highlights = Vec::new();
23433
23434 let insertion_highlight_style = HighlightStyle {
23435 color: Some(cx.theme().colors().text),
23436 ..Default::default()
23437 };
23438
23439 for (_, edit_text) in edits {
23440 let start_offset = text.len();
23441 text.push_str(edit_text);
23442 let end_offset = text.len();
23443
23444 if start_offset < end_offset {
23445 highlights.push((start_offset..end_offset, insertion_highlight_style));
23446 }
23447 }
23448
23449 HighlightedText {
23450 text: text.into(),
23451 highlights,
23452 }
23453}
23454
23455pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23456 match severity {
23457 lsp::DiagnosticSeverity::ERROR => colors.error,
23458 lsp::DiagnosticSeverity::WARNING => colors.warning,
23459 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23460 lsp::DiagnosticSeverity::HINT => colors.info,
23461 _ => colors.ignored,
23462 }
23463}
23464
23465pub fn styled_runs_for_code_label<'a>(
23466 label: &'a CodeLabel,
23467 syntax_theme: &'a theme::SyntaxTheme,
23468) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23469 let fade_out = HighlightStyle {
23470 fade_out: Some(0.35),
23471 ..Default::default()
23472 };
23473
23474 let mut prev_end = label.filter_range.end;
23475 label
23476 .runs
23477 .iter()
23478 .enumerate()
23479 .flat_map(move |(ix, (range, highlight_id))| {
23480 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23481 style
23482 } else {
23483 return Default::default();
23484 };
23485 let mut muted_style = style;
23486 muted_style.highlight(fade_out);
23487
23488 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23489 if range.start >= label.filter_range.end {
23490 if range.start > prev_end {
23491 runs.push((prev_end..range.start, fade_out));
23492 }
23493 runs.push((range.clone(), muted_style));
23494 } else if range.end <= label.filter_range.end {
23495 runs.push((range.clone(), style));
23496 } else {
23497 runs.push((range.start..label.filter_range.end, style));
23498 runs.push((label.filter_range.end..range.end, muted_style));
23499 }
23500 prev_end = cmp::max(prev_end, range.end);
23501
23502 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23503 runs.push((prev_end..label.text.len(), fade_out));
23504 }
23505
23506 runs
23507 })
23508}
23509
23510pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23511 let mut prev_index = 0;
23512 let mut prev_codepoint: Option<char> = None;
23513 text.char_indices()
23514 .chain([(text.len(), '\0')])
23515 .filter_map(move |(index, codepoint)| {
23516 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23517 let is_boundary = index == text.len()
23518 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23519 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23520 if is_boundary {
23521 let chunk = &text[prev_index..index];
23522 prev_index = index;
23523 Some(chunk)
23524 } else {
23525 None
23526 }
23527 })
23528}
23529
23530pub trait RangeToAnchorExt: Sized {
23531 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23532
23533 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23534 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23535 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23536 }
23537}
23538
23539impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23540 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23541 let start_offset = self.start.to_offset(snapshot);
23542 let end_offset = self.end.to_offset(snapshot);
23543 if start_offset == end_offset {
23544 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23545 } else {
23546 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23547 }
23548 }
23549}
23550
23551pub trait RowExt {
23552 fn as_f32(&self) -> f32;
23553
23554 fn next_row(&self) -> Self;
23555
23556 fn previous_row(&self) -> Self;
23557
23558 fn minus(&self, other: Self) -> u32;
23559}
23560
23561impl RowExt for DisplayRow {
23562 fn as_f32(&self) -> f32 {
23563 self.0 as f32
23564 }
23565
23566 fn next_row(&self) -> Self {
23567 Self(self.0 + 1)
23568 }
23569
23570 fn previous_row(&self) -> Self {
23571 Self(self.0.saturating_sub(1))
23572 }
23573
23574 fn minus(&self, other: Self) -> u32 {
23575 self.0 - other.0
23576 }
23577}
23578
23579impl RowExt for MultiBufferRow {
23580 fn as_f32(&self) -> f32 {
23581 self.0 as f32
23582 }
23583
23584 fn next_row(&self) -> Self {
23585 Self(self.0 + 1)
23586 }
23587
23588 fn previous_row(&self) -> Self {
23589 Self(self.0.saturating_sub(1))
23590 }
23591
23592 fn minus(&self, other: Self) -> u32 {
23593 self.0 - other.0
23594 }
23595}
23596
23597trait RowRangeExt {
23598 type Row;
23599
23600 fn len(&self) -> usize;
23601
23602 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23603}
23604
23605impl RowRangeExt for Range<MultiBufferRow> {
23606 type Row = MultiBufferRow;
23607
23608 fn len(&self) -> usize {
23609 (self.end.0 - self.start.0) as usize
23610 }
23611
23612 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23613 (self.start.0..self.end.0).map(MultiBufferRow)
23614 }
23615}
23616
23617impl RowRangeExt for Range<DisplayRow> {
23618 type Row = DisplayRow;
23619
23620 fn len(&self) -> usize {
23621 (self.end.0 - self.start.0) as usize
23622 }
23623
23624 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23625 (self.start.0..self.end.0).map(DisplayRow)
23626 }
23627}
23628
23629/// If select range has more than one line, we
23630/// just point the cursor to range.start.
23631fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23632 if range.start.row == range.end.row {
23633 range
23634 } else {
23635 range.start..range.start
23636 }
23637}
23638pub struct KillRing(ClipboardItem);
23639impl Global for KillRing {}
23640
23641const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23642
23643enum BreakpointPromptEditAction {
23644 Log,
23645 Condition,
23646 HitCondition,
23647}
23648
23649struct BreakpointPromptEditor {
23650 pub(crate) prompt: Entity<Editor>,
23651 editor: WeakEntity<Editor>,
23652 breakpoint_anchor: Anchor,
23653 breakpoint: Breakpoint,
23654 edit_action: BreakpointPromptEditAction,
23655 block_ids: HashSet<CustomBlockId>,
23656 editor_margins: Arc<Mutex<EditorMargins>>,
23657 _subscriptions: Vec<Subscription>,
23658}
23659
23660impl BreakpointPromptEditor {
23661 const MAX_LINES: u8 = 4;
23662
23663 fn new(
23664 editor: WeakEntity<Editor>,
23665 breakpoint_anchor: Anchor,
23666 breakpoint: Breakpoint,
23667 edit_action: BreakpointPromptEditAction,
23668 window: &mut Window,
23669 cx: &mut Context<Self>,
23670 ) -> Self {
23671 let base_text = match edit_action {
23672 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23673 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23674 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23675 }
23676 .map(|msg| msg.to_string())
23677 .unwrap_or_default();
23678
23679 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23680 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23681
23682 let prompt = cx.new(|cx| {
23683 let mut prompt = Editor::new(
23684 EditorMode::AutoHeight {
23685 min_lines: 1,
23686 max_lines: Some(Self::MAX_LINES as usize),
23687 },
23688 buffer,
23689 None,
23690 window,
23691 cx,
23692 );
23693 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23694 prompt.set_show_cursor_when_unfocused(false, cx);
23695 prompt.set_placeholder_text(
23696 match edit_action {
23697 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23698 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23699 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23700 },
23701 cx,
23702 );
23703
23704 prompt
23705 });
23706
23707 Self {
23708 prompt,
23709 editor,
23710 breakpoint_anchor,
23711 breakpoint,
23712 edit_action,
23713 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23714 block_ids: Default::default(),
23715 _subscriptions: vec![],
23716 }
23717 }
23718
23719 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23720 self.block_ids.extend(block_ids)
23721 }
23722
23723 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23724 if let Some(editor) = self.editor.upgrade() {
23725 let message = self
23726 .prompt
23727 .read(cx)
23728 .buffer
23729 .read(cx)
23730 .as_singleton()
23731 .expect("A multi buffer in breakpoint prompt isn't possible")
23732 .read(cx)
23733 .as_rope()
23734 .to_string();
23735
23736 editor.update(cx, |editor, cx| {
23737 editor.edit_breakpoint_at_anchor(
23738 self.breakpoint_anchor,
23739 self.breakpoint.clone(),
23740 match self.edit_action {
23741 BreakpointPromptEditAction::Log => {
23742 BreakpointEditAction::EditLogMessage(message.into())
23743 }
23744 BreakpointPromptEditAction::Condition => {
23745 BreakpointEditAction::EditCondition(message.into())
23746 }
23747 BreakpointPromptEditAction::HitCondition => {
23748 BreakpointEditAction::EditHitCondition(message.into())
23749 }
23750 },
23751 cx,
23752 );
23753
23754 editor.remove_blocks(self.block_ids.clone(), None, cx);
23755 cx.focus_self(window);
23756 });
23757 }
23758 }
23759
23760 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23761 self.editor
23762 .update(cx, |editor, cx| {
23763 editor.remove_blocks(self.block_ids.clone(), None, cx);
23764 window.focus(&editor.focus_handle);
23765 })
23766 .log_err();
23767 }
23768
23769 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23770 let settings = ThemeSettings::get_global(cx);
23771 let text_style = TextStyle {
23772 color: if self.prompt.read(cx).read_only(cx) {
23773 cx.theme().colors().text_disabled
23774 } else {
23775 cx.theme().colors().text
23776 },
23777 font_family: settings.buffer_font.family.clone(),
23778 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23779 font_size: settings.buffer_font_size(cx).into(),
23780 font_weight: settings.buffer_font.weight,
23781 line_height: relative(settings.buffer_line_height.value()),
23782 ..Default::default()
23783 };
23784 EditorElement::new(
23785 &self.prompt,
23786 EditorStyle {
23787 background: cx.theme().colors().editor_background,
23788 local_player: cx.theme().players().local(),
23789 text: text_style,
23790 ..Default::default()
23791 },
23792 )
23793 }
23794}
23795
23796impl Render for BreakpointPromptEditor {
23797 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23798 let editor_margins = *self.editor_margins.lock();
23799 let gutter_dimensions = editor_margins.gutter;
23800 h_flex()
23801 .key_context("Editor")
23802 .bg(cx.theme().colors().editor_background)
23803 .border_y_1()
23804 .border_color(cx.theme().status().info_border)
23805 .size_full()
23806 .py(window.line_height() / 2.5)
23807 .on_action(cx.listener(Self::confirm))
23808 .on_action(cx.listener(Self::cancel))
23809 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23810 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23811 }
23812}
23813
23814impl Focusable for BreakpointPromptEditor {
23815 fn focus_handle(&self, cx: &App) -> FocusHandle {
23816 self.prompt.focus_handle(cx)
23817 }
23818}
23819
23820fn all_edits_insertions_or_deletions(
23821 edits: &Vec<(Range<Anchor>, String)>,
23822 snapshot: &MultiBufferSnapshot,
23823) -> bool {
23824 let mut all_insertions = true;
23825 let mut all_deletions = true;
23826
23827 for (range, new_text) in edits.iter() {
23828 let range_is_empty = range.to_offset(snapshot).is_empty();
23829 let text_is_empty = new_text.is_empty();
23830
23831 if range_is_empty != text_is_empty {
23832 if range_is_empty {
23833 all_deletions = false;
23834 } else {
23835 all_insertions = false;
23836 }
23837 } else {
23838 return false;
23839 }
23840
23841 if !all_insertions && !all_deletions {
23842 return false;
23843 }
23844 }
23845 all_insertions || all_deletions
23846}
23847
23848struct MissingEditPredictionKeybindingTooltip;
23849
23850impl Render for MissingEditPredictionKeybindingTooltip {
23851 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23852 ui::tooltip_container(window, cx, |container, _, cx| {
23853 container
23854 .flex_shrink_0()
23855 .max_w_80()
23856 .min_h(rems_from_px(124.))
23857 .justify_between()
23858 .child(
23859 v_flex()
23860 .flex_1()
23861 .text_ui_sm(cx)
23862 .child(Label::new("Conflict with Accept Keybinding"))
23863 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23864 )
23865 .child(
23866 h_flex()
23867 .pb_1()
23868 .gap_1()
23869 .items_end()
23870 .w_full()
23871 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23872 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23873 }))
23874 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23875 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23876 })),
23877 )
23878 })
23879 }
23880}
23881
23882#[derive(Debug, Clone, Copy, PartialEq)]
23883pub struct LineHighlight {
23884 pub background: Background,
23885 pub border: Option<gpui::Hsla>,
23886 pub include_gutter: bool,
23887 pub type_id: Option<TypeId>,
23888}
23889
23890struct LineManipulationResult {
23891 pub new_text: String,
23892 pub line_count_before: usize,
23893 pub line_count_after: usize,
23894}
23895
23896fn render_diff_hunk_controls(
23897 row: u32,
23898 status: &DiffHunkStatus,
23899 hunk_range: Range<Anchor>,
23900 is_created_file: bool,
23901 line_height: Pixels,
23902 editor: &Entity<Editor>,
23903 _window: &mut Window,
23904 cx: &mut App,
23905) -> AnyElement {
23906 h_flex()
23907 .h(line_height)
23908 .mr_1()
23909 .gap_1()
23910 .px_0p5()
23911 .pb_1()
23912 .border_x_1()
23913 .border_b_1()
23914 .border_color(cx.theme().colors().border_variant)
23915 .rounded_b_lg()
23916 .bg(cx.theme().colors().editor_background)
23917 .gap_1()
23918 .block_mouse_except_scroll()
23919 .shadow_md()
23920 .child(if status.has_secondary_hunk() {
23921 Button::new(("stage", row as u64), "Stage")
23922 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23923 .tooltip({
23924 let focus_handle = editor.focus_handle(cx);
23925 move |window, cx| {
23926 Tooltip::for_action_in(
23927 "Stage Hunk",
23928 &::git::ToggleStaged,
23929 &focus_handle,
23930 window,
23931 cx,
23932 )
23933 }
23934 })
23935 .on_click({
23936 let editor = editor.clone();
23937 move |_event, _window, cx| {
23938 editor.update(cx, |editor, cx| {
23939 editor.stage_or_unstage_diff_hunks(
23940 true,
23941 vec![hunk_range.start..hunk_range.start],
23942 cx,
23943 );
23944 });
23945 }
23946 })
23947 } else {
23948 Button::new(("unstage", row as u64), "Unstage")
23949 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23950 .tooltip({
23951 let focus_handle = editor.focus_handle(cx);
23952 move |window, cx| {
23953 Tooltip::for_action_in(
23954 "Unstage Hunk",
23955 &::git::ToggleStaged,
23956 &focus_handle,
23957 window,
23958 cx,
23959 )
23960 }
23961 })
23962 .on_click({
23963 let editor = editor.clone();
23964 move |_event, _window, cx| {
23965 editor.update(cx, |editor, cx| {
23966 editor.stage_or_unstage_diff_hunks(
23967 false,
23968 vec![hunk_range.start..hunk_range.start],
23969 cx,
23970 );
23971 });
23972 }
23973 })
23974 })
23975 .child(
23976 Button::new(("restore", row as u64), "Restore")
23977 .tooltip({
23978 let focus_handle = editor.focus_handle(cx);
23979 move |window, cx| {
23980 Tooltip::for_action_in(
23981 "Restore Hunk",
23982 &::git::Restore,
23983 &focus_handle,
23984 window,
23985 cx,
23986 )
23987 }
23988 })
23989 .on_click({
23990 let editor = editor.clone();
23991 move |_event, window, cx| {
23992 editor.update(cx, |editor, cx| {
23993 let snapshot = editor.snapshot(window, cx);
23994 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23995 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23996 });
23997 }
23998 })
23999 .disabled(is_created_file),
24000 )
24001 .when(
24002 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24003 |el| {
24004 el.child(
24005 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24006 .shape(IconButtonShape::Square)
24007 .icon_size(IconSize::Small)
24008 // .disabled(!has_multiple_hunks)
24009 .tooltip({
24010 let focus_handle = editor.focus_handle(cx);
24011 move |window, cx| {
24012 Tooltip::for_action_in(
24013 "Next Hunk",
24014 &GoToHunk,
24015 &focus_handle,
24016 window,
24017 cx,
24018 )
24019 }
24020 })
24021 .on_click({
24022 let editor = editor.clone();
24023 move |_event, window, cx| {
24024 editor.update(cx, |editor, cx| {
24025 let snapshot = editor.snapshot(window, cx);
24026 let position =
24027 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24028 editor.go_to_hunk_before_or_after_position(
24029 &snapshot,
24030 position,
24031 Direction::Next,
24032 window,
24033 cx,
24034 );
24035 editor.expand_selected_diff_hunks(cx);
24036 });
24037 }
24038 }),
24039 )
24040 .child(
24041 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24042 .shape(IconButtonShape::Square)
24043 .icon_size(IconSize::Small)
24044 // .disabled(!has_multiple_hunks)
24045 .tooltip({
24046 let focus_handle = editor.focus_handle(cx);
24047 move |window, cx| {
24048 Tooltip::for_action_in(
24049 "Previous Hunk",
24050 &GoToPreviousHunk,
24051 &focus_handle,
24052 window,
24053 cx,
24054 )
24055 }
24056 })
24057 .on_click({
24058 let editor = editor.clone();
24059 move |_event, window, cx| {
24060 editor.update(cx, |editor, cx| {
24061 let snapshot = editor.snapshot(window, cx);
24062 let point =
24063 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24064 editor.go_to_hunk_before_or_after_position(
24065 &snapshot,
24066 point,
24067 Direction::Prev,
24068 window,
24069 cx,
24070 );
24071 editor.expand_selected_diff_hunks(cx);
24072 });
24073 }
24074 }),
24075 )
24076 },
24077 )
24078 .into_any_element()
24079}
24080
24081pub fn multibuffer_context_lines(cx: &App) -> u32 {
24082 EditorSettings::try_get(cx)
24083 .map(|settings| settings.excerpt_context_lines)
24084 .unwrap_or(2)
24085 .clamp(1, 32)
24086}