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;
222pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253enum ReportEditorEvent {
254 Saved { auto_saved: bool },
255 EditorOpened,
256 Closed,
257}
258
259impl ReportEditorEvent {
260 pub fn event_type(&self) -> &'static str {
261 match self {
262 Self::Saved { .. } => "Editor Saved",
263 Self::EditorOpened => "Editor Opened",
264 Self::Closed => "Editor Closed",
265 }
266 }
267}
268
269struct InlineValueCache {
270 enabled: bool,
271 inlays: Vec<InlayId>,
272 refresh_task: Task<Option<()>>,
273}
274
275impl InlineValueCache {
276 fn new(enabled: bool) -> Self {
277 Self {
278 enabled,
279 inlays: Vec::new(),
280 refresh_task: Task::ready(None),
281 }
282 }
283}
284
285#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
286pub enum InlayId {
287 EditPrediction(usize),
288 DebuggerValue(usize),
289 // LSP
290 Hint(usize),
291 Color(usize),
292}
293
294impl InlayId {
295 fn id(&self) -> usize {
296 match self {
297 Self::EditPrediction(id) => *id,
298 Self::DebuggerValue(id) => *id,
299 Self::Hint(id) => *id,
300 Self::Color(id) => *id,
301 }
302 }
303}
304
305pub enum ActiveDebugLine {}
306pub enum DebugStackFrameLine {}
307enum DocumentHighlightRead {}
308enum DocumentHighlightWrite {}
309enum InputComposition {}
310pub enum PendingInput {}
311enum SelectedTextHighlight {}
312
313pub enum ConflictsOuter {}
314pub enum ConflictsOurs {}
315pub enum ConflictsTheirs {}
316pub enum ConflictsOursMarker {}
317pub enum ConflictsTheirsMarker {}
318
319#[derive(Debug, Copy, Clone, PartialEq, Eq)]
320pub enum Navigated {
321 Yes,
322 No,
323}
324
325impl Navigated {
326 pub fn from_bool(yes: bool) -> Navigated {
327 if yes { Navigated::Yes } else { Navigated::No }
328 }
329}
330
331#[derive(Debug, Clone, PartialEq, Eq)]
332enum DisplayDiffHunk {
333 Folded {
334 display_row: DisplayRow,
335 },
336 Unfolded {
337 is_created_file: bool,
338 diff_base_byte_range: Range<usize>,
339 display_row_range: Range<DisplayRow>,
340 multi_buffer_range: Range<Anchor>,
341 status: DiffHunkStatus,
342 },
343}
344
345pub enum HideMouseCursorOrigin {
346 TypingAction,
347 MovementAction,
348}
349
350pub fn init_settings(cx: &mut App) {
351 EditorSettings::register(cx);
352}
353
354pub fn init(cx: &mut App) {
355 init_settings(cx);
356
357 cx.set_global(GlobalBlameRenderer(Arc::new(())));
358
359 workspace::register_project_item::<Editor>(cx);
360 workspace::FollowableViewRegistry::register::<Editor>(cx);
361 workspace::register_serializable_item::<Editor>(cx);
362
363 cx.observe_new(
364 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
365 workspace.register_action(Editor::new_file);
366 workspace.register_action(Editor::new_file_vertical);
367 workspace.register_action(Editor::new_file_horizontal);
368 workspace.register_action(Editor::cancel_language_server_work);
369 workspace.register_action(Editor::toggle_focus);
370 },
371 )
372 .detach();
373
374 cx.on_action(move |_: &workspace::NewFile, cx| {
375 let app_state = workspace::AppState::global(cx);
376 if let Some(app_state) = app_state.upgrade() {
377 workspace::open_new(
378 Default::default(),
379 app_state,
380 cx,
381 |workspace, window, cx| {
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388 cx.on_action(move |_: &workspace::NewWindow, cx| {
389 let app_state = workspace::AppState::global(cx);
390 if let Some(app_state) = app_state.upgrade() {
391 workspace::open_new(
392 Default::default(),
393 app_state,
394 cx,
395 |workspace, window, cx| {
396 cx.activate(true);
397 Editor::new_file(workspace, &Default::default(), window, cx)
398 },
399 )
400 .detach();
401 }
402 });
403}
404
405pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
406 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
407}
408
409pub trait DiagnosticRenderer {
410 fn render_group(
411 &self,
412 diagnostic_group: Vec<DiagnosticEntry<Point>>,
413 buffer_id: BufferId,
414 snapshot: EditorSnapshot,
415 editor: WeakEntity<Editor>,
416 cx: &mut App,
417 ) -> Vec<BlockProperties<Anchor>>;
418
419 fn render_hover(
420 &self,
421 diagnostic_group: Vec<DiagnosticEntry<Point>>,
422 range: Range<Point>,
423 buffer_id: BufferId,
424 cx: &mut App,
425 ) -> Option<Entity<markdown::Markdown>>;
426
427 fn open_link(
428 &self,
429 editor: &mut Editor,
430 link: SharedString,
431 window: &mut Window,
432 cx: &mut Context<Editor>,
433 );
434}
435
436pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
437
438impl GlobalDiagnosticRenderer {
439 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
440 cx.try_global::<Self>().map(|g| g.0.clone())
441 }
442}
443
444impl gpui::Global for GlobalDiagnosticRenderer {}
445pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
446 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
447}
448
449pub struct SearchWithinRange;
450
451trait InvalidationRegion {
452 fn ranges(&self) -> &[Range<Anchor>];
453}
454
455#[derive(Clone, Debug, PartialEq)]
456pub enum SelectPhase {
457 Begin {
458 position: DisplayPoint,
459 add: bool,
460 click_count: usize,
461 },
462 BeginColumnar {
463 position: DisplayPoint,
464 reset: bool,
465 mode: ColumnarMode,
466 goal_column: u32,
467 },
468 Extend {
469 position: DisplayPoint,
470 click_count: usize,
471 },
472 Update {
473 position: DisplayPoint,
474 goal_column: u32,
475 scroll_delta: gpui::Point<f32>,
476 },
477 End,
478}
479
480#[derive(Clone, Debug, PartialEq)]
481pub enum ColumnarMode {
482 FromMouse,
483 FromSelection,
484}
485
486#[derive(Clone, Debug)]
487pub enum SelectMode {
488 Character,
489 Word(Range<Anchor>),
490 Line(Range<Anchor>),
491 All,
492}
493
494#[derive(Clone, PartialEq, Eq, Debug)]
495pub enum EditorMode {
496 SingleLine,
497 AutoHeight {
498 min_lines: usize,
499 max_lines: Option<usize>,
500 },
501 Full {
502 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
503 scale_ui_elements_with_buffer_font_size: bool,
504 /// When set to `true`, the editor will render a background for the active line.
505 show_active_line_background: bool,
506 /// When set to `true`, the editor's height will be determined by its content.
507 sized_by_content: bool,
508 },
509 Minimap {
510 parent: WeakEntity<Editor>,
511 },
512}
513
514impl EditorMode {
515 pub fn full() -> Self {
516 Self::Full {
517 scale_ui_elements_with_buffer_font_size: true,
518 show_active_line_background: true,
519 sized_by_content: false,
520 }
521 }
522
523 #[inline]
524 pub fn is_full(&self) -> bool {
525 matches!(self, Self::Full { .. })
526 }
527
528 #[inline]
529 pub fn is_single_line(&self) -> bool {
530 matches!(self, Self::SingleLine { .. })
531 }
532
533 #[inline]
534 fn is_minimap(&self) -> bool {
535 matches!(self, Self::Minimap { .. })
536 }
537}
538
539#[derive(Copy, Clone, Debug)]
540pub enum SoftWrap {
541 /// Prefer not to wrap at all.
542 ///
543 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
544 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
545 GitDiff,
546 /// Prefer a single line generally, unless an overly long line is encountered.
547 None,
548 /// Soft wrap lines that exceed the editor width.
549 EditorWidth,
550 /// Soft wrap lines at the preferred line length.
551 Column(u32),
552 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
553 Bounded(u32),
554}
555
556#[derive(Clone)]
557pub struct EditorStyle {
558 pub background: Hsla,
559 pub border: Hsla,
560 pub local_player: PlayerColor,
561 pub text: TextStyle,
562 pub scrollbar_width: Pixels,
563 pub syntax: Arc<SyntaxTheme>,
564 pub status: StatusColors,
565 pub inlay_hints_style: HighlightStyle,
566 pub edit_prediction_styles: EditPredictionStyles,
567 pub unnecessary_code_fade: f32,
568 pub show_underlines: bool,
569}
570
571impl Default for EditorStyle {
572 fn default() -> Self {
573 Self {
574 background: Hsla::default(),
575 border: Hsla::default(),
576 local_player: PlayerColor::default(),
577 text: TextStyle::default(),
578 scrollbar_width: Pixels::default(),
579 syntax: Default::default(),
580 // HACK: Status colors don't have a real default.
581 // We should look into removing the status colors from the editor
582 // style and retrieve them directly from the theme.
583 status: StatusColors::dark(),
584 inlay_hints_style: HighlightStyle::default(),
585 edit_prediction_styles: EditPredictionStyles {
586 insertion: HighlightStyle::default(),
587 whitespace: HighlightStyle::default(),
588 },
589 unnecessary_code_fade: Default::default(),
590 show_underlines: true,
591 }
592 }
593}
594
595pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
596 let show_background = language_settings::language_settings(None, None, cx)
597 .inlay_hints
598 .show_background;
599
600 HighlightStyle {
601 color: Some(cx.theme().status().hint),
602 background_color: show_background.then(|| cx.theme().status().hint_background),
603 ..HighlightStyle::default()
604 }
605}
606
607pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
608 EditPredictionStyles {
609 insertion: HighlightStyle {
610 color: Some(cx.theme().status().predictive),
611 ..HighlightStyle::default()
612 },
613 whitespace: HighlightStyle {
614 background_color: Some(cx.theme().status().created_background),
615 ..HighlightStyle::default()
616 },
617 }
618}
619
620type CompletionId = usize;
621
622pub(crate) enum EditDisplayMode {
623 TabAccept,
624 DiffPopover,
625 Inline,
626}
627
628enum EditPrediction {
629 Edit {
630 edits: Vec<(Range<Anchor>, String)>,
631 edit_preview: Option<EditPreview>,
632 display_mode: EditDisplayMode,
633 snapshot: BufferSnapshot,
634 },
635 Move {
636 target: Anchor,
637 snapshot: BufferSnapshot,
638 },
639}
640
641struct EditPredictionState {
642 inlay_ids: Vec<InlayId>,
643 completion: EditPrediction,
644 completion_id: Option<SharedString>,
645 invalidation_range: Range<Anchor>,
646}
647
648enum EditPredictionSettings {
649 Disabled,
650 Enabled {
651 show_in_menu: bool,
652 preview_requires_modifier: bool,
653 },
654}
655
656enum EditPredictionHighlight {}
657
658#[derive(Debug, Clone)]
659struct InlineDiagnostic {
660 message: SharedString,
661 group_id: usize,
662 is_primary: bool,
663 start: Point,
664 severity: lsp::DiagnosticSeverity,
665}
666
667pub enum MenuEditPredictionsPolicy {
668 Never,
669 ByProvider,
670}
671
672pub enum EditPredictionPreview {
673 /// Modifier is not pressed
674 Inactive { released_too_fast: bool },
675 /// Modifier pressed
676 Active {
677 since: Instant,
678 previous_scroll_position: Option<ScrollAnchor>,
679 },
680}
681
682impl EditPredictionPreview {
683 pub fn released_too_fast(&self) -> bool {
684 match self {
685 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
686 EditPredictionPreview::Active { .. } => false,
687 }
688 }
689
690 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
691 if let EditPredictionPreview::Active {
692 previous_scroll_position,
693 ..
694 } = self
695 {
696 *previous_scroll_position = scroll_position;
697 }
698 }
699}
700
701pub struct ContextMenuOptions {
702 pub min_entries_visible: usize,
703 pub max_entries_visible: usize,
704 pub placement: Option<ContextMenuPlacement>,
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708pub enum ContextMenuPlacement {
709 Above,
710 Below,
711}
712
713#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
714struct EditorActionId(usize);
715
716impl EditorActionId {
717 pub fn post_inc(&mut self) -> Self {
718 let answer = self.0;
719
720 *self = Self(answer + 1);
721
722 Self(answer)
723 }
724}
725
726// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
727// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
728
729type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
730type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
731
732#[derive(Default)]
733struct ScrollbarMarkerState {
734 scrollbar_size: Size<Pixels>,
735 dirty: bool,
736 markers: Arc<[PaintQuad]>,
737 pending_refresh: Option<Task<Result<()>>>,
738}
739
740impl ScrollbarMarkerState {
741 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
742 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
743 }
744}
745
746#[derive(Clone, Copy, PartialEq, Eq)]
747pub enum MinimapVisibility {
748 Disabled,
749 Enabled {
750 /// The configuration currently present in the users settings.
751 setting_configuration: bool,
752 /// Whether to override the currently set visibility from the users setting.
753 toggle_override: bool,
754 },
755}
756
757impl MinimapVisibility {
758 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
759 if mode.is_full() {
760 Self::Enabled {
761 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
762 toggle_override: false,
763 }
764 } else {
765 Self::Disabled
766 }
767 }
768
769 fn hidden(&self) -> Self {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 ..
774 } => Self::Enabled {
775 setting_configuration,
776 toggle_override: setting_configuration,
777 },
778 Self::Disabled => Self::Disabled,
779 }
780 }
781
782 fn disabled(&self) -> bool {
783 matches!(*self, Self::Disabled)
784 }
785
786 fn settings_visibility(&self) -> bool {
787 match *self {
788 Self::Enabled {
789 setting_configuration,
790 ..
791 } => setting_configuration,
792 _ => false,
793 }
794 }
795
796 fn visible(&self) -> bool {
797 match *self {
798 Self::Enabled {
799 setting_configuration,
800 toggle_override,
801 } => setting_configuration ^ toggle_override,
802 _ => false,
803 }
804 }
805
806 fn toggle_visibility(&self) -> Self {
807 match *self {
808 Self::Enabled {
809 toggle_override,
810 setting_configuration,
811 } => Self::Enabled {
812 setting_configuration,
813 toggle_override: !toggle_override,
814 },
815 Self::Disabled => Self::Disabled,
816 }
817 }
818}
819
820#[derive(Clone, Debug)]
821struct RunnableTasks {
822 templates: Vec<(TaskSourceKind, TaskTemplate)>,
823 offset: multi_buffer::Anchor,
824 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
825 column: u32,
826 // Values of all named captures, including those starting with '_'
827 extra_variables: HashMap<String, String>,
828 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
829 context_range: Range<BufferOffset>,
830}
831
832impl RunnableTasks {
833 fn resolve<'a>(
834 &'a self,
835 cx: &'a task::TaskContext,
836 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
837 self.templates.iter().filter_map(|(kind, template)| {
838 template
839 .resolve_task(&kind.to_id_base(), cx)
840 .map(|task| (kind.clone(), task))
841 })
842 }
843}
844
845#[derive(Clone)]
846pub struct ResolvedTasks {
847 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
848 position: Anchor,
849}
850
851#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
852struct BufferOffset(usize);
853
854// Addons allow storing per-editor state in other crates (e.g. Vim)
855pub trait Addon: 'static {
856 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
857
858 fn render_buffer_header_controls(
859 &self,
860 _: &ExcerptInfo,
861 _: &Window,
862 _: &App,
863 ) -> Option<AnyElement> {
864 None
865 }
866
867 fn to_any(&self) -> &dyn std::any::Any;
868
869 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
870 None
871 }
872}
873
874struct ChangeLocation {
875 current: Option<Vec<Anchor>>,
876 original: Vec<Anchor>,
877}
878impl ChangeLocation {
879 fn locations(&self) -> &[Anchor] {
880 self.current.as_ref().unwrap_or(&self.original)
881 }
882}
883
884/// A set of caret positions, registered when the editor was edited.
885pub struct ChangeList {
886 changes: Vec<ChangeLocation>,
887 /// Currently "selected" change.
888 position: Option<usize>,
889}
890
891impl ChangeList {
892 pub fn new() -> Self {
893 Self {
894 changes: Vec::new(),
895 position: None,
896 }
897 }
898
899 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
900 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
901 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
902 if self.changes.is_empty() {
903 return None;
904 }
905
906 let prev = self.position.unwrap_or(self.changes.len());
907 let next = if direction == Direction::Prev {
908 prev.saturating_sub(count)
909 } else {
910 (prev + count).min(self.changes.len() - 1)
911 };
912 self.position = Some(next);
913 self.changes.get(next).map(|change| change.locations())
914 }
915
916 /// Adds a new change to the list, resetting the change list position.
917 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
918 self.position.take();
919 if let Some(last) = self.changes.last_mut()
920 && group
921 {
922 last.current = Some(new_positions)
923 } else {
924 self.changes.push(ChangeLocation {
925 original: new_positions,
926 current: None,
927 });
928 }
929 }
930
931 pub fn last(&self) -> Option<&[Anchor]> {
932 self.changes.last().map(|change| change.locations())
933 }
934
935 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
936 self.changes.last().map(|change| change.original.as_slice())
937 }
938
939 pub fn invert_last_group(&mut self) {
940 if let Some(last) = self.changes.last_mut()
941 && let Some(current) = last.current.as_mut()
942 {
943 mem::swap(&mut last.original, current);
944 }
945 }
946}
947
948#[derive(Clone)]
949struct InlineBlamePopoverState {
950 scroll_handle: ScrollHandle,
951 commit_message: Option<ParsedCommitMessage>,
952 markdown: Entity<Markdown>,
953}
954
955struct InlineBlamePopover {
956 position: gpui::Point<Pixels>,
957 hide_task: Option<Task<()>>,
958 popover_bounds: Option<Bounds<Pixels>>,
959 popover_state: InlineBlamePopoverState,
960 keyboard_grace: bool,
961}
962
963enum SelectionDragState {
964 /// State when no drag related activity is detected.
965 None,
966 /// State when the mouse is down on a selection that is about to be dragged.
967 ReadyToDrag {
968 selection: Selection<Anchor>,
969 click_position: gpui::Point<Pixels>,
970 mouse_down_time: Instant,
971 },
972 /// State when the mouse is dragging the selection in the editor.
973 Dragging {
974 selection: Selection<Anchor>,
975 drop_cursor: Selection<Anchor>,
976 hide_drop_cursor: bool,
977 },
978}
979
980enum ColumnarSelectionState {
981 FromMouse {
982 selection_tail: Anchor,
983 display_point: Option<DisplayPoint>,
984 },
985 FromSelection {
986 selection_tail: Anchor,
987 },
988}
989
990/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
991/// a breakpoint on them.
992#[derive(Clone, Copy, Debug, PartialEq, Eq)]
993struct PhantomBreakpointIndicator {
994 display_row: DisplayRow,
995 /// There's a small debounce between hovering over the line and showing the indicator.
996 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
997 is_active: bool,
998 collides_with_existing_breakpoint: bool,
999}
1000
1001/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1002///
1003/// See the [module level documentation](self) for more information.
1004pub struct Editor {
1005 focus_handle: FocusHandle,
1006 last_focused_descendant: Option<WeakFocusHandle>,
1007 /// The text buffer being edited
1008 buffer: Entity<MultiBuffer>,
1009 /// Map of how text in the buffer should be displayed.
1010 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1011 pub display_map: Entity<DisplayMap>,
1012 pub selections: SelectionsCollection,
1013 pub scroll_manager: ScrollManager,
1014 /// When inline assist editors are linked, they all render cursors because
1015 /// typing enters text into each of them, even the ones that aren't focused.
1016 pub(crate) show_cursor_when_unfocused: bool,
1017 columnar_selection_state: Option<ColumnarSelectionState>,
1018 add_selections_state: Option<AddSelectionsState>,
1019 select_next_state: Option<SelectNextState>,
1020 select_prev_state: Option<SelectNextState>,
1021 selection_history: SelectionHistory,
1022 defer_selection_effects: bool,
1023 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1024 autoclose_regions: Vec<AutocloseRegion>,
1025 snippet_stack: InvalidationStack<SnippetState>,
1026 select_syntax_node_history: SelectSyntaxNodeHistory,
1027 ime_transaction: Option<TransactionId>,
1028 pub diagnostics_max_severity: DiagnosticSeverity,
1029 active_diagnostics: ActiveDiagnostic,
1030 show_inline_diagnostics: bool,
1031 inline_diagnostics_update: Task<()>,
1032 inline_diagnostics_enabled: bool,
1033 diagnostics_enabled: bool,
1034 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1035 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1036 hard_wrap: Option<usize>,
1037 project: Option<Entity<Project>>,
1038 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1039 completion_provider: Option<Rc<dyn CompletionProvider>>,
1040 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1041 blink_manager: Entity<BlinkManager>,
1042 show_cursor_names: bool,
1043 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1044 pub show_local_selections: bool,
1045 mode: EditorMode,
1046 show_breadcrumbs: bool,
1047 show_gutter: bool,
1048 show_scrollbars: ScrollbarAxes,
1049 minimap_visibility: MinimapVisibility,
1050 offset_content: bool,
1051 disable_expand_excerpt_buttons: bool,
1052 show_line_numbers: Option<bool>,
1053 use_relative_line_numbers: Option<bool>,
1054 show_git_diff_gutter: Option<bool>,
1055 show_code_actions: Option<bool>,
1056 show_runnables: Option<bool>,
1057 show_breakpoints: Option<bool>,
1058 show_wrap_guides: Option<bool>,
1059 show_indent_guides: Option<bool>,
1060 placeholder_text: Option<Arc<str>>,
1061 highlight_order: usize,
1062 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1063 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1064 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1065 scrollbar_marker_state: ScrollbarMarkerState,
1066 active_indent_guides_state: ActiveIndentGuidesState,
1067 nav_history: Option<ItemNavHistory>,
1068 context_menu: RefCell<Option<CodeContextMenu>>,
1069 context_menu_options: Option<ContextMenuOptions>,
1070 mouse_context_menu: Option<MouseContextMenu>,
1071 completion_tasks: Vec<(CompletionId, Task<()>)>,
1072 inline_blame_popover: Option<InlineBlamePopover>,
1073 inline_blame_popover_show_task: Option<Task<()>>,
1074 signature_help_state: SignatureHelpState,
1075 auto_signature_help: Option<bool>,
1076 find_all_references_task_sources: Vec<Anchor>,
1077 next_completion_id: CompletionId,
1078 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1079 code_actions_task: Option<Task<Result<()>>>,
1080 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1081 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1082 document_highlights_task: Option<Task<()>>,
1083 linked_editing_range_task: Option<Task<Option<()>>>,
1084 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1085 pending_rename: Option<RenameState>,
1086 searchable: bool,
1087 cursor_shape: CursorShape,
1088 current_line_highlight: Option<CurrentLineHighlight>,
1089 collapse_matches: bool,
1090 autoindent_mode: Option<AutoindentMode>,
1091 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1092 input_enabled: bool,
1093 use_modal_editing: bool,
1094 read_only: bool,
1095 leader_id: Option<CollaboratorId>,
1096 remote_id: Option<ViewId>,
1097 pub hover_state: HoverState,
1098 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1099 gutter_hovered: bool,
1100 hovered_link_state: Option<HoveredLinkState>,
1101 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1102 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1103 active_edit_prediction: Option<EditPredictionState>,
1104 /// Used to prevent flickering as the user types while the menu is open
1105 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1106 edit_prediction_settings: EditPredictionSettings,
1107 edit_predictions_hidden_for_vim_mode: bool,
1108 show_edit_predictions_override: Option<bool>,
1109 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1110 edit_prediction_preview: EditPredictionPreview,
1111 edit_prediction_indent_conflict: bool,
1112 edit_prediction_requires_modifier_in_indent_conflict: bool,
1113 inlay_hint_cache: InlayHintCache,
1114 next_inlay_id: usize,
1115 _subscriptions: Vec<Subscription>,
1116 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1117 gutter_dimensions: GutterDimensions,
1118 style: Option<EditorStyle>,
1119 text_style_refinement: Option<TextStyleRefinement>,
1120 next_editor_action_id: EditorActionId,
1121 editor_actions: Rc<
1122 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1123 >,
1124 use_autoclose: bool,
1125 use_auto_surround: bool,
1126 auto_replace_emoji_shortcode: bool,
1127 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1128 show_git_blame_gutter: bool,
1129 show_git_blame_inline: bool,
1130 show_git_blame_inline_delay_task: Option<Task<()>>,
1131 git_blame_inline_enabled: bool,
1132 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1133 serialize_dirty_buffers: bool,
1134 show_selection_menu: Option<bool>,
1135 blame: Option<Entity<GitBlame>>,
1136 blame_subscription: Option<Subscription>,
1137 custom_context_menu: Option<
1138 Box<
1139 dyn 'static
1140 + Fn(
1141 &mut Self,
1142 DisplayPoint,
1143 &mut Window,
1144 &mut Context<Self>,
1145 ) -> Option<Entity<ui::ContextMenu>>,
1146 >,
1147 >,
1148 last_bounds: Option<Bounds<Pixels>>,
1149 last_position_map: Option<Rc<PositionMap>>,
1150 expect_bounds_change: Option<Bounds<Pixels>>,
1151 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1152 tasks_update_task: Option<Task<()>>,
1153 breakpoint_store: Option<Entity<BreakpointStore>>,
1154 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1155 hovered_diff_hunk_row: Option<DisplayRow>,
1156 pull_diagnostics_task: Task<()>,
1157 in_project_search: bool,
1158 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1159 breadcrumb_header: Option<String>,
1160 focused_block: Option<FocusedBlock>,
1161 next_scroll_position: NextScrollCursorCenterTopBottom,
1162 addons: HashMap<TypeId, Box<dyn Addon>>,
1163 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1164 load_diff_task: Option<Shared<Task<()>>>,
1165 /// Whether we are temporarily displaying a diff other than git's
1166 temporary_diff_override: bool,
1167 selection_mark_mode: bool,
1168 toggle_fold_multiple_buffers: Task<()>,
1169 _scroll_cursor_center_top_bottom_task: Task<()>,
1170 serialize_selections: Task<()>,
1171 serialize_folds: Task<()>,
1172 mouse_cursor_hidden: bool,
1173 minimap: Option<Entity<Self>>,
1174 hide_mouse_mode: HideMouseMode,
1175 pub change_list: ChangeList,
1176 inline_value_cache: InlineValueCache,
1177 selection_drag_state: SelectionDragState,
1178 next_color_inlay_id: usize,
1179 colors: Option<LspColorData>,
1180 folding_newlines: Task<()>,
1181}
1182
1183#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1184enum NextScrollCursorCenterTopBottom {
1185 #[default]
1186 Center,
1187 Top,
1188 Bottom,
1189}
1190
1191impl NextScrollCursorCenterTopBottom {
1192 fn next(&self) -> Self {
1193 match self {
1194 Self::Center => Self::Top,
1195 Self::Top => Self::Bottom,
1196 Self::Bottom => Self::Center,
1197 }
1198 }
1199}
1200
1201#[derive(Clone)]
1202pub struct EditorSnapshot {
1203 pub mode: EditorMode,
1204 show_gutter: bool,
1205 show_line_numbers: Option<bool>,
1206 show_git_diff_gutter: Option<bool>,
1207 show_code_actions: Option<bool>,
1208 show_runnables: Option<bool>,
1209 show_breakpoints: Option<bool>,
1210 git_blame_gutter_max_author_length: Option<usize>,
1211 pub display_snapshot: DisplaySnapshot,
1212 pub placeholder_text: Option<Arc<str>>,
1213 is_focused: bool,
1214 scroll_anchor: ScrollAnchor,
1215 ongoing_scroll: OngoingScroll,
1216 current_line_highlight: CurrentLineHighlight,
1217 gutter_hovered: bool,
1218}
1219
1220#[derive(Default, Debug, Clone, Copy)]
1221pub struct GutterDimensions {
1222 pub left_padding: Pixels,
1223 pub right_padding: Pixels,
1224 pub width: Pixels,
1225 pub margin: Pixels,
1226 pub git_blame_entries_width: Option<Pixels>,
1227}
1228
1229impl GutterDimensions {
1230 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1231 Self {
1232 margin: Self::default_gutter_margin(font_id, font_size, cx),
1233 ..Default::default()
1234 }
1235 }
1236
1237 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1238 -cx.text_system().descent(font_id, font_size)
1239 }
1240 /// The full width of the space taken up by the gutter.
1241 pub fn full_width(&self) -> Pixels {
1242 self.margin + self.width
1243 }
1244
1245 /// The width of the space reserved for the fold indicators,
1246 /// use alongside 'justify_end' and `gutter_width` to
1247 /// right align content with the line numbers
1248 pub fn fold_area_width(&self) -> Pixels {
1249 self.margin + self.right_padding
1250 }
1251}
1252
1253struct CharacterDimensions {
1254 em_width: Pixels,
1255 em_advance: Pixels,
1256 line_height: Pixels,
1257}
1258
1259#[derive(Debug)]
1260pub struct RemoteSelection {
1261 pub replica_id: ReplicaId,
1262 pub selection: Selection<Anchor>,
1263 pub cursor_shape: CursorShape,
1264 pub collaborator_id: CollaboratorId,
1265 pub line_mode: bool,
1266 pub user_name: Option<SharedString>,
1267 pub color: PlayerColor,
1268}
1269
1270#[derive(Clone, Debug)]
1271struct SelectionHistoryEntry {
1272 selections: Arc<[Selection<Anchor>]>,
1273 select_next_state: Option<SelectNextState>,
1274 select_prev_state: Option<SelectNextState>,
1275 add_selections_state: Option<AddSelectionsState>,
1276}
1277
1278#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1279enum SelectionHistoryMode {
1280 Normal,
1281 Undoing,
1282 Redoing,
1283 Skipping,
1284}
1285
1286#[derive(Clone, PartialEq, Eq, Hash)]
1287struct HoveredCursor {
1288 replica_id: u16,
1289 selection_id: usize,
1290}
1291
1292impl Default for SelectionHistoryMode {
1293 fn default() -> Self {
1294 Self::Normal
1295 }
1296}
1297
1298#[derive(Debug)]
1299/// SelectionEffects controls the side-effects of updating the selection.
1300///
1301/// The default behaviour does "what you mostly want":
1302/// - it pushes to the nav history if the cursor moved by >10 lines
1303/// - it re-triggers completion requests
1304/// - it scrolls to fit
1305///
1306/// You might want to modify these behaviours. For example when doing a "jump"
1307/// like go to definition, we always want to add to nav history; but when scrolling
1308/// in vim mode we never do.
1309///
1310/// Similarly, you might want to disable scrolling if you don't want the viewport to
1311/// move.
1312#[derive(Clone)]
1313pub struct SelectionEffects {
1314 nav_history: Option<bool>,
1315 completions: bool,
1316 scroll: Option<Autoscroll>,
1317}
1318
1319impl Default for SelectionEffects {
1320 fn default() -> Self {
1321 Self {
1322 nav_history: None,
1323 completions: true,
1324 scroll: Some(Autoscroll::fit()),
1325 }
1326 }
1327}
1328impl SelectionEffects {
1329 pub fn scroll(scroll: Autoscroll) -> Self {
1330 Self {
1331 scroll: Some(scroll),
1332 ..Default::default()
1333 }
1334 }
1335
1336 pub fn no_scroll() -> Self {
1337 Self {
1338 scroll: None,
1339 ..Default::default()
1340 }
1341 }
1342
1343 pub fn completions(self, completions: bool) -> Self {
1344 Self {
1345 completions,
1346 ..self
1347 }
1348 }
1349
1350 pub fn nav_history(self, nav_history: bool) -> Self {
1351 Self {
1352 nav_history: Some(nav_history),
1353 ..self
1354 }
1355 }
1356}
1357
1358struct DeferredSelectionEffectsState {
1359 changed: bool,
1360 effects: SelectionEffects,
1361 old_cursor_position: Anchor,
1362 history_entry: SelectionHistoryEntry,
1363}
1364
1365#[derive(Default)]
1366struct SelectionHistory {
1367 #[allow(clippy::type_complexity)]
1368 selections_by_transaction:
1369 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1370 mode: SelectionHistoryMode,
1371 undo_stack: VecDeque<SelectionHistoryEntry>,
1372 redo_stack: VecDeque<SelectionHistoryEntry>,
1373}
1374
1375impl SelectionHistory {
1376 #[track_caller]
1377 fn insert_transaction(
1378 &mut self,
1379 transaction_id: TransactionId,
1380 selections: Arc<[Selection<Anchor>]>,
1381 ) {
1382 if selections.is_empty() {
1383 log::error!(
1384 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1385 std::panic::Location::caller()
1386 );
1387 return;
1388 }
1389 self.selections_by_transaction
1390 .insert(transaction_id, (selections, None));
1391 }
1392
1393 #[allow(clippy::type_complexity)]
1394 fn transaction(
1395 &self,
1396 transaction_id: TransactionId,
1397 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1398 self.selections_by_transaction.get(&transaction_id)
1399 }
1400
1401 #[allow(clippy::type_complexity)]
1402 fn transaction_mut(
1403 &mut self,
1404 transaction_id: TransactionId,
1405 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1406 self.selections_by_transaction.get_mut(&transaction_id)
1407 }
1408
1409 fn push(&mut self, entry: SelectionHistoryEntry) {
1410 if !entry.selections.is_empty() {
1411 match self.mode {
1412 SelectionHistoryMode::Normal => {
1413 self.push_undo(entry);
1414 self.redo_stack.clear();
1415 }
1416 SelectionHistoryMode::Undoing => self.push_redo(entry),
1417 SelectionHistoryMode::Redoing => self.push_undo(entry),
1418 SelectionHistoryMode::Skipping => {}
1419 }
1420 }
1421 }
1422
1423 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1424 if self
1425 .undo_stack
1426 .back()
1427 .is_none_or(|e| e.selections != entry.selections)
1428 {
1429 self.undo_stack.push_back(entry);
1430 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1431 self.undo_stack.pop_front();
1432 }
1433 }
1434 }
1435
1436 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1437 if self
1438 .redo_stack
1439 .back()
1440 .is_none_or(|e| e.selections != entry.selections)
1441 {
1442 self.redo_stack.push_back(entry);
1443 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1444 self.redo_stack.pop_front();
1445 }
1446 }
1447 }
1448}
1449
1450#[derive(Clone, Copy)]
1451pub struct RowHighlightOptions {
1452 pub autoscroll: bool,
1453 pub include_gutter: bool,
1454}
1455
1456impl Default for RowHighlightOptions {
1457 fn default() -> Self {
1458 Self {
1459 autoscroll: Default::default(),
1460 include_gutter: true,
1461 }
1462 }
1463}
1464
1465struct RowHighlight {
1466 index: usize,
1467 range: Range<Anchor>,
1468 color: Hsla,
1469 options: RowHighlightOptions,
1470 type_id: TypeId,
1471}
1472
1473#[derive(Clone, Debug)]
1474struct AddSelectionsState {
1475 groups: Vec<AddSelectionsGroup>,
1476}
1477
1478#[derive(Clone, Debug)]
1479struct AddSelectionsGroup {
1480 above: bool,
1481 stack: Vec<usize>,
1482}
1483
1484#[derive(Clone)]
1485struct SelectNextState {
1486 query: AhoCorasick,
1487 wordwise: bool,
1488 done: bool,
1489}
1490
1491impl std::fmt::Debug for SelectNextState {
1492 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1493 f.debug_struct(std::any::type_name::<Self>())
1494 .field("wordwise", &self.wordwise)
1495 .field("done", &self.done)
1496 .finish()
1497 }
1498}
1499
1500#[derive(Debug)]
1501struct AutocloseRegion {
1502 selection_id: usize,
1503 range: Range<Anchor>,
1504 pair: BracketPair,
1505}
1506
1507#[derive(Debug)]
1508struct SnippetState {
1509 ranges: Vec<Vec<Range<Anchor>>>,
1510 active_index: usize,
1511 choices: Vec<Option<Vec<String>>>,
1512}
1513
1514#[doc(hidden)]
1515pub struct RenameState {
1516 pub range: Range<Anchor>,
1517 pub old_name: Arc<str>,
1518 pub editor: Entity<Editor>,
1519 block_id: CustomBlockId,
1520}
1521
1522struct InvalidationStack<T>(Vec<T>);
1523
1524struct RegisteredEditPredictionProvider {
1525 provider: Arc<dyn EditPredictionProviderHandle>,
1526 _subscription: Subscription,
1527}
1528
1529#[derive(Debug, PartialEq, Eq)]
1530pub struct ActiveDiagnosticGroup {
1531 pub active_range: Range<Anchor>,
1532 pub active_message: String,
1533 pub group_id: usize,
1534 pub blocks: HashSet<CustomBlockId>,
1535}
1536
1537#[derive(Debug, PartialEq, Eq)]
1538
1539pub(crate) enum ActiveDiagnostic {
1540 None,
1541 All,
1542 Group(ActiveDiagnosticGroup),
1543}
1544
1545#[derive(Serialize, Deserialize, Clone, Debug)]
1546pub struct ClipboardSelection {
1547 /// The number of bytes in this selection.
1548 pub len: usize,
1549 /// Whether this was a full-line selection.
1550 pub is_entire_line: bool,
1551 /// The indentation of the first line when this content was originally copied.
1552 pub first_line_indent: u32,
1553}
1554
1555// selections, scroll behavior, was newest selection reversed
1556type SelectSyntaxNodeHistoryState = (
1557 Box<[Selection<usize>]>,
1558 SelectSyntaxNodeScrollBehavior,
1559 bool,
1560);
1561
1562#[derive(Default)]
1563struct SelectSyntaxNodeHistory {
1564 stack: Vec<SelectSyntaxNodeHistoryState>,
1565 // disable temporarily to allow changing selections without losing the stack
1566 pub disable_clearing: bool,
1567}
1568
1569impl SelectSyntaxNodeHistory {
1570 pub fn try_clear(&mut self) {
1571 if !self.disable_clearing {
1572 self.stack.clear();
1573 }
1574 }
1575
1576 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1577 self.stack.push(selection);
1578 }
1579
1580 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1581 self.stack.pop()
1582 }
1583}
1584
1585enum SelectSyntaxNodeScrollBehavior {
1586 CursorTop,
1587 FitSelection,
1588 CursorBottom,
1589}
1590
1591#[derive(Debug)]
1592pub(crate) struct NavigationData {
1593 cursor_anchor: Anchor,
1594 cursor_position: Point,
1595 scroll_anchor: ScrollAnchor,
1596 scroll_top_row: u32,
1597}
1598
1599#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1600pub enum GotoDefinitionKind {
1601 Symbol,
1602 Declaration,
1603 Type,
1604 Implementation,
1605}
1606
1607#[derive(Debug, Clone)]
1608enum InlayHintRefreshReason {
1609 ModifiersChanged(bool),
1610 Toggle(bool),
1611 SettingsChange(InlayHintSettings),
1612 NewLinesShown,
1613 BufferEdited(HashSet<Arc<Language>>),
1614 RefreshRequested,
1615 ExcerptsRemoved(Vec<ExcerptId>),
1616}
1617
1618impl InlayHintRefreshReason {
1619 fn description(&self) -> &'static str {
1620 match self {
1621 Self::ModifiersChanged(_) => "modifiers changed",
1622 Self::Toggle(_) => "toggle",
1623 Self::SettingsChange(_) => "settings change",
1624 Self::NewLinesShown => "new lines shown",
1625 Self::BufferEdited(_) => "buffer edited",
1626 Self::RefreshRequested => "refresh requested",
1627 Self::ExcerptsRemoved(_) => "excerpts removed",
1628 }
1629 }
1630}
1631
1632pub enum FormatTarget {
1633 Buffers(HashSet<Entity<Buffer>>),
1634 Ranges(Vec<Range<MultiBufferPoint>>),
1635}
1636
1637pub(crate) struct FocusedBlock {
1638 id: BlockId,
1639 focus_handle: WeakFocusHandle,
1640}
1641
1642#[derive(Clone)]
1643enum JumpData {
1644 MultiBufferRow {
1645 row: MultiBufferRow,
1646 line_offset_from_top: u32,
1647 },
1648 MultiBufferPoint {
1649 excerpt_id: ExcerptId,
1650 position: Point,
1651 anchor: text::Anchor,
1652 line_offset_from_top: u32,
1653 },
1654}
1655
1656pub enum MultibufferSelectionMode {
1657 First,
1658 All,
1659}
1660
1661#[derive(Clone, Copy, Debug, Default)]
1662pub struct RewrapOptions {
1663 pub override_language_settings: bool,
1664 pub preserve_existing_whitespace: bool,
1665}
1666
1667impl Editor {
1668 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1669 let buffer = cx.new(|cx| Buffer::local("", cx));
1670 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1671 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1672 }
1673
1674 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(EditorMode::full(), buffer, None, window, cx)
1678 }
1679
1680 pub fn auto_height(
1681 min_lines: usize,
1682 max_lines: usize,
1683 window: &mut Window,
1684 cx: &mut Context<Self>,
1685 ) -> Self {
1686 let buffer = cx.new(|cx| Buffer::local("", cx));
1687 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1688 Self::new(
1689 EditorMode::AutoHeight {
1690 min_lines,
1691 max_lines: Some(max_lines),
1692 },
1693 buffer,
1694 None,
1695 window,
1696 cx,
1697 )
1698 }
1699
1700 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1701 /// The editor grows as tall as needed to fit its content.
1702 pub fn auto_height_unbounded(
1703 min_lines: usize,
1704 window: &mut Window,
1705 cx: &mut Context<Self>,
1706 ) -> Self {
1707 let buffer = cx.new(|cx| Buffer::local("", cx));
1708 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1709 Self::new(
1710 EditorMode::AutoHeight {
1711 min_lines,
1712 max_lines: None,
1713 },
1714 buffer,
1715 None,
1716 window,
1717 cx,
1718 )
1719 }
1720
1721 pub fn for_buffer(
1722 buffer: Entity<Buffer>,
1723 project: Option<Entity<Project>>,
1724 window: &mut Window,
1725 cx: &mut Context<Self>,
1726 ) -> Self {
1727 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1728 Self::new(EditorMode::full(), buffer, project, window, cx)
1729 }
1730
1731 pub fn for_multibuffer(
1732 buffer: Entity<MultiBuffer>,
1733 project: Option<Entity<Project>>,
1734 window: &mut Window,
1735 cx: &mut Context<Self>,
1736 ) -> Self {
1737 Self::new(EditorMode::full(), buffer, project, window, cx)
1738 }
1739
1740 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1741 let mut clone = Self::new(
1742 self.mode.clone(),
1743 self.buffer.clone(),
1744 self.project.clone(),
1745 window,
1746 cx,
1747 );
1748 self.display_map.update(cx, |display_map, cx| {
1749 let snapshot = display_map.snapshot(cx);
1750 clone.display_map.update(cx, |display_map, cx| {
1751 display_map.set_state(&snapshot, cx);
1752 });
1753 });
1754 clone.folds_did_change(cx);
1755 clone.selections.clone_state(&self.selections);
1756 clone.scroll_manager.clone_state(&self.scroll_manager);
1757 clone.searchable = self.searchable;
1758 clone.read_only = self.read_only;
1759 clone
1760 }
1761
1762 pub fn new(
1763 mode: EditorMode,
1764 buffer: Entity<MultiBuffer>,
1765 project: Option<Entity<Project>>,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 Editor::new_internal(mode, buffer, project, None, window, cx)
1770 }
1771
1772 fn new_internal(
1773 mode: EditorMode,
1774 buffer: Entity<MultiBuffer>,
1775 project: Option<Entity<Project>>,
1776 display_map: Option<Entity<DisplayMap>>,
1777 window: &mut Window,
1778 cx: &mut Context<Self>,
1779 ) -> Self {
1780 debug_assert!(
1781 display_map.is_none() || mode.is_minimap(),
1782 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1783 );
1784
1785 let full_mode = mode.is_full();
1786 let is_minimap = mode.is_minimap();
1787 let diagnostics_max_severity = if full_mode {
1788 EditorSettings::get_global(cx)
1789 .diagnostics_max_severity
1790 .unwrap_or(DiagnosticSeverity::Hint)
1791 } else {
1792 DiagnosticSeverity::Off
1793 };
1794 let style = window.text_style();
1795 let font_size = style.font_size.to_pixels(window.rem_size());
1796 let editor = cx.entity().downgrade();
1797 let fold_placeholder = FoldPlaceholder {
1798 constrain_width: true,
1799 render: Arc::new(move |fold_id, fold_range, cx| {
1800 let editor = editor.clone();
1801 div()
1802 .id(fold_id)
1803 .bg(cx.theme().colors().ghost_element_background)
1804 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1805 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1806 .rounded_xs()
1807 .size_full()
1808 .cursor_pointer()
1809 .child("⋯")
1810 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1811 .on_click(move |_, _window, cx| {
1812 editor
1813 .update(cx, |editor, cx| {
1814 editor.unfold_ranges(
1815 &[fold_range.start..fold_range.end],
1816 true,
1817 false,
1818 cx,
1819 );
1820 cx.stop_propagation();
1821 })
1822 .ok();
1823 })
1824 .into_any()
1825 }),
1826 merge_adjacent: true,
1827 ..FoldPlaceholder::default()
1828 };
1829 let display_map = display_map.unwrap_or_else(|| {
1830 cx.new(|cx| {
1831 DisplayMap::new(
1832 buffer.clone(),
1833 style.font(),
1834 font_size,
1835 None,
1836 FILE_HEADER_HEIGHT,
1837 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1838 fold_placeholder,
1839 diagnostics_max_severity,
1840 cx,
1841 )
1842 })
1843 });
1844
1845 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1846
1847 let blink_manager = cx.new(|cx| {
1848 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1849 if is_minimap {
1850 blink_manager.disable(cx);
1851 }
1852 blink_manager
1853 });
1854
1855 let soft_wrap_mode_override =
1856 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1857
1858 let mut project_subscriptions = Vec::new();
1859 if full_mode && let Some(project) = project.as_ref() {
1860 project_subscriptions.push(cx.subscribe_in(
1861 project,
1862 window,
1863 |editor, _, event, window, cx| match event {
1864 project::Event::RefreshCodeLens => {
1865 // we always query lens with actions, without storing them, always refreshing them
1866 }
1867 project::Event::RefreshInlayHints => {
1868 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1869 }
1870 project::Event::LanguageServerAdded(..)
1871 | project::Event::LanguageServerRemoved(..) => {
1872 if editor.tasks_update_task.is_none() {
1873 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1874 }
1875 }
1876 project::Event::SnippetEdit(id, snippet_edits) => {
1877 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1878 let focus_handle = editor.focus_handle(cx);
1879 if focus_handle.is_focused(window) {
1880 let snapshot = buffer.read(cx).snapshot();
1881 for (range, snippet) in snippet_edits {
1882 let editor_range =
1883 language::range_from_lsp(*range).to_offset(&snapshot);
1884 editor
1885 .insert_snippet(
1886 &[editor_range],
1887 snippet.clone(),
1888 window,
1889 cx,
1890 )
1891 .ok();
1892 }
1893 }
1894 }
1895 }
1896 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1897 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1898 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1899 }
1900 }
1901
1902 project::Event::EntryRenamed(transaction) => {
1903 let Some(workspace) = editor.workspace() else {
1904 return;
1905 };
1906 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1907 else {
1908 return;
1909 };
1910 if active_editor.entity_id() == cx.entity_id() {
1911 let edited_buffers_already_open = {
1912 let other_editors: Vec<Entity<Editor>> = workspace
1913 .read(cx)
1914 .panes()
1915 .iter()
1916 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1917 .filter(|editor| editor.entity_id() != cx.entity_id())
1918 .collect();
1919
1920 transaction.0.keys().all(|buffer| {
1921 other_editors.iter().any(|editor| {
1922 let multi_buffer = editor.read(cx).buffer();
1923 multi_buffer.read(cx).is_singleton()
1924 && multi_buffer.read(cx).as_singleton().map_or(
1925 false,
1926 |singleton| {
1927 singleton.entity_id() == buffer.entity_id()
1928 },
1929 )
1930 })
1931 })
1932 };
1933
1934 if !edited_buffers_already_open {
1935 let workspace = workspace.downgrade();
1936 let transaction = transaction.clone();
1937 cx.defer_in(window, move |_, window, cx| {
1938 cx.spawn_in(window, async move |editor, cx| {
1939 Self::open_project_transaction(
1940 &editor,
1941 workspace,
1942 transaction,
1943 "Rename".to_string(),
1944 cx,
1945 )
1946 .await
1947 .ok()
1948 })
1949 .detach();
1950 });
1951 }
1952 }
1953 }
1954
1955 _ => {}
1956 },
1957 ));
1958 if let Some(task_inventory) = project
1959 .read(cx)
1960 .task_store()
1961 .read(cx)
1962 .task_inventory()
1963 .cloned()
1964 {
1965 project_subscriptions.push(cx.observe_in(
1966 &task_inventory,
1967 window,
1968 |editor, _, window, cx| {
1969 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1970 },
1971 ));
1972 };
1973
1974 project_subscriptions.push(cx.subscribe_in(
1975 &project.read(cx).breakpoint_store(),
1976 window,
1977 |editor, _, event, window, cx| match event {
1978 BreakpointStoreEvent::ClearDebugLines => {
1979 editor.clear_row_highlights::<ActiveDebugLine>();
1980 editor.refresh_inline_values(cx);
1981 }
1982 BreakpointStoreEvent::SetDebugLine => {
1983 if editor.go_to_active_debug_line(window, cx) {
1984 cx.stop_propagation();
1985 }
1986
1987 editor.refresh_inline_values(cx);
1988 }
1989 _ => {}
1990 },
1991 ));
1992 let git_store = project.read(cx).git_store().clone();
1993 let project = project.clone();
1994 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1995 if let GitStoreEvent::RepositoryUpdated(
1996 _,
1997 RepositoryEvent::Updated {
1998 new_instance: true, ..
1999 },
2000 _,
2001 ) = event
2002 {
2003 this.load_diff_task = Some(
2004 update_uncommitted_diff_for_buffer(
2005 cx.entity(),
2006 &project,
2007 this.buffer.read(cx).all_buffers(),
2008 this.buffer.clone(),
2009 cx,
2010 )
2011 .shared(),
2012 );
2013 }
2014 }));
2015 }
2016
2017 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2018
2019 let inlay_hint_settings =
2020 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2021 let focus_handle = cx.focus_handle();
2022 if !is_minimap {
2023 cx.on_focus(&focus_handle, window, Self::handle_focus)
2024 .detach();
2025 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2026 .detach();
2027 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2028 .detach();
2029 cx.on_blur(&focus_handle, window, Self::handle_blur)
2030 .detach();
2031 cx.observe_pending_input(window, Self::observe_pending_input)
2032 .detach();
2033 }
2034
2035 let show_indent_guides =
2036 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2037 Some(false)
2038 } else {
2039 None
2040 };
2041
2042 let breakpoint_store = match (&mode, project.as_ref()) {
2043 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2044 _ => None,
2045 };
2046
2047 let mut code_action_providers = Vec::new();
2048 let mut load_uncommitted_diff = None;
2049 if let Some(project) = project.clone() {
2050 load_uncommitted_diff = Some(
2051 update_uncommitted_diff_for_buffer(
2052 cx.entity(),
2053 &project,
2054 buffer.read(cx).all_buffers(),
2055 buffer.clone(),
2056 cx,
2057 )
2058 .shared(),
2059 );
2060 code_action_providers.push(Rc::new(project) as Rc<_>);
2061 }
2062
2063 let mut editor = Self {
2064 focus_handle,
2065 show_cursor_when_unfocused: false,
2066 last_focused_descendant: None,
2067 buffer: buffer.clone(),
2068 display_map: display_map.clone(),
2069 selections,
2070 scroll_manager: ScrollManager::new(cx),
2071 columnar_selection_state: None,
2072 add_selections_state: None,
2073 select_next_state: None,
2074 select_prev_state: None,
2075 selection_history: SelectionHistory::default(),
2076 defer_selection_effects: false,
2077 deferred_selection_effects_state: None,
2078 autoclose_regions: Vec::new(),
2079 snippet_stack: InvalidationStack::default(),
2080 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2081 ime_transaction: None,
2082 active_diagnostics: ActiveDiagnostic::None,
2083 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2084 inline_diagnostics_update: Task::ready(()),
2085 inline_diagnostics: Vec::new(),
2086 soft_wrap_mode_override,
2087 diagnostics_max_severity,
2088 hard_wrap: None,
2089 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2090 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2091 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2092 project,
2093 blink_manager: blink_manager.clone(),
2094 show_local_selections: true,
2095 show_scrollbars: ScrollbarAxes {
2096 horizontal: full_mode,
2097 vertical: full_mode,
2098 },
2099 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2100 offset_content: !matches!(mode, EditorMode::SingleLine),
2101 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2102 show_gutter: full_mode,
2103 show_line_numbers: (!full_mode).then_some(false),
2104 use_relative_line_numbers: None,
2105 disable_expand_excerpt_buttons: !full_mode,
2106 show_git_diff_gutter: None,
2107 show_code_actions: None,
2108 show_runnables: None,
2109 show_breakpoints: None,
2110 show_wrap_guides: None,
2111 show_indent_guides,
2112 placeholder_text: None,
2113 highlight_order: 0,
2114 highlighted_rows: HashMap::default(),
2115 background_highlights: TreeMap::default(),
2116 gutter_highlights: TreeMap::default(),
2117 scrollbar_marker_state: ScrollbarMarkerState::default(),
2118 active_indent_guides_state: ActiveIndentGuidesState::default(),
2119 nav_history: None,
2120 context_menu: RefCell::new(None),
2121 context_menu_options: None,
2122 mouse_context_menu: None,
2123 completion_tasks: Vec::new(),
2124 inline_blame_popover: None,
2125 inline_blame_popover_show_task: None,
2126 signature_help_state: SignatureHelpState::default(),
2127 auto_signature_help: None,
2128 find_all_references_task_sources: Vec::new(),
2129 next_completion_id: 0,
2130 next_inlay_id: 0,
2131 code_action_providers,
2132 available_code_actions: None,
2133 code_actions_task: None,
2134 quick_selection_highlight_task: None,
2135 debounced_selection_highlight_task: None,
2136 document_highlights_task: None,
2137 linked_editing_range_task: None,
2138 pending_rename: None,
2139 searchable: !is_minimap,
2140 cursor_shape: EditorSettings::get_global(cx)
2141 .cursor_shape
2142 .unwrap_or_default(),
2143 current_line_highlight: None,
2144 autoindent_mode: Some(AutoindentMode::EachLine),
2145 collapse_matches: false,
2146 workspace: None,
2147 input_enabled: !is_minimap,
2148 use_modal_editing: full_mode,
2149 read_only: is_minimap,
2150 use_autoclose: true,
2151 use_auto_surround: true,
2152 auto_replace_emoji_shortcode: false,
2153 jsx_tag_auto_close_enabled_in_any_buffer: false,
2154 leader_id: None,
2155 remote_id: None,
2156 hover_state: HoverState::default(),
2157 pending_mouse_down: None,
2158 hovered_link_state: None,
2159 edit_prediction_provider: None,
2160 active_edit_prediction: None,
2161 stale_edit_prediction_in_menu: None,
2162 edit_prediction_preview: EditPredictionPreview::Inactive {
2163 released_too_fast: false,
2164 },
2165 inline_diagnostics_enabled: full_mode,
2166 diagnostics_enabled: full_mode,
2167 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2168 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2169 gutter_hovered: false,
2170 pixel_position_of_newest_cursor: None,
2171 last_bounds: None,
2172 last_position_map: None,
2173 expect_bounds_change: None,
2174 gutter_dimensions: GutterDimensions::default(),
2175 style: None,
2176 show_cursor_names: false,
2177 hovered_cursors: HashMap::default(),
2178 next_editor_action_id: EditorActionId::default(),
2179 editor_actions: Rc::default(),
2180 edit_predictions_hidden_for_vim_mode: false,
2181 show_edit_predictions_override: None,
2182 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2183 edit_prediction_settings: EditPredictionSettings::Disabled,
2184 edit_prediction_indent_conflict: false,
2185 edit_prediction_requires_modifier_in_indent_conflict: true,
2186 custom_context_menu: None,
2187 show_git_blame_gutter: false,
2188 show_git_blame_inline: false,
2189 show_selection_menu: None,
2190 show_git_blame_inline_delay_task: None,
2191 git_blame_inline_enabled: full_mode
2192 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2193 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2194 serialize_dirty_buffers: !is_minimap
2195 && ProjectSettings::get_global(cx)
2196 .session
2197 .restore_unsaved_buffers,
2198 blame: None,
2199 blame_subscription: None,
2200 tasks: BTreeMap::default(),
2201
2202 breakpoint_store,
2203 gutter_breakpoint_indicator: (None, None),
2204 hovered_diff_hunk_row: None,
2205 _subscriptions: (!is_minimap)
2206 .then(|| {
2207 vec![
2208 cx.observe(&buffer, Self::on_buffer_changed),
2209 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2210 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2211 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2212 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2213 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2214 cx.observe_window_activation(window, |editor, window, cx| {
2215 let active = window.is_window_active();
2216 editor.blink_manager.update(cx, |blink_manager, cx| {
2217 if active {
2218 blink_manager.enable(cx);
2219 } else {
2220 blink_manager.disable(cx);
2221 }
2222 });
2223 if active {
2224 editor.show_mouse_cursor(cx);
2225 }
2226 }),
2227 ]
2228 })
2229 .unwrap_or_default(),
2230 tasks_update_task: None,
2231 pull_diagnostics_task: Task::ready(()),
2232 colors: None,
2233 next_color_inlay_id: 0,
2234 linked_edit_ranges: Default::default(),
2235 in_project_search: false,
2236 previous_search_ranges: None,
2237 breadcrumb_header: None,
2238 focused_block: None,
2239 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2240 addons: HashMap::default(),
2241 registered_buffers: HashMap::default(),
2242 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2243 selection_mark_mode: false,
2244 toggle_fold_multiple_buffers: Task::ready(()),
2245 serialize_selections: Task::ready(()),
2246 serialize_folds: Task::ready(()),
2247 text_style_refinement: None,
2248 load_diff_task: load_uncommitted_diff,
2249 temporary_diff_override: false,
2250 mouse_cursor_hidden: false,
2251 minimap: None,
2252 hide_mouse_mode: EditorSettings::get_global(cx)
2253 .hide_mouse
2254 .unwrap_or_default(),
2255 change_list: ChangeList::new(),
2256 mode,
2257 selection_drag_state: SelectionDragState::None,
2258 folding_newlines: Task::ready(()),
2259 };
2260
2261 if is_minimap {
2262 return editor;
2263 }
2264
2265 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2266 editor
2267 ._subscriptions
2268 .push(cx.observe(breakpoints, |_, _, cx| {
2269 cx.notify();
2270 }));
2271 }
2272 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2273 editor._subscriptions.extend(project_subscriptions);
2274
2275 editor._subscriptions.push(cx.subscribe_in(
2276 &cx.entity(),
2277 window,
2278 |editor, _, e: &EditorEvent, window, cx| match e {
2279 EditorEvent::ScrollPositionChanged { local, .. } => {
2280 if *local {
2281 let new_anchor = editor.scroll_manager.anchor();
2282 let snapshot = editor.snapshot(window, cx);
2283 editor.update_restoration_data(cx, move |data| {
2284 data.scroll_position = (
2285 new_anchor.top_row(&snapshot.buffer_snapshot),
2286 new_anchor.offset,
2287 );
2288 });
2289 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2290 editor.inline_blame_popover.take();
2291 }
2292 }
2293 EditorEvent::Edited { .. } => {
2294 if !vim_enabled(cx) {
2295 let (map, selections) = editor.selections.all_adjusted_display(cx);
2296 let pop_state = editor
2297 .change_list
2298 .last()
2299 .map(|previous| {
2300 previous.len() == selections.len()
2301 && previous.iter().enumerate().all(|(ix, p)| {
2302 p.to_display_point(&map).row()
2303 == selections[ix].head().row()
2304 })
2305 })
2306 .unwrap_or(false);
2307 let new_positions = selections
2308 .into_iter()
2309 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2310 .collect();
2311 editor
2312 .change_list
2313 .push_to_change_list(pop_state, new_positions);
2314 }
2315 }
2316 _ => (),
2317 },
2318 ));
2319
2320 if let Some(dap_store) = editor
2321 .project
2322 .as_ref()
2323 .map(|project| project.read(cx).dap_store())
2324 {
2325 let weak_editor = cx.weak_entity();
2326
2327 editor
2328 ._subscriptions
2329 .push(
2330 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2331 let session_entity = cx.entity();
2332 weak_editor
2333 .update(cx, |editor, cx| {
2334 editor._subscriptions.push(
2335 cx.subscribe(&session_entity, Self::on_debug_session_event),
2336 );
2337 })
2338 .ok();
2339 }),
2340 );
2341
2342 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2343 editor
2344 ._subscriptions
2345 .push(cx.subscribe(&session, Self::on_debug_session_event));
2346 }
2347 }
2348
2349 // skip adding the initial selection to selection history
2350 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2351 editor.end_selection(window, cx);
2352 editor.selection_history.mode = SelectionHistoryMode::Normal;
2353
2354 editor.scroll_manager.show_scrollbars(window, cx);
2355 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2356
2357 if full_mode {
2358 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2359 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2360
2361 if editor.git_blame_inline_enabled {
2362 editor.start_git_blame_inline(false, window, cx);
2363 }
2364
2365 editor.go_to_active_debug_line(window, cx);
2366
2367 if let Some(buffer) = buffer.read(cx).as_singleton()
2368 && let Some(project) = editor.project()
2369 {
2370 let handle = project.update(cx, |project, cx| {
2371 project.register_buffer_with_language_servers(&buffer, cx)
2372 });
2373 editor
2374 .registered_buffers
2375 .insert(buffer.read(cx).remote_id(), handle);
2376 }
2377
2378 editor.minimap =
2379 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2380 editor.colors = Some(LspColorData::new(cx));
2381 editor.update_lsp_data(false, None, window, cx);
2382 }
2383
2384 if editor.mode.is_full() {
2385 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2386 }
2387
2388 editor
2389 }
2390
2391 pub fn deploy_mouse_context_menu(
2392 &mut self,
2393 position: gpui::Point<Pixels>,
2394 context_menu: Entity<ContextMenu>,
2395 window: &mut Window,
2396 cx: &mut Context<Self>,
2397 ) {
2398 self.mouse_context_menu = Some(MouseContextMenu::new(
2399 self,
2400 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2401 context_menu,
2402 window,
2403 cx,
2404 ));
2405 }
2406
2407 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2408 self.mouse_context_menu
2409 .as_ref()
2410 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2411 }
2412
2413 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2414 if self
2415 .selections
2416 .pending
2417 .as_ref()
2418 .is_some_and(|pending_selection| {
2419 let snapshot = self.buffer().read(cx).snapshot(cx);
2420 pending_selection
2421 .selection
2422 .range()
2423 .includes(range, &snapshot)
2424 })
2425 {
2426 return true;
2427 }
2428
2429 self.selections
2430 .disjoint_in_range::<usize>(range.clone(), cx)
2431 .into_iter()
2432 .any(|selection| {
2433 // This is needed to cover a corner case, if we just check for an existing
2434 // selection in the fold range, having a cursor at the start of the fold
2435 // marks it as selected. Non-empty selections don't cause this.
2436 let length = selection.end - selection.start;
2437 length > 0
2438 })
2439 }
2440
2441 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2442 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2443 }
2444
2445 fn key_context_internal(
2446 &self,
2447 has_active_edit_prediction: bool,
2448 window: &Window,
2449 cx: &App,
2450 ) -> KeyContext {
2451 let mut key_context = KeyContext::new_with_defaults();
2452 key_context.add("Editor");
2453 let mode = match self.mode {
2454 EditorMode::SingleLine => "single_line",
2455 EditorMode::AutoHeight { .. } => "auto_height",
2456 EditorMode::Minimap { .. } => "minimap",
2457 EditorMode::Full { .. } => "full",
2458 };
2459
2460 if EditorSettings::jupyter_enabled(cx) {
2461 key_context.add("jupyter");
2462 }
2463
2464 key_context.set("mode", mode);
2465 if self.pending_rename.is_some() {
2466 key_context.add("renaming");
2467 }
2468
2469 match self.context_menu.borrow().as_ref() {
2470 Some(CodeContextMenu::Completions(menu)) => {
2471 if menu.visible() {
2472 key_context.add("menu");
2473 key_context.add("showing_completions");
2474 }
2475 }
2476 Some(CodeContextMenu::CodeActions(menu)) => {
2477 if menu.visible() {
2478 key_context.add("menu");
2479 key_context.add("showing_code_actions")
2480 }
2481 }
2482 None => {}
2483 }
2484
2485 if self.signature_help_state.has_multiple_signatures() {
2486 key_context.add("showing_signature_help");
2487 }
2488
2489 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2490 if !self.focus_handle(cx).contains_focused(window, cx)
2491 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2492 {
2493 for addon in self.addons.values() {
2494 addon.extend_key_context(&mut key_context, cx)
2495 }
2496 }
2497
2498 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2499 if let Some(extension) = singleton_buffer
2500 .read(cx)
2501 .file()
2502 .and_then(|file| file.path().extension()?.to_str())
2503 {
2504 key_context.set("extension", extension.to_string());
2505 }
2506 } else {
2507 key_context.add("multibuffer");
2508 }
2509
2510 if has_active_edit_prediction {
2511 if self.edit_prediction_in_conflict() {
2512 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2513 } else {
2514 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2515 key_context.add("copilot_suggestion");
2516 }
2517 }
2518
2519 if self.selection_mark_mode {
2520 key_context.add("selection_mode");
2521 }
2522
2523 key_context
2524 }
2525
2526 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2527 if self.mouse_cursor_hidden {
2528 self.mouse_cursor_hidden = false;
2529 cx.notify();
2530 }
2531 }
2532
2533 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2534 let hide_mouse_cursor = match origin {
2535 HideMouseCursorOrigin::TypingAction => {
2536 matches!(
2537 self.hide_mouse_mode,
2538 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2539 )
2540 }
2541 HideMouseCursorOrigin::MovementAction => {
2542 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2543 }
2544 };
2545 if self.mouse_cursor_hidden != hide_mouse_cursor {
2546 self.mouse_cursor_hidden = hide_mouse_cursor;
2547 cx.notify();
2548 }
2549 }
2550
2551 pub fn edit_prediction_in_conflict(&self) -> bool {
2552 if !self.show_edit_predictions_in_menu() {
2553 return false;
2554 }
2555
2556 let showing_completions = self
2557 .context_menu
2558 .borrow()
2559 .as_ref()
2560 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2561
2562 showing_completions
2563 || self.edit_prediction_requires_modifier()
2564 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2565 // bindings to insert tab characters.
2566 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2567 }
2568
2569 pub fn accept_edit_prediction_keybind(
2570 &self,
2571 accept_partial: bool,
2572 window: &Window,
2573 cx: &App,
2574 ) -> AcceptEditPredictionBinding {
2575 let key_context = self.key_context_internal(true, window, cx);
2576 let in_conflict = self.edit_prediction_in_conflict();
2577
2578 let bindings = if accept_partial {
2579 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2580 } else {
2581 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2582 };
2583
2584 // TODO: if the binding contains multiple keystrokes, display all of them, not
2585 // just the first one.
2586 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2587 !in_conflict
2588 || binding
2589 .keystrokes()
2590 .first()
2591 .is_some_and(|keystroke| keystroke.modifiers.modified())
2592 }))
2593 }
2594
2595 pub fn new_file(
2596 workspace: &mut Workspace,
2597 _: &workspace::NewFile,
2598 window: &mut Window,
2599 cx: &mut Context<Workspace>,
2600 ) {
2601 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2602 "Failed to create buffer",
2603 window,
2604 cx,
2605 |e, _, _| match e.error_code() {
2606 ErrorCode::RemoteUpgradeRequired => Some(format!(
2607 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2608 e.error_tag("required").unwrap_or("the latest version")
2609 )),
2610 _ => None,
2611 },
2612 );
2613 }
2614
2615 pub fn new_in_workspace(
2616 workspace: &mut Workspace,
2617 window: &mut Window,
2618 cx: &mut Context<Workspace>,
2619 ) -> Task<Result<Entity<Editor>>> {
2620 let project = workspace.project().clone();
2621 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2622
2623 cx.spawn_in(window, async move |workspace, cx| {
2624 let buffer = create.await?;
2625 workspace.update_in(cx, |workspace, window, cx| {
2626 let editor =
2627 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2628 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2629 editor
2630 })
2631 })
2632 }
2633
2634 fn new_file_vertical(
2635 workspace: &mut Workspace,
2636 _: &workspace::NewFileSplitVertical,
2637 window: &mut Window,
2638 cx: &mut Context<Workspace>,
2639 ) {
2640 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2641 }
2642
2643 fn new_file_horizontal(
2644 workspace: &mut Workspace,
2645 _: &workspace::NewFileSplitHorizontal,
2646 window: &mut Window,
2647 cx: &mut Context<Workspace>,
2648 ) {
2649 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2650 }
2651
2652 fn new_file_in_direction(
2653 workspace: &mut Workspace,
2654 direction: SplitDirection,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 let project = workspace.project().clone();
2659 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2660
2661 cx.spawn_in(window, async move |workspace, cx| {
2662 let buffer = create.await?;
2663 workspace.update_in(cx, move |workspace, window, cx| {
2664 workspace.split_item(
2665 direction,
2666 Box::new(
2667 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2668 ),
2669 window,
2670 cx,
2671 )
2672 })?;
2673 anyhow::Ok(())
2674 })
2675 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2676 match e.error_code() {
2677 ErrorCode::RemoteUpgradeRequired => Some(format!(
2678 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2679 e.error_tag("required").unwrap_or("the latest version")
2680 )),
2681 _ => None,
2682 }
2683 });
2684 }
2685
2686 pub fn leader_id(&self) -> Option<CollaboratorId> {
2687 self.leader_id
2688 }
2689
2690 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2691 &self.buffer
2692 }
2693
2694 pub fn project(&self) -> Option<&Entity<Project>> {
2695 self.project.as_ref()
2696 }
2697
2698 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2699 self.workspace.as_ref()?.0.upgrade()
2700 }
2701
2702 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2703 self.buffer().read(cx).title(cx)
2704 }
2705
2706 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2707 let git_blame_gutter_max_author_length = self
2708 .render_git_blame_gutter(cx)
2709 .then(|| {
2710 if let Some(blame) = self.blame.as_ref() {
2711 let max_author_length =
2712 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2713 Some(max_author_length)
2714 } else {
2715 None
2716 }
2717 })
2718 .flatten();
2719
2720 EditorSnapshot {
2721 mode: self.mode.clone(),
2722 show_gutter: self.show_gutter,
2723 show_line_numbers: self.show_line_numbers,
2724 show_git_diff_gutter: self.show_git_diff_gutter,
2725 show_code_actions: self.show_code_actions,
2726 show_runnables: self.show_runnables,
2727 show_breakpoints: self.show_breakpoints,
2728 git_blame_gutter_max_author_length,
2729 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2730 scroll_anchor: self.scroll_manager.anchor(),
2731 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2732 placeholder_text: self.placeholder_text.clone(),
2733 is_focused: self.focus_handle.is_focused(window),
2734 current_line_highlight: self
2735 .current_line_highlight
2736 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2737 gutter_hovered: self.gutter_hovered,
2738 }
2739 }
2740
2741 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2742 self.buffer.read(cx).language_at(point, cx)
2743 }
2744
2745 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2746 self.buffer.read(cx).read(cx).file_at(point).cloned()
2747 }
2748
2749 pub fn active_excerpt(
2750 &self,
2751 cx: &App,
2752 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2753 self.buffer
2754 .read(cx)
2755 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2756 }
2757
2758 pub fn mode(&self) -> &EditorMode {
2759 &self.mode
2760 }
2761
2762 pub fn set_mode(&mut self, mode: EditorMode) {
2763 self.mode = mode;
2764 }
2765
2766 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2767 self.collaboration_hub.as_deref()
2768 }
2769
2770 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2771 self.collaboration_hub = Some(hub);
2772 }
2773
2774 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2775 self.in_project_search = in_project_search;
2776 }
2777
2778 pub fn set_custom_context_menu(
2779 &mut self,
2780 f: impl 'static
2781 + Fn(
2782 &mut Self,
2783 DisplayPoint,
2784 &mut Window,
2785 &mut Context<Self>,
2786 ) -> Option<Entity<ui::ContextMenu>>,
2787 ) {
2788 self.custom_context_menu = Some(Box::new(f))
2789 }
2790
2791 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2792 self.completion_provider = provider;
2793 }
2794
2795 #[cfg(any(test, feature = "test-support"))]
2796 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2797 self.completion_provider.clone()
2798 }
2799
2800 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2801 self.semantics_provider.clone()
2802 }
2803
2804 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2805 self.semantics_provider = provider;
2806 }
2807
2808 pub fn set_edit_prediction_provider<T>(
2809 &mut self,
2810 provider: Option<Entity<T>>,
2811 window: &mut Window,
2812 cx: &mut Context<Self>,
2813 ) where
2814 T: EditPredictionProvider,
2815 {
2816 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2817 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2818 if this.focus_handle.is_focused(window) {
2819 this.update_visible_edit_prediction(window, cx);
2820 }
2821 }),
2822 provider: Arc::new(provider),
2823 });
2824 self.update_edit_prediction_settings(cx);
2825 self.refresh_edit_prediction(false, false, window, cx);
2826 }
2827
2828 pub fn placeholder_text(&self) -> Option<&str> {
2829 self.placeholder_text.as_deref()
2830 }
2831
2832 pub fn set_placeholder_text(
2833 &mut self,
2834 placeholder_text: impl Into<Arc<str>>,
2835 cx: &mut Context<Self>,
2836 ) {
2837 let placeholder_text = Some(placeholder_text.into());
2838 if self.placeholder_text != placeholder_text {
2839 self.placeholder_text = placeholder_text;
2840 cx.notify();
2841 }
2842 }
2843
2844 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2845 self.cursor_shape = cursor_shape;
2846
2847 // Disrupt blink for immediate user feedback that the cursor shape has changed
2848 self.blink_manager.update(cx, BlinkManager::show_cursor);
2849
2850 cx.notify();
2851 }
2852
2853 pub fn set_current_line_highlight(
2854 &mut self,
2855 current_line_highlight: Option<CurrentLineHighlight>,
2856 ) {
2857 self.current_line_highlight = current_line_highlight;
2858 }
2859
2860 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2861 self.collapse_matches = collapse_matches;
2862 }
2863
2864 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2865 let buffers = self.buffer.read(cx).all_buffers();
2866 let Some(project) = self.project.as_ref() else {
2867 return;
2868 };
2869 project.update(cx, |project, cx| {
2870 for buffer in buffers {
2871 self.registered_buffers
2872 .entry(buffer.read(cx).remote_id())
2873 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2874 }
2875 })
2876 }
2877
2878 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2879 if self.collapse_matches {
2880 return range.start..range.start;
2881 }
2882 range.clone()
2883 }
2884
2885 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2886 if self.display_map.read(cx).clip_at_line_ends != clip {
2887 self.display_map
2888 .update(cx, |map, _| map.clip_at_line_ends = clip);
2889 }
2890 }
2891
2892 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2893 self.input_enabled = input_enabled;
2894 }
2895
2896 pub fn set_edit_predictions_hidden_for_vim_mode(
2897 &mut self,
2898 hidden: bool,
2899 window: &mut Window,
2900 cx: &mut Context<Self>,
2901 ) {
2902 if hidden != self.edit_predictions_hidden_for_vim_mode {
2903 self.edit_predictions_hidden_for_vim_mode = hidden;
2904 if hidden {
2905 self.update_visible_edit_prediction(window, cx);
2906 } else {
2907 self.refresh_edit_prediction(true, false, window, cx);
2908 }
2909 }
2910 }
2911
2912 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2913 self.menu_edit_predictions_policy = value;
2914 }
2915
2916 pub fn set_autoindent(&mut self, autoindent: bool) {
2917 if autoindent {
2918 self.autoindent_mode = Some(AutoindentMode::EachLine);
2919 } else {
2920 self.autoindent_mode = None;
2921 }
2922 }
2923
2924 pub fn read_only(&self, cx: &App) -> bool {
2925 self.read_only || self.buffer.read(cx).read_only()
2926 }
2927
2928 pub fn set_read_only(&mut self, read_only: bool) {
2929 self.read_only = read_only;
2930 }
2931
2932 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2933 self.use_autoclose = autoclose;
2934 }
2935
2936 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2937 self.use_auto_surround = auto_surround;
2938 }
2939
2940 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2941 self.auto_replace_emoji_shortcode = auto_replace;
2942 }
2943
2944 pub fn toggle_edit_predictions(
2945 &mut self,
2946 _: &ToggleEditPrediction,
2947 window: &mut Window,
2948 cx: &mut Context<Self>,
2949 ) {
2950 if self.show_edit_predictions_override.is_some() {
2951 self.set_show_edit_predictions(None, window, cx);
2952 } else {
2953 let show_edit_predictions = !self.edit_predictions_enabled();
2954 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2955 }
2956 }
2957
2958 pub fn set_show_edit_predictions(
2959 &mut self,
2960 show_edit_predictions: Option<bool>,
2961 window: &mut Window,
2962 cx: &mut Context<Self>,
2963 ) {
2964 self.show_edit_predictions_override = show_edit_predictions;
2965 self.update_edit_prediction_settings(cx);
2966
2967 if let Some(false) = show_edit_predictions {
2968 self.discard_edit_prediction(false, cx);
2969 } else {
2970 self.refresh_edit_prediction(false, true, window, cx);
2971 }
2972 }
2973
2974 fn edit_predictions_disabled_in_scope(
2975 &self,
2976 buffer: &Entity<Buffer>,
2977 buffer_position: language::Anchor,
2978 cx: &App,
2979 ) -> bool {
2980 let snapshot = buffer.read(cx).snapshot();
2981 let settings = snapshot.settings_at(buffer_position, cx);
2982
2983 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2984 return false;
2985 };
2986
2987 scope.override_name().is_some_and(|scope_name| {
2988 settings
2989 .edit_predictions_disabled_in
2990 .iter()
2991 .any(|s| s == scope_name)
2992 })
2993 }
2994
2995 pub fn set_use_modal_editing(&mut self, to: bool) {
2996 self.use_modal_editing = to;
2997 }
2998
2999 pub fn use_modal_editing(&self) -> bool {
3000 self.use_modal_editing
3001 }
3002
3003 fn selections_did_change(
3004 &mut self,
3005 local: bool,
3006 old_cursor_position: &Anchor,
3007 effects: SelectionEffects,
3008 window: &mut Window,
3009 cx: &mut Context<Self>,
3010 ) {
3011 window.invalidate_character_coordinates();
3012
3013 // Copy selections to primary selection buffer
3014 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3015 if local {
3016 let selections = self.selections.all::<usize>(cx);
3017 let buffer_handle = self.buffer.read(cx).read(cx);
3018
3019 let mut text = String::new();
3020 for (index, selection) in selections.iter().enumerate() {
3021 let text_for_selection = buffer_handle
3022 .text_for_range(selection.start..selection.end)
3023 .collect::<String>();
3024
3025 text.push_str(&text_for_selection);
3026 if index != selections.len() - 1 {
3027 text.push('\n');
3028 }
3029 }
3030
3031 if !text.is_empty() {
3032 cx.write_to_primary(ClipboardItem::new_string(text));
3033 }
3034 }
3035
3036 let selection_anchors = self.selections.disjoint_anchors();
3037
3038 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3039 self.buffer.update(cx, |buffer, cx| {
3040 buffer.set_active_selections(
3041 &selection_anchors,
3042 self.selections.line_mode,
3043 self.cursor_shape,
3044 cx,
3045 )
3046 });
3047 }
3048 let display_map = self
3049 .display_map
3050 .update(cx, |display_map, cx| display_map.snapshot(cx));
3051 let buffer = &display_map.buffer_snapshot;
3052 if self.selections.count() == 1 {
3053 self.add_selections_state = None;
3054 }
3055 self.select_next_state = None;
3056 self.select_prev_state = None;
3057 self.select_syntax_node_history.try_clear();
3058 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3059 self.snippet_stack.invalidate(&selection_anchors, buffer);
3060 self.take_rename(false, window, cx);
3061
3062 let newest_selection = self.selections.newest_anchor();
3063 let new_cursor_position = newest_selection.head();
3064 let selection_start = newest_selection.start;
3065
3066 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3067 self.push_to_nav_history(
3068 *old_cursor_position,
3069 Some(new_cursor_position.to_point(buffer)),
3070 false,
3071 effects.nav_history == Some(true),
3072 cx,
3073 );
3074 }
3075
3076 if local {
3077 if let Some(buffer_id) = new_cursor_position.buffer_id
3078 && !self.registered_buffers.contains_key(&buffer_id)
3079 && let Some(project) = self.project.as_ref()
3080 {
3081 project.update(cx, |project, cx| {
3082 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3083 return;
3084 };
3085 self.registered_buffers.insert(
3086 buffer_id,
3087 project.register_buffer_with_language_servers(&buffer, cx),
3088 );
3089 })
3090 }
3091
3092 let mut context_menu = self.context_menu.borrow_mut();
3093 let completion_menu = match context_menu.as_ref() {
3094 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3095 Some(CodeContextMenu::CodeActions(_)) => {
3096 *context_menu = None;
3097 None
3098 }
3099 None => None,
3100 };
3101 let completion_position = completion_menu.map(|menu| menu.initial_position);
3102 drop(context_menu);
3103
3104 if effects.completions
3105 && let Some(completion_position) = completion_position
3106 {
3107 let start_offset = selection_start.to_offset(buffer);
3108 let position_matches = start_offset == completion_position.to_offset(buffer);
3109 let continue_showing = if position_matches {
3110 if self.snippet_stack.is_empty() {
3111 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3112 } else {
3113 // Snippet choices can be shown even when the cursor is in whitespace.
3114 // Dismissing the menu with actions like backspace is handled by
3115 // invalidation regions.
3116 true
3117 }
3118 } else {
3119 false
3120 };
3121
3122 if continue_showing {
3123 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3124 } else {
3125 self.hide_context_menu(window, cx);
3126 }
3127 }
3128
3129 hide_hover(self, cx);
3130
3131 if old_cursor_position.to_display_point(&display_map).row()
3132 != new_cursor_position.to_display_point(&display_map).row()
3133 {
3134 self.available_code_actions.take();
3135 }
3136 self.refresh_code_actions(window, cx);
3137 self.refresh_document_highlights(cx);
3138 self.refresh_selected_text_highlights(false, window, cx);
3139 refresh_matching_bracket_highlights(self, window, cx);
3140 self.update_visible_edit_prediction(window, cx);
3141 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3142 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3143 self.inline_blame_popover.take();
3144 if self.git_blame_inline_enabled {
3145 self.start_inline_blame_timer(window, cx);
3146 }
3147 }
3148
3149 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3150 cx.emit(EditorEvent::SelectionsChanged { local });
3151
3152 let selections = &self.selections.disjoint;
3153 if selections.len() == 1 {
3154 cx.emit(SearchEvent::ActiveMatchChanged)
3155 }
3156 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3157 let inmemory_selections = selections
3158 .iter()
3159 .map(|s| {
3160 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3161 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3162 })
3163 .collect();
3164 self.update_restoration_data(cx, |data| {
3165 data.selections = inmemory_selections;
3166 });
3167
3168 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3169 && let Some(workspace_id) =
3170 self.workspace.as_ref().and_then(|workspace| workspace.1)
3171 {
3172 let snapshot = self.buffer().read(cx).snapshot(cx);
3173 let selections = selections.clone();
3174 let background_executor = cx.background_executor().clone();
3175 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3176 self.serialize_selections = cx.background_spawn(async move {
3177 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3178 let db_selections = selections
3179 .iter()
3180 .map(|selection| {
3181 (
3182 selection.start.to_offset(&snapshot),
3183 selection.end.to_offset(&snapshot),
3184 )
3185 })
3186 .collect();
3187
3188 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3189 .await
3190 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3191 .log_err();
3192 });
3193 }
3194 }
3195
3196 cx.notify();
3197 }
3198
3199 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3200 use text::ToOffset as _;
3201 use text::ToPoint as _;
3202
3203 if self.mode.is_minimap()
3204 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3205 {
3206 return;
3207 }
3208
3209 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3210 return;
3211 };
3212
3213 let snapshot = singleton.read(cx).snapshot();
3214 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3215 let display_snapshot = display_map.snapshot(cx);
3216
3217 display_snapshot
3218 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3219 .map(|fold| {
3220 fold.range.start.text_anchor.to_point(&snapshot)
3221 ..fold.range.end.text_anchor.to_point(&snapshot)
3222 })
3223 .collect()
3224 });
3225 self.update_restoration_data(cx, |data| {
3226 data.folds = inmemory_folds;
3227 });
3228
3229 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3230 return;
3231 };
3232 let background_executor = cx.background_executor().clone();
3233 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3234 let db_folds = self.display_map.update(cx, |display_map, cx| {
3235 display_map
3236 .snapshot(cx)
3237 .folds_in_range(0..snapshot.len())
3238 .map(|fold| {
3239 (
3240 fold.range.start.text_anchor.to_offset(&snapshot),
3241 fold.range.end.text_anchor.to_offset(&snapshot),
3242 )
3243 })
3244 .collect()
3245 });
3246 self.serialize_folds = cx.background_spawn(async move {
3247 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3248 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3249 .await
3250 .with_context(|| {
3251 format!(
3252 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3253 )
3254 })
3255 .log_err();
3256 });
3257 }
3258
3259 pub fn sync_selections(
3260 &mut self,
3261 other: Entity<Editor>,
3262 cx: &mut Context<Self>,
3263 ) -> gpui::Subscription {
3264 let other_selections = other.read(cx).selections.disjoint.to_vec();
3265 self.selections.change_with(cx, |selections| {
3266 selections.select_anchors(other_selections);
3267 });
3268
3269 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3270 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3271 let other_selections = other.read(cx).selections.disjoint.to_vec();
3272 if other_selections.is_empty() {
3273 return;
3274 }
3275 this.selections.change_with(cx, |selections| {
3276 selections.select_anchors(other_selections);
3277 });
3278 }
3279 });
3280
3281 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3282 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3283 let these_selections = this.selections.disjoint.to_vec();
3284 if these_selections.is_empty() {
3285 return;
3286 }
3287 other.update(cx, |other_editor, cx| {
3288 other_editor.selections.change_with(cx, |selections| {
3289 selections.select_anchors(these_selections);
3290 })
3291 });
3292 }
3293 });
3294
3295 Subscription::join(other_subscription, this_subscription)
3296 }
3297
3298 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3299 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3300 /// effects of selection change occur at the end of the transaction.
3301 pub fn change_selections<R>(
3302 &mut self,
3303 effects: SelectionEffects,
3304 window: &mut Window,
3305 cx: &mut Context<Self>,
3306 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3307 ) -> R {
3308 if let Some(state) = &mut self.deferred_selection_effects_state {
3309 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3310 state.effects.completions = effects.completions;
3311 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3312 let (changed, result) = self.selections.change_with(cx, change);
3313 state.changed |= changed;
3314 return result;
3315 }
3316 let mut state = DeferredSelectionEffectsState {
3317 changed: false,
3318 effects,
3319 old_cursor_position: self.selections.newest_anchor().head(),
3320 history_entry: SelectionHistoryEntry {
3321 selections: self.selections.disjoint_anchors(),
3322 select_next_state: self.select_next_state.clone(),
3323 select_prev_state: self.select_prev_state.clone(),
3324 add_selections_state: self.add_selections_state.clone(),
3325 },
3326 };
3327 let (changed, result) = self.selections.change_with(cx, change);
3328 state.changed = state.changed || changed;
3329 if self.defer_selection_effects {
3330 self.deferred_selection_effects_state = Some(state);
3331 } else {
3332 self.apply_selection_effects(state, window, cx);
3333 }
3334 result
3335 }
3336
3337 /// Defers the effects of selection change, so that the effects of multiple calls to
3338 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3339 /// to selection history and the state of popovers based on selection position aren't
3340 /// erroneously updated.
3341 pub fn with_selection_effects_deferred<R>(
3342 &mut self,
3343 window: &mut Window,
3344 cx: &mut Context<Self>,
3345 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3346 ) -> R {
3347 let already_deferred = self.defer_selection_effects;
3348 self.defer_selection_effects = true;
3349 let result = update(self, window, cx);
3350 if !already_deferred {
3351 self.defer_selection_effects = false;
3352 if let Some(state) = self.deferred_selection_effects_state.take() {
3353 self.apply_selection_effects(state, window, cx);
3354 }
3355 }
3356 result
3357 }
3358
3359 fn apply_selection_effects(
3360 &mut self,
3361 state: DeferredSelectionEffectsState,
3362 window: &mut Window,
3363 cx: &mut Context<Self>,
3364 ) {
3365 if state.changed {
3366 self.selection_history.push(state.history_entry);
3367
3368 if let Some(autoscroll) = state.effects.scroll {
3369 self.request_autoscroll(autoscroll, cx);
3370 }
3371
3372 let old_cursor_position = &state.old_cursor_position;
3373
3374 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3375
3376 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3377 self.show_signature_help(&ShowSignatureHelp, window, cx);
3378 }
3379 }
3380 }
3381
3382 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3383 where
3384 I: IntoIterator<Item = (Range<S>, T)>,
3385 S: ToOffset,
3386 T: Into<Arc<str>>,
3387 {
3388 if self.read_only(cx) {
3389 return;
3390 }
3391
3392 self.buffer
3393 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3394 }
3395
3396 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3397 where
3398 I: IntoIterator<Item = (Range<S>, T)>,
3399 S: ToOffset,
3400 T: Into<Arc<str>>,
3401 {
3402 if self.read_only(cx) {
3403 return;
3404 }
3405
3406 self.buffer.update(cx, |buffer, cx| {
3407 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3408 });
3409 }
3410
3411 pub fn edit_with_block_indent<I, S, T>(
3412 &mut self,
3413 edits: I,
3414 original_indent_columns: Vec<Option<u32>>,
3415 cx: &mut Context<Self>,
3416 ) where
3417 I: IntoIterator<Item = (Range<S>, T)>,
3418 S: ToOffset,
3419 T: Into<Arc<str>>,
3420 {
3421 if self.read_only(cx) {
3422 return;
3423 }
3424
3425 self.buffer.update(cx, |buffer, cx| {
3426 buffer.edit(
3427 edits,
3428 Some(AutoindentMode::Block {
3429 original_indent_columns,
3430 }),
3431 cx,
3432 )
3433 });
3434 }
3435
3436 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3437 self.hide_context_menu(window, cx);
3438
3439 match phase {
3440 SelectPhase::Begin {
3441 position,
3442 add,
3443 click_count,
3444 } => self.begin_selection(position, add, click_count, window, cx),
3445 SelectPhase::BeginColumnar {
3446 position,
3447 goal_column,
3448 reset,
3449 mode,
3450 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3451 SelectPhase::Extend {
3452 position,
3453 click_count,
3454 } => self.extend_selection(position, click_count, window, cx),
3455 SelectPhase::Update {
3456 position,
3457 goal_column,
3458 scroll_delta,
3459 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3460 SelectPhase::End => self.end_selection(window, cx),
3461 }
3462 }
3463
3464 fn extend_selection(
3465 &mut self,
3466 position: DisplayPoint,
3467 click_count: usize,
3468 window: &mut Window,
3469 cx: &mut Context<Self>,
3470 ) {
3471 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3472 let tail = self.selections.newest::<usize>(cx).tail();
3473 self.begin_selection(position, false, click_count, window, cx);
3474
3475 let position = position.to_offset(&display_map, Bias::Left);
3476 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3477
3478 let mut pending_selection = self
3479 .selections
3480 .pending_anchor()
3481 .expect("extend_selection not called with pending selection");
3482 if position >= tail {
3483 pending_selection.start = tail_anchor;
3484 } else {
3485 pending_selection.end = tail_anchor;
3486 pending_selection.reversed = true;
3487 }
3488
3489 let mut pending_mode = self.selections.pending_mode().unwrap();
3490 match &mut pending_mode {
3491 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3492 _ => {}
3493 }
3494
3495 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3496 SelectionEffects::scroll(Autoscroll::fit())
3497 } else {
3498 SelectionEffects::no_scroll()
3499 };
3500
3501 self.change_selections(effects, window, cx, |s| {
3502 s.set_pending(pending_selection, pending_mode)
3503 });
3504 }
3505
3506 fn begin_selection(
3507 &mut self,
3508 position: DisplayPoint,
3509 add: bool,
3510 click_count: usize,
3511 window: &mut Window,
3512 cx: &mut Context<Self>,
3513 ) {
3514 if !self.focus_handle.is_focused(window) {
3515 self.last_focused_descendant = None;
3516 window.focus(&self.focus_handle);
3517 }
3518
3519 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3520 let buffer = &display_map.buffer_snapshot;
3521 let position = display_map.clip_point(position, Bias::Left);
3522
3523 let start;
3524 let end;
3525 let mode;
3526 let mut auto_scroll;
3527 match click_count {
3528 1 => {
3529 start = buffer.anchor_before(position.to_point(&display_map));
3530 end = start;
3531 mode = SelectMode::Character;
3532 auto_scroll = true;
3533 }
3534 2 => {
3535 let position = display_map
3536 .clip_point(position, Bias::Left)
3537 .to_offset(&display_map, Bias::Left);
3538 let (range, _) = buffer.surrounding_word(position, false);
3539 start = buffer.anchor_before(range.start);
3540 end = buffer.anchor_before(range.end);
3541 mode = SelectMode::Word(start..end);
3542 auto_scroll = true;
3543 }
3544 3 => {
3545 let position = display_map
3546 .clip_point(position, Bias::Left)
3547 .to_point(&display_map);
3548 let line_start = display_map.prev_line_boundary(position).0;
3549 let next_line_start = buffer.clip_point(
3550 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3551 Bias::Left,
3552 );
3553 start = buffer.anchor_before(line_start);
3554 end = buffer.anchor_before(next_line_start);
3555 mode = SelectMode::Line(start..end);
3556 auto_scroll = true;
3557 }
3558 _ => {
3559 start = buffer.anchor_before(0);
3560 end = buffer.anchor_before(buffer.len());
3561 mode = SelectMode::All;
3562 auto_scroll = false;
3563 }
3564 }
3565 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3566
3567 let point_to_delete: Option<usize> = {
3568 let selected_points: Vec<Selection<Point>> =
3569 self.selections.disjoint_in_range(start..end, cx);
3570
3571 if !add || click_count > 1 {
3572 None
3573 } else if !selected_points.is_empty() {
3574 Some(selected_points[0].id)
3575 } else {
3576 let clicked_point_already_selected =
3577 self.selections.disjoint.iter().find(|selection| {
3578 selection.start.to_point(buffer) == start.to_point(buffer)
3579 || selection.end.to_point(buffer) == end.to_point(buffer)
3580 });
3581
3582 clicked_point_already_selected.map(|selection| selection.id)
3583 }
3584 };
3585
3586 let selections_count = self.selections.count();
3587 let effects = if auto_scroll {
3588 SelectionEffects::default()
3589 } else {
3590 SelectionEffects::no_scroll()
3591 };
3592
3593 self.change_selections(effects, window, cx, |s| {
3594 if let Some(point_to_delete) = point_to_delete {
3595 s.delete(point_to_delete);
3596
3597 if selections_count == 1 {
3598 s.set_pending_anchor_range(start..end, mode);
3599 }
3600 } else {
3601 if !add {
3602 s.clear_disjoint();
3603 }
3604
3605 s.set_pending_anchor_range(start..end, mode);
3606 }
3607 });
3608 }
3609
3610 fn begin_columnar_selection(
3611 &mut self,
3612 position: DisplayPoint,
3613 goal_column: u32,
3614 reset: bool,
3615 mode: ColumnarMode,
3616 window: &mut Window,
3617 cx: &mut Context<Self>,
3618 ) {
3619 if !self.focus_handle.is_focused(window) {
3620 self.last_focused_descendant = None;
3621 window.focus(&self.focus_handle);
3622 }
3623
3624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3625
3626 if reset {
3627 let pointer_position = display_map
3628 .buffer_snapshot
3629 .anchor_before(position.to_point(&display_map));
3630
3631 self.change_selections(
3632 SelectionEffects::scroll(Autoscroll::newest()),
3633 window,
3634 cx,
3635 |s| {
3636 s.clear_disjoint();
3637 s.set_pending_anchor_range(
3638 pointer_position..pointer_position,
3639 SelectMode::Character,
3640 );
3641 },
3642 );
3643 };
3644
3645 let tail = self.selections.newest::<Point>(cx).tail();
3646 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3647 self.columnar_selection_state = match mode {
3648 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3649 selection_tail: selection_anchor,
3650 display_point: if reset {
3651 if position.column() != goal_column {
3652 Some(DisplayPoint::new(position.row(), goal_column))
3653 } else {
3654 None
3655 }
3656 } else {
3657 None
3658 },
3659 }),
3660 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3661 selection_tail: selection_anchor,
3662 }),
3663 };
3664
3665 if !reset {
3666 self.select_columns(position, goal_column, &display_map, window, cx);
3667 }
3668 }
3669
3670 fn update_selection(
3671 &mut self,
3672 position: DisplayPoint,
3673 goal_column: u32,
3674 scroll_delta: gpui::Point<f32>,
3675 window: &mut Window,
3676 cx: &mut Context<Self>,
3677 ) {
3678 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3679
3680 if self.columnar_selection_state.is_some() {
3681 self.select_columns(position, goal_column, &display_map, window, cx);
3682 } else if let Some(mut pending) = self.selections.pending_anchor() {
3683 let buffer = &display_map.buffer_snapshot;
3684 let head;
3685 let tail;
3686 let mode = self.selections.pending_mode().unwrap();
3687 match &mode {
3688 SelectMode::Character => {
3689 head = position.to_point(&display_map);
3690 tail = pending.tail().to_point(buffer);
3691 }
3692 SelectMode::Word(original_range) => {
3693 let offset = display_map
3694 .clip_point(position, Bias::Left)
3695 .to_offset(&display_map, Bias::Left);
3696 let original_range = original_range.to_offset(buffer);
3697
3698 let head_offset = if buffer.is_inside_word(offset, false)
3699 || original_range.contains(&offset)
3700 {
3701 let (word_range, _) = buffer.surrounding_word(offset, false);
3702 if word_range.start < original_range.start {
3703 word_range.start
3704 } else {
3705 word_range.end
3706 }
3707 } else {
3708 offset
3709 };
3710
3711 head = head_offset.to_point(buffer);
3712 if head_offset <= original_range.start {
3713 tail = original_range.end.to_point(buffer);
3714 } else {
3715 tail = original_range.start.to_point(buffer);
3716 }
3717 }
3718 SelectMode::Line(original_range) => {
3719 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3720
3721 let position = display_map
3722 .clip_point(position, Bias::Left)
3723 .to_point(&display_map);
3724 let line_start = display_map.prev_line_boundary(position).0;
3725 let next_line_start = buffer.clip_point(
3726 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3727 Bias::Left,
3728 );
3729
3730 if line_start < original_range.start {
3731 head = line_start
3732 } else {
3733 head = next_line_start
3734 }
3735
3736 if head <= original_range.start {
3737 tail = original_range.end;
3738 } else {
3739 tail = original_range.start;
3740 }
3741 }
3742 SelectMode::All => {
3743 return;
3744 }
3745 };
3746
3747 if head < tail {
3748 pending.start = buffer.anchor_before(head);
3749 pending.end = buffer.anchor_before(tail);
3750 pending.reversed = true;
3751 } else {
3752 pending.start = buffer.anchor_before(tail);
3753 pending.end = buffer.anchor_before(head);
3754 pending.reversed = false;
3755 }
3756
3757 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3758 s.set_pending(pending, mode);
3759 });
3760 } else {
3761 log::error!("update_selection dispatched with no pending selection");
3762 return;
3763 }
3764
3765 self.apply_scroll_delta(scroll_delta, window, cx);
3766 cx.notify();
3767 }
3768
3769 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3770 self.columnar_selection_state.take();
3771 if self.selections.pending_anchor().is_some() {
3772 let selections = self.selections.all::<usize>(cx);
3773 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3774 s.select(selections);
3775 s.clear_pending();
3776 });
3777 }
3778 }
3779
3780 fn select_columns(
3781 &mut self,
3782 head: DisplayPoint,
3783 goal_column: u32,
3784 display_map: &DisplaySnapshot,
3785 window: &mut Window,
3786 cx: &mut Context<Self>,
3787 ) {
3788 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3789 return;
3790 };
3791
3792 let tail = match columnar_state {
3793 ColumnarSelectionState::FromMouse {
3794 selection_tail,
3795 display_point,
3796 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3797 ColumnarSelectionState::FromSelection { selection_tail } => {
3798 selection_tail.to_display_point(display_map)
3799 }
3800 };
3801
3802 let start_row = cmp::min(tail.row(), head.row());
3803 let end_row = cmp::max(tail.row(), head.row());
3804 let start_column = cmp::min(tail.column(), goal_column);
3805 let end_column = cmp::max(tail.column(), goal_column);
3806 let reversed = start_column < tail.column();
3807
3808 let selection_ranges = (start_row.0..=end_row.0)
3809 .map(DisplayRow)
3810 .filter_map(|row| {
3811 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3812 || start_column <= display_map.line_len(row))
3813 && !display_map.is_block_line(row)
3814 {
3815 let start = display_map
3816 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3817 .to_point(display_map);
3818 let end = display_map
3819 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3820 .to_point(display_map);
3821 if reversed {
3822 Some(end..start)
3823 } else {
3824 Some(start..end)
3825 }
3826 } else {
3827 None
3828 }
3829 })
3830 .collect::<Vec<_>>();
3831
3832 let ranges = match columnar_state {
3833 ColumnarSelectionState::FromMouse { .. } => {
3834 let mut non_empty_ranges = selection_ranges
3835 .iter()
3836 .filter(|selection_range| selection_range.start != selection_range.end)
3837 .peekable();
3838 if non_empty_ranges.peek().is_some() {
3839 non_empty_ranges.cloned().collect()
3840 } else {
3841 selection_ranges
3842 }
3843 }
3844 _ => selection_ranges,
3845 };
3846
3847 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3848 s.select_ranges(ranges);
3849 });
3850 cx.notify();
3851 }
3852
3853 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3854 self.selections
3855 .all_adjusted(cx)
3856 .iter()
3857 .any(|selection| !selection.is_empty())
3858 }
3859
3860 pub fn has_pending_nonempty_selection(&self) -> bool {
3861 let pending_nonempty_selection = match self.selections.pending_anchor() {
3862 Some(Selection { start, end, .. }) => start != end,
3863 None => false,
3864 };
3865
3866 pending_nonempty_selection
3867 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3868 }
3869
3870 pub fn has_pending_selection(&self) -> bool {
3871 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3872 }
3873
3874 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3875 self.selection_mark_mode = false;
3876 self.selection_drag_state = SelectionDragState::None;
3877
3878 if self.clear_expanded_diff_hunks(cx) {
3879 cx.notify();
3880 return;
3881 }
3882 if self.dismiss_menus_and_popups(true, window, cx) {
3883 return;
3884 }
3885
3886 if self.mode.is_full()
3887 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3888 {
3889 return;
3890 }
3891
3892 cx.propagate();
3893 }
3894
3895 pub fn dismiss_menus_and_popups(
3896 &mut self,
3897 is_user_requested: bool,
3898 window: &mut Window,
3899 cx: &mut Context<Self>,
3900 ) -> bool {
3901 if self.take_rename(false, window, cx).is_some() {
3902 return true;
3903 }
3904
3905 if hide_hover(self, cx) {
3906 return true;
3907 }
3908
3909 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3910 return true;
3911 }
3912
3913 if self.hide_context_menu(window, cx).is_some() {
3914 return true;
3915 }
3916
3917 if self.mouse_context_menu.take().is_some() {
3918 return true;
3919 }
3920
3921 if is_user_requested && self.discard_edit_prediction(true, cx) {
3922 return true;
3923 }
3924
3925 if self.snippet_stack.pop().is_some() {
3926 return true;
3927 }
3928
3929 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3930 self.dismiss_diagnostics(cx);
3931 return true;
3932 }
3933
3934 false
3935 }
3936
3937 fn linked_editing_ranges_for(
3938 &self,
3939 selection: Range<text::Anchor>,
3940 cx: &App,
3941 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3942 if self.linked_edit_ranges.is_empty() {
3943 return None;
3944 }
3945 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3946 selection.end.buffer_id.and_then(|end_buffer_id| {
3947 if selection.start.buffer_id != Some(end_buffer_id) {
3948 return None;
3949 }
3950 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3951 let snapshot = buffer.read(cx).snapshot();
3952 self.linked_edit_ranges
3953 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3954 .map(|ranges| (ranges, snapshot, buffer))
3955 })?;
3956 use text::ToOffset as TO;
3957 // find offset from the start of current range to current cursor position
3958 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3959
3960 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3961 let start_difference = start_offset - start_byte_offset;
3962 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3963 let end_difference = end_offset - start_byte_offset;
3964 // Current range has associated linked ranges.
3965 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3966 for range in linked_ranges.iter() {
3967 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3968 let end_offset = start_offset + end_difference;
3969 let start_offset = start_offset + start_difference;
3970 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3971 continue;
3972 }
3973 if self.selections.disjoint_anchor_ranges().any(|s| {
3974 if s.start.buffer_id != selection.start.buffer_id
3975 || s.end.buffer_id != selection.end.buffer_id
3976 {
3977 return false;
3978 }
3979 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3980 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3981 }) {
3982 continue;
3983 }
3984 let start = buffer_snapshot.anchor_after(start_offset);
3985 let end = buffer_snapshot.anchor_after(end_offset);
3986 linked_edits
3987 .entry(buffer.clone())
3988 .or_default()
3989 .push(start..end);
3990 }
3991 Some(linked_edits)
3992 }
3993
3994 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3995 let text: Arc<str> = text.into();
3996
3997 if self.read_only(cx) {
3998 return;
3999 }
4000
4001 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4002
4003 let selections = self.selections.all_adjusted(cx);
4004 let mut bracket_inserted = false;
4005 let mut edits = Vec::new();
4006 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4007 let mut new_selections = Vec::with_capacity(selections.len());
4008 let mut new_autoclose_regions = Vec::new();
4009 let snapshot = self.buffer.read(cx).read(cx);
4010 let mut clear_linked_edit_ranges = false;
4011
4012 for (selection, autoclose_region) in
4013 self.selections_with_autoclose_regions(selections, &snapshot)
4014 {
4015 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4016 // Determine if the inserted text matches the opening or closing
4017 // bracket of any of this language's bracket pairs.
4018 let mut bracket_pair = None;
4019 let mut is_bracket_pair_start = false;
4020 let mut is_bracket_pair_end = false;
4021 if !text.is_empty() {
4022 let mut bracket_pair_matching_end = None;
4023 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4024 // and they are removing the character that triggered IME popup.
4025 for (pair, enabled) in scope.brackets() {
4026 if !pair.close && !pair.surround {
4027 continue;
4028 }
4029
4030 if enabled && pair.start.ends_with(text.as_ref()) {
4031 let prefix_len = pair.start.len() - text.len();
4032 let preceding_text_matches_prefix = prefix_len == 0
4033 || (selection.start.column >= (prefix_len as u32)
4034 && snapshot.contains_str_at(
4035 Point::new(
4036 selection.start.row,
4037 selection.start.column - (prefix_len as u32),
4038 ),
4039 &pair.start[..prefix_len],
4040 ));
4041 if preceding_text_matches_prefix {
4042 bracket_pair = Some(pair.clone());
4043 is_bracket_pair_start = true;
4044 break;
4045 }
4046 }
4047 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4048 {
4049 // take first bracket pair matching end, but don't break in case a later bracket
4050 // pair matches start
4051 bracket_pair_matching_end = Some(pair.clone());
4052 }
4053 }
4054 if let Some(end) = bracket_pair_matching_end
4055 && bracket_pair.is_none()
4056 {
4057 bracket_pair = Some(end);
4058 is_bracket_pair_end = true;
4059 }
4060 }
4061
4062 if let Some(bracket_pair) = bracket_pair {
4063 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4064 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4065 let auto_surround =
4066 self.use_auto_surround && snapshot_settings.use_auto_surround;
4067 if selection.is_empty() {
4068 if is_bracket_pair_start {
4069 // If the inserted text is a suffix of an opening bracket and the
4070 // selection is preceded by the rest of the opening bracket, then
4071 // insert the closing bracket.
4072 let following_text_allows_autoclose = snapshot
4073 .chars_at(selection.start)
4074 .next()
4075 .is_none_or(|c| scope.should_autoclose_before(c));
4076
4077 let preceding_text_allows_autoclose = selection.start.column == 0
4078 || snapshot
4079 .reversed_chars_at(selection.start)
4080 .next()
4081 .is_none_or(|c| {
4082 bracket_pair.start != bracket_pair.end
4083 || !snapshot
4084 .char_classifier_at(selection.start)
4085 .is_word(c)
4086 });
4087
4088 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4089 && bracket_pair.start.len() == 1
4090 {
4091 let target = bracket_pair.start.chars().next().unwrap();
4092 let current_line_count = snapshot
4093 .reversed_chars_at(selection.start)
4094 .take_while(|&c| c != '\n')
4095 .filter(|&c| c == target)
4096 .count();
4097 current_line_count % 2 == 1
4098 } else {
4099 false
4100 };
4101
4102 if autoclose
4103 && bracket_pair.close
4104 && following_text_allows_autoclose
4105 && preceding_text_allows_autoclose
4106 && !is_closing_quote
4107 {
4108 let anchor = snapshot.anchor_before(selection.end);
4109 new_selections.push((selection.map(|_| anchor), text.len()));
4110 new_autoclose_regions.push((
4111 anchor,
4112 text.len(),
4113 selection.id,
4114 bracket_pair.clone(),
4115 ));
4116 edits.push((
4117 selection.range(),
4118 format!("{}{}", text, bracket_pair.end).into(),
4119 ));
4120 bracket_inserted = true;
4121 continue;
4122 }
4123 }
4124
4125 if let Some(region) = autoclose_region {
4126 // If the selection is followed by an auto-inserted closing bracket,
4127 // then don't insert that closing bracket again; just move the selection
4128 // past the closing bracket.
4129 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4130 && text.as_ref() == region.pair.end.as_str()
4131 && snapshot.contains_str_at(region.range.end, text.as_ref());
4132 if should_skip {
4133 let anchor = snapshot.anchor_after(selection.end);
4134 new_selections
4135 .push((selection.map(|_| anchor), region.pair.end.len()));
4136 continue;
4137 }
4138 }
4139
4140 let always_treat_brackets_as_autoclosed = snapshot
4141 .language_settings_at(selection.start, cx)
4142 .always_treat_brackets_as_autoclosed;
4143 if always_treat_brackets_as_autoclosed
4144 && is_bracket_pair_end
4145 && snapshot.contains_str_at(selection.end, text.as_ref())
4146 {
4147 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4148 // and the inserted text is a closing bracket and the selection is followed
4149 // by the closing bracket then move the selection past the closing bracket.
4150 let anchor = snapshot.anchor_after(selection.end);
4151 new_selections.push((selection.map(|_| anchor), text.len()));
4152 continue;
4153 }
4154 }
4155 // If an opening bracket is 1 character long and is typed while
4156 // text is selected, then surround that text with the bracket pair.
4157 else if auto_surround
4158 && bracket_pair.surround
4159 && is_bracket_pair_start
4160 && bracket_pair.start.chars().count() == 1
4161 {
4162 edits.push((selection.start..selection.start, text.clone()));
4163 edits.push((
4164 selection.end..selection.end,
4165 bracket_pair.end.as_str().into(),
4166 ));
4167 bracket_inserted = true;
4168 new_selections.push((
4169 Selection {
4170 id: selection.id,
4171 start: snapshot.anchor_after(selection.start),
4172 end: snapshot.anchor_before(selection.end),
4173 reversed: selection.reversed,
4174 goal: selection.goal,
4175 },
4176 0,
4177 ));
4178 continue;
4179 }
4180 }
4181 }
4182
4183 if self.auto_replace_emoji_shortcode
4184 && selection.is_empty()
4185 && text.as_ref().ends_with(':')
4186 && let Some(possible_emoji_short_code) =
4187 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4188 && !possible_emoji_short_code.is_empty()
4189 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4190 {
4191 let emoji_shortcode_start = Point::new(
4192 selection.start.row,
4193 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4194 );
4195
4196 // Remove shortcode from buffer
4197 edits.push((
4198 emoji_shortcode_start..selection.start,
4199 "".to_string().into(),
4200 ));
4201 new_selections.push((
4202 Selection {
4203 id: selection.id,
4204 start: snapshot.anchor_after(emoji_shortcode_start),
4205 end: snapshot.anchor_before(selection.start),
4206 reversed: selection.reversed,
4207 goal: selection.goal,
4208 },
4209 0,
4210 ));
4211
4212 // Insert emoji
4213 let selection_start_anchor = snapshot.anchor_after(selection.start);
4214 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4215 edits.push((selection.start..selection.end, emoji.to_string().into()));
4216
4217 continue;
4218 }
4219
4220 // If not handling any auto-close operation, then just replace the selected
4221 // text with the given input and move the selection to the end of the
4222 // newly inserted text.
4223 let anchor = snapshot.anchor_after(selection.end);
4224 if !self.linked_edit_ranges.is_empty() {
4225 let start_anchor = snapshot.anchor_before(selection.start);
4226
4227 let is_word_char = text.chars().next().is_none_or(|char| {
4228 let classifier = snapshot
4229 .char_classifier_at(start_anchor.to_offset(&snapshot))
4230 .ignore_punctuation(true);
4231 classifier.is_word(char)
4232 });
4233
4234 if is_word_char {
4235 if let Some(ranges) = self
4236 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4237 {
4238 for (buffer, edits) in ranges {
4239 linked_edits
4240 .entry(buffer.clone())
4241 .or_default()
4242 .extend(edits.into_iter().map(|range| (range, text.clone())));
4243 }
4244 }
4245 } else {
4246 clear_linked_edit_ranges = true;
4247 }
4248 }
4249
4250 new_selections.push((selection.map(|_| anchor), 0));
4251 edits.push((selection.start..selection.end, text.clone()));
4252 }
4253
4254 drop(snapshot);
4255
4256 self.transact(window, cx, |this, window, cx| {
4257 if clear_linked_edit_ranges {
4258 this.linked_edit_ranges.clear();
4259 }
4260 let initial_buffer_versions =
4261 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4262
4263 this.buffer.update(cx, |buffer, cx| {
4264 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4265 });
4266 for (buffer, edits) in linked_edits {
4267 buffer.update(cx, |buffer, cx| {
4268 let snapshot = buffer.snapshot();
4269 let edits = edits
4270 .into_iter()
4271 .map(|(range, text)| {
4272 use text::ToPoint as TP;
4273 let end_point = TP::to_point(&range.end, &snapshot);
4274 let start_point = TP::to_point(&range.start, &snapshot);
4275 (start_point..end_point, text)
4276 })
4277 .sorted_by_key(|(range, _)| range.start);
4278 buffer.edit(edits, None, cx);
4279 })
4280 }
4281 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4282 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4283 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4284 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4285 .zip(new_selection_deltas)
4286 .map(|(selection, delta)| Selection {
4287 id: selection.id,
4288 start: selection.start + delta,
4289 end: selection.end + delta,
4290 reversed: selection.reversed,
4291 goal: SelectionGoal::None,
4292 })
4293 .collect::<Vec<_>>();
4294
4295 let mut i = 0;
4296 for (position, delta, selection_id, pair) in new_autoclose_regions {
4297 let position = position.to_offset(&map.buffer_snapshot) + delta;
4298 let start = map.buffer_snapshot.anchor_before(position);
4299 let end = map.buffer_snapshot.anchor_after(position);
4300 while let Some(existing_state) = this.autoclose_regions.get(i) {
4301 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4302 Ordering::Less => i += 1,
4303 Ordering::Greater => break,
4304 Ordering::Equal => {
4305 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4306 Ordering::Less => i += 1,
4307 Ordering::Equal => break,
4308 Ordering::Greater => break,
4309 }
4310 }
4311 }
4312 }
4313 this.autoclose_regions.insert(
4314 i,
4315 AutocloseRegion {
4316 selection_id,
4317 range: start..end,
4318 pair,
4319 },
4320 );
4321 }
4322
4323 let had_active_edit_prediction = this.has_active_edit_prediction();
4324 this.change_selections(
4325 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4326 window,
4327 cx,
4328 |s| s.select(new_selections),
4329 );
4330
4331 if !bracket_inserted
4332 && let Some(on_type_format_task) =
4333 this.trigger_on_type_formatting(text.to_string(), window, cx)
4334 {
4335 on_type_format_task.detach_and_log_err(cx);
4336 }
4337
4338 let editor_settings = EditorSettings::get_global(cx);
4339 if bracket_inserted
4340 && (editor_settings.auto_signature_help
4341 || editor_settings.show_signature_help_after_edits)
4342 {
4343 this.show_signature_help(&ShowSignatureHelp, window, cx);
4344 }
4345
4346 let trigger_in_words =
4347 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4348 if this.hard_wrap.is_some() {
4349 let latest: Range<Point> = this.selections.newest(cx).range();
4350 if latest.is_empty()
4351 && this
4352 .buffer()
4353 .read(cx)
4354 .snapshot(cx)
4355 .line_len(MultiBufferRow(latest.start.row))
4356 == latest.start.column
4357 {
4358 this.rewrap_impl(
4359 RewrapOptions {
4360 override_language_settings: true,
4361 preserve_existing_whitespace: true,
4362 },
4363 cx,
4364 )
4365 }
4366 }
4367 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4368 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4369 this.refresh_edit_prediction(true, false, window, cx);
4370 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4371 });
4372 }
4373
4374 fn find_possible_emoji_shortcode_at_position(
4375 snapshot: &MultiBufferSnapshot,
4376 position: Point,
4377 ) -> Option<String> {
4378 let mut chars = Vec::new();
4379 let mut found_colon = false;
4380 for char in snapshot.reversed_chars_at(position).take(100) {
4381 // Found a possible emoji shortcode in the middle of the buffer
4382 if found_colon {
4383 if char.is_whitespace() {
4384 chars.reverse();
4385 return Some(chars.iter().collect());
4386 }
4387 // If the previous character is not a whitespace, we are in the middle of a word
4388 // and we only want to complete the shortcode if the word is made up of other emojis
4389 let mut containing_word = String::new();
4390 for ch in snapshot
4391 .reversed_chars_at(position)
4392 .skip(chars.len() + 1)
4393 .take(100)
4394 {
4395 if ch.is_whitespace() {
4396 break;
4397 }
4398 containing_word.push(ch);
4399 }
4400 let containing_word = containing_word.chars().rev().collect::<String>();
4401 if util::word_consists_of_emojis(containing_word.as_str()) {
4402 chars.reverse();
4403 return Some(chars.iter().collect());
4404 }
4405 }
4406
4407 if char.is_whitespace() || !char.is_ascii() {
4408 return None;
4409 }
4410 if char == ':' {
4411 found_colon = true;
4412 } else {
4413 chars.push(char);
4414 }
4415 }
4416 // Found a possible emoji shortcode at the beginning of the buffer
4417 chars.reverse();
4418 Some(chars.iter().collect())
4419 }
4420
4421 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4422 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4423 self.transact(window, cx, |this, window, cx| {
4424 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4425 let selections = this.selections.all::<usize>(cx);
4426 let multi_buffer = this.buffer.read(cx);
4427 let buffer = multi_buffer.snapshot(cx);
4428 selections
4429 .iter()
4430 .map(|selection| {
4431 let start_point = selection.start.to_point(&buffer);
4432 let mut existing_indent =
4433 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4434 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4435 let start = selection.start;
4436 let end = selection.end;
4437 let selection_is_empty = start == end;
4438 let language_scope = buffer.language_scope_at(start);
4439 let (
4440 comment_delimiter,
4441 doc_delimiter,
4442 insert_extra_newline,
4443 indent_on_newline,
4444 indent_on_extra_newline,
4445 ) = if let Some(language) = &language_scope {
4446 let mut insert_extra_newline =
4447 insert_extra_newline_brackets(&buffer, start..end, language)
4448 || insert_extra_newline_tree_sitter(&buffer, start..end);
4449
4450 // Comment extension on newline is allowed only for cursor selections
4451 let comment_delimiter = maybe!({
4452 if !selection_is_empty {
4453 return None;
4454 }
4455
4456 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4457 return None;
4458 }
4459
4460 let delimiters = language.line_comment_prefixes();
4461 let max_len_of_delimiter =
4462 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4463 let (snapshot, range) =
4464 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4465
4466 let num_of_whitespaces = snapshot
4467 .chars_for_range(range.clone())
4468 .take_while(|c| c.is_whitespace())
4469 .count();
4470 let comment_candidate = snapshot
4471 .chars_for_range(range.clone())
4472 .skip(num_of_whitespaces)
4473 .take(max_len_of_delimiter)
4474 .collect::<String>();
4475 let (delimiter, trimmed_len) = delimiters
4476 .iter()
4477 .filter_map(|delimiter| {
4478 let prefix = delimiter.trim_end();
4479 if comment_candidate.starts_with(prefix) {
4480 Some((delimiter, prefix.len()))
4481 } else {
4482 None
4483 }
4484 })
4485 .max_by_key(|(_, len)| *len)?;
4486
4487 if let Some(BlockCommentConfig {
4488 start: block_start, ..
4489 }) = language.block_comment()
4490 {
4491 let block_start_trimmed = block_start.trim_end();
4492 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4493 let line_content = snapshot
4494 .chars_for_range(range)
4495 .skip(num_of_whitespaces)
4496 .take(block_start_trimmed.len())
4497 .collect::<String>();
4498
4499 if line_content.starts_with(block_start_trimmed) {
4500 return None;
4501 }
4502 }
4503 }
4504
4505 let cursor_is_placed_after_comment_marker =
4506 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4507 if cursor_is_placed_after_comment_marker {
4508 Some(delimiter.clone())
4509 } else {
4510 None
4511 }
4512 });
4513
4514 let mut indent_on_newline = IndentSize::spaces(0);
4515 let mut indent_on_extra_newline = IndentSize::spaces(0);
4516
4517 let doc_delimiter = maybe!({
4518 if !selection_is_empty {
4519 return None;
4520 }
4521
4522 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4523 return None;
4524 }
4525
4526 let BlockCommentConfig {
4527 start: start_tag,
4528 end: end_tag,
4529 prefix: delimiter,
4530 tab_size: len,
4531 } = language.documentation_comment()?;
4532 let is_within_block_comment = buffer
4533 .language_scope_at(start_point)
4534 .is_some_and(|scope| scope.override_name() == Some("comment"));
4535 if !is_within_block_comment {
4536 return None;
4537 }
4538
4539 let (snapshot, range) =
4540 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4541
4542 let num_of_whitespaces = snapshot
4543 .chars_for_range(range.clone())
4544 .take_while(|c| c.is_whitespace())
4545 .count();
4546
4547 // 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.
4548 let column = start_point.column;
4549 let cursor_is_after_start_tag = {
4550 let start_tag_len = start_tag.len();
4551 let start_tag_line = snapshot
4552 .chars_for_range(range.clone())
4553 .skip(num_of_whitespaces)
4554 .take(start_tag_len)
4555 .collect::<String>();
4556 if start_tag_line.starts_with(start_tag.as_ref()) {
4557 num_of_whitespaces + start_tag_len <= column as usize
4558 } else {
4559 false
4560 }
4561 };
4562
4563 let cursor_is_after_delimiter = {
4564 let delimiter_trim = delimiter.trim_end();
4565 let delimiter_line = snapshot
4566 .chars_for_range(range.clone())
4567 .skip(num_of_whitespaces)
4568 .take(delimiter_trim.len())
4569 .collect::<String>();
4570 if delimiter_line.starts_with(delimiter_trim) {
4571 num_of_whitespaces + delimiter_trim.len() <= column as usize
4572 } else {
4573 false
4574 }
4575 };
4576
4577 let cursor_is_before_end_tag_if_exists = {
4578 let mut char_position = 0u32;
4579 let mut end_tag_offset = None;
4580
4581 'outer: for chunk in snapshot.text_for_range(range) {
4582 if let Some(byte_pos) = chunk.find(&**end_tag) {
4583 let chars_before_match =
4584 chunk[..byte_pos].chars().count() as u32;
4585 end_tag_offset =
4586 Some(char_position + chars_before_match);
4587 break 'outer;
4588 }
4589 char_position += chunk.chars().count() as u32;
4590 }
4591
4592 if let Some(end_tag_offset) = end_tag_offset {
4593 let cursor_is_before_end_tag = column <= end_tag_offset;
4594 if cursor_is_after_start_tag {
4595 if cursor_is_before_end_tag {
4596 insert_extra_newline = true;
4597 }
4598 let cursor_is_at_start_of_end_tag =
4599 column == end_tag_offset;
4600 if cursor_is_at_start_of_end_tag {
4601 indent_on_extra_newline.len = *len;
4602 }
4603 }
4604 cursor_is_before_end_tag
4605 } else {
4606 true
4607 }
4608 };
4609
4610 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4611 && cursor_is_before_end_tag_if_exists
4612 {
4613 if cursor_is_after_start_tag {
4614 indent_on_newline.len = *len;
4615 }
4616 Some(delimiter.clone())
4617 } else {
4618 None
4619 }
4620 });
4621
4622 (
4623 comment_delimiter,
4624 doc_delimiter,
4625 insert_extra_newline,
4626 indent_on_newline,
4627 indent_on_extra_newline,
4628 )
4629 } else {
4630 (
4631 None,
4632 None,
4633 false,
4634 IndentSize::default(),
4635 IndentSize::default(),
4636 )
4637 };
4638
4639 let prevent_auto_indent = doc_delimiter.is_some();
4640 let delimiter = comment_delimiter.or(doc_delimiter);
4641
4642 let capacity_for_delimiter =
4643 delimiter.as_deref().map(str::len).unwrap_or_default();
4644 let mut new_text = String::with_capacity(
4645 1 + capacity_for_delimiter
4646 + existing_indent.len as usize
4647 + indent_on_newline.len as usize
4648 + indent_on_extra_newline.len as usize,
4649 );
4650 new_text.push('\n');
4651 new_text.extend(existing_indent.chars());
4652 new_text.extend(indent_on_newline.chars());
4653
4654 if let Some(delimiter) = &delimiter {
4655 new_text.push_str(delimiter);
4656 }
4657
4658 if insert_extra_newline {
4659 new_text.push('\n');
4660 new_text.extend(existing_indent.chars());
4661 new_text.extend(indent_on_extra_newline.chars());
4662 }
4663
4664 let anchor = buffer.anchor_after(end);
4665 let new_selection = selection.map(|_| anchor);
4666 (
4667 ((start..end, new_text), prevent_auto_indent),
4668 (insert_extra_newline, new_selection),
4669 )
4670 })
4671 .unzip()
4672 };
4673
4674 let mut auto_indent_edits = Vec::new();
4675 let mut edits = Vec::new();
4676 for (edit, prevent_auto_indent) in edits_with_flags {
4677 if prevent_auto_indent {
4678 edits.push(edit);
4679 } else {
4680 auto_indent_edits.push(edit);
4681 }
4682 }
4683 if !edits.is_empty() {
4684 this.edit(edits, cx);
4685 }
4686 if !auto_indent_edits.is_empty() {
4687 this.edit_with_autoindent(auto_indent_edits, cx);
4688 }
4689
4690 let buffer = this.buffer.read(cx).snapshot(cx);
4691 let new_selections = selection_info
4692 .into_iter()
4693 .map(|(extra_newline_inserted, new_selection)| {
4694 let mut cursor = new_selection.end.to_point(&buffer);
4695 if extra_newline_inserted {
4696 cursor.row -= 1;
4697 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4698 }
4699 new_selection.map(|_| cursor)
4700 })
4701 .collect();
4702
4703 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4704 this.refresh_edit_prediction(true, false, window, cx);
4705 });
4706 }
4707
4708 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4709 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4710
4711 let buffer = self.buffer.read(cx);
4712 let snapshot = buffer.snapshot(cx);
4713
4714 let mut edits = Vec::new();
4715 let mut rows = Vec::new();
4716
4717 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4718 let cursor = selection.head();
4719 let row = cursor.row;
4720
4721 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4722
4723 let newline = "\n".to_string();
4724 edits.push((start_of_line..start_of_line, newline));
4725
4726 rows.push(row + rows_inserted as u32);
4727 }
4728
4729 self.transact(window, cx, |editor, window, cx| {
4730 editor.edit(edits, cx);
4731
4732 editor.change_selections(Default::default(), window, cx, |s| {
4733 let mut index = 0;
4734 s.move_cursors_with(|map, _, _| {
4735 let row = rows[index];
4736 index += 1;
4737
4738 let point = Point::new(row, 0);
4739 let boundary = map.next_line_boundary(point).1;
4740 let clipped = map.clip_point(boundary, Bias::Left);
4741
4742 (clipped, SelectionGoal::None)
4743 });
4744 });
4745
4746 let mut indent_edits = Vec::new();
4747 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4748 for row in rows {
4749 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4750 for (row, indent) in indents {
4751 if indent.len == 0 {
4752 continue;
4753 }
4754
4755 let text = match indent.kind {
4756 IndentKind::Space => " ".repeat(indent.len as usize),
4757 IndentKind::Tab => "\t".repeat(indent.len as usize),
4758 };
4759 let point = Point::new(row.0, 0);
4760 indent_edits.push((point..point, text));
4761 }
4762 }
4763 editor.edit(indent_edits, cx);
4764 });
4765 }
4766
4767 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4768 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4769
4770 let buffer = self.buffer.read(cx);
4771 let snapshot = buffer.snapshot(cx);
4772
4773 let mut edits = Vec::new();
4774 let mut rows = Vec::new();
4775 let mut rows_inserted = 0;
4776
4777 for selection in self.selections.all_adjusted(cx) {
4778 let cursor = selection.head();
4779 let row = cursor.row;
4780
4781 let point = Point::new(row + 1, 0);
4782 let start_of_line = snapshot.clip_point(point, Bias::Left);
4783
4784 let newline = "\n".to_string();
4785 edits.push((start_of_line..start_of_line, newline));
4786
4787 rows_inserted += 1;
4788 rows.push(row + rows_inserted);
4789 }
4790
4791 self.transact(window, cx, |editor, window, cx| {
4792 editor.edit(edits, cx);
4793
4794 editor.change_selections(Default::default(), window, cx, |s| {
4795 let mut index = 0;
4796 s.move_cursors_with(|map, _, _| {
4797 let row = rows[index];
4798 index += 1;
4799
4800 let point = Point::new(row, 0);
4801 let boundary = map.next_line_boundary(point).1;
4802 let clipped = map.clip_point(boundary, Bias::Left);
4803
4804 (clipped, SelectionGoal::None)
4805 });
4806 });
4807
4808 let mut indent_edits = Vec::new();
4809 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4810 for row in rows {
4811 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4812 for (row, indent) in indents {
4813 if indent.len == 0 {
4814 continue;
4815 }
4816
4817 let text = match indent.kind {
4818 IndentKind::Space => " ".repeat(indent.len as usize),
4819 IndentKind::Tab => "\t".repeat(indent.len as usize),
4820 };
4821 let point = Point::new(row.0, 0);
4822 indent_edits.push((point..point, text));
4823 }
4824 }
4825 editor.edit(indent_edits, cx);
4826 });
4827 }
4828
4829 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4830 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4831 original_indent_columns: Vec::new(),
4832 });
4833 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4834 }
4835
4836 fn insert_with_autoindent_mode(
4837 &mut self,
4838 text: &str,
4839 autoindent_mode: Option<AutoindentMode>,
4840 window: &mut Window,
4841 cx: &mut Context<Self>,
4842 ) {
4843 if self.read_only(cx) {
4844 return;
4845 }
4846
4847 let text: Arc<str> = text.into();
4848 self.transact(window, cx, |this, window, cx| {
4849 let old_selections = this.selections.all_adjusted(cx);
4850 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4851 let anchors = {
4852 let snapshot = buffer.read(cx);
4853 old_selections
4854 .iter()
4855 .map(|s| {
4856 let anchor = snapshot.anchor_after(s.head());
4857 s.map(|_| anchor)
4858 })
4859 .collect::<Vec<_>>()
4860 };
4861 buffer.edit(
4862 old_selections
4863 .iter()
4864 .map(|s| (s.start..s.end, text.clone())),
4865 autoindent_mode,
4866 cx,
4867 );
4868 anchors
4869 });
4870
4871 this.change_selections(Default::default(), window, cx, |s| {
4872 s.select_anchors(selection_anchors);
4873 });
4874
4875 cx.notify();
4876 });
4877 }
4878
4879 fn trigger_completion_on_input(
4880 &mut self,
4881 text: &str,
4882 trigger_in_words: bool,
4883 window: &mut Window,
4884 cx: &mut Context<Self>,
4885 ) {
4886 let completions_source = self
4887 .context_menu
4888 .borrow()
4889 .as_ref()
4890 .and_then(|menu| match menu {
4891 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4892 CodeContextMenu::CodeActions(_) => None,
4893 });
4894
4895 match completions_source {
4896 Some(CompletionsMenuSource::Words) => {
4897 self.show_word_completions(&ShowWordCompletions, window, cx)
4898 }
4899 Some(CompletionsMenuSource::Normal)
4900 | Some(CompletionsMenuSource::SnippetChoices)
4901 | None
4902 if self.is_completion_trigger(
4903 text,
4904 trigger_in_words,
4905 completions_source.is_some(),
4906 cx,
4907 ) =>
4908 {
4909 self.show_completions(
4910 &ShowCompletions {
4911 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4912 },
4913 window,
4914 cx,
4915 )
4916 }
4917 _ => {
4918 self.hide_context_menu(window, cx);
4919 }
4920 }
4921 }
4922
4923 fn is_completion_trigger(
4924 &self,
4925 text: &str,
4926 trigger_in_words: bool,
4927 menu_is_open: bool,
4928 cx: &mut Context<Self>,
4929 ) -> bool {
4930 let position = self.selections.newest_anchor().head();
4931 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4932 return false;
4933 };
4934
4935 if let Some(completion_provider) = &self.completion_provider {
4936 completion_provider.is_completion_trigger(
4937 &buffer,
4938 position.text_anchor,
4939 text,
4940 trigger_in_words,
4941 menu_is_open,
4942 cx,
4943 )
4944 } else {
4945 false
4946 }
4947 }
4948
4949 /// If any empty selections is touching the start of its innermost containing autoclose
4950 /// region, expand it to select the brackets.
4951 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4952 let selections = self.selections.all::<usize>(cx);
4953 let buffer = self.buffer.read(cx).read(cx);
4954 let new_selections = self
4955 .selections_with_autoclose_regions(selections, &buffer)
4956 .map(|(mut selection, region)| {
4957 if !selection.is_empty() {
4958 return selection;
4959 }
4960
4961 if let Some(region) = region {
4962 let mut range = region.range.to_offset(&buffer);
4963 if selection.start == range.start && range.start >= region.pair.start.len() {
4964 range.start -= region.pair.start.len();
4965 if buffer.contains_str_at(range.start, ®ion.pair.start)
4966 && buffer.contains_str_at(range.end, ®ion.pair.end)
4967 {
4968 range.end += region.pair.end.len();
4969 selection.start = range.start;
4970 selection.end = range.end;
4971
4972 return selection;
4973 }
4974 }
4975 }
4976
4977 let always_treat_brackets_as_autoclosed = buffer
4978 .language_settings_at(selection.start, cx)
4979 .always_treat_brackets_as_autoclosed;
4980
4981 if !always_treat_brackets_as_autoclosed {
4982 return selection;
4983 }
4984
4985 if let Some(scope) = buffer.language_scope_at(selection.start) {
4986 for (pair, enabled) in scope.brackets() {
4987 if !enabled || !pair.close {
4988 continue;
4989 }
4990
4991 if buffer.contains_str_at(selection.start, &pair.end) {
4992 let pair_start_len = pair.start.len();
4993 if buffer.contains_str_at(
4994 selection.start.saturating_sub(pair_start_len),
4995 &pair.start,
4996 ) {
4997 selection.start -= pair_start_len;
4998 selection.end += pair.end.len();
4999
5000 return selection;
5001 }
5002 }
5003 }
5004 }
5005
5006 selection
5007 })
5008 .collect();
5009
5010 drop(buffer);
5011 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5012 selections.select(new_selections)
5013 });
5014 }
5015
5016 /// Iterate the given selections, and for each one, find the smallest surrounding
5017 /// autoclose region. This uses the ordering of the selections and the autoclose
5018 /// regions to avoid repeated comparisons.
5019 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5020 &'a self,
5021 selections: impl IntoIterator<Item = Selection<D>>,
5022 buffer: &'a MultiBufferSnapshot,
5023 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5024 let mut i = 0;
5025 let mut regions = self.autoclose_regions.as_slice();
5026 selections.into_iter().map(move |selection| {
5027 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5028
5029 let mut enclosing = None;
5030 while let Some(pair_state) = regions.get(i) {
5031 if pair_state.range.end.to_offset(buffer) < range.start {
5032 regions = ®ions[i + 1..];
5033 i = 0;
5034 } else if pair_state.range.start.to_offset(buffer) > range.end {
5035 break;
5036 } else {
5037 if pair_state.selection_id == selection.id {
5038 enclosing = Some(pair_state);
5039 }
5040 i += 1;
5041 }
5042 }
5043
5044 (selection, enclosing)
5045 })
5046 }
5047
5048 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5049 fn invalidate_autoclose_regions(
5050 &mut self,
5051 mut selections: &[Selection<Anchor>],
5052 buffer: &MultiBufferSnapshot,
5053 ) {
5054 self.autoclose_regions.retain(|state| {
5055 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5056 return false;
5057 }
5058
5059 let mut i = 0;
5060 while let Some(selection) = selections.get(i) {
5061 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5062 selections = &selections[1..];
5063 continue;
5064 }
5065 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5066 break;
5067 }
5068 if selection.id == state.selection_id {
5069 return true;
5070 } else {
5071 i += 1;
5072 }
5073 }
5074 false
5075 });
5076 }
5077
5078 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5079 let offset = position.to_offset(buffer);
5080 let (word_range, kind) = buffer.surrounding_word(offset, true);
5081 if offset > word_range.start && kind == Some(CharKind::Word) {
5082 Some(
5083 buffer
5084 .text_for_range(word_range.start..offset)
5085 .collect::<String>(),
5086 )
5087 } else {
5088 None
5089 }
5090 }
5091
5092 pub fn toggle_inline_values(
5093 &mut self,
5094 _: &ToggleInlineValues,
5095 _: &mut Window,
5096 cx: &mut Context<Self>,
5097 ) {
5098 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5099
5100 self.refresh_inline_values(cx);
5101 }
5102
5103 pub fn toggle_inlay_hints(
5104 &mut self,
5105 _: &ToggleInlayHints,
5106 _: &mut Window,
5107 cx: &mut Context<Self>,
5108 ) {
5109 self.refresh_inlay_hints(
5110 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5111 cx,
5112 );
5113 }
5114
5115 pub fn inlay_hints_enabled(&self) -> bool {
5116 self.inlay_hint_cache.enabled
5117 }
5118
5119 pub fn inline_values_enabled(&self) -> bool {
5120 self.inline_value_cache.enabled
5121 }
5122
5123 #[cfg(any(test, feature = "test-support"))]
5124 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5125 self.display_map
5126 .read(cx)
5127 .current_inlays()
5128 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5129 .cloned()
5130 .collect()
5131 }
5132
5133 #[cfg(any(test, feature = "test-support"))]
5134 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5135 self.display_map
5136 .read(cx)
5137 .current_inlays()
5138 .cloned()
5139 .collect()
5140 }
5141
5142 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5143 if self.semantics_provider.is_none() || !self.mode.is_full() {
5144 return;
5145 }
5146
5147 let reason_description = reason.description();
5148 let ignore_debounce = matches!(
5149 reason,
5150 InlayHintRefreshReason::SettingsChange(_)
5151 | InlayHintRefreshReason::Toggle(_)
5152 | InlayHintRefreshReason::ExcerptsRemoved(_)
5153 | InlayHintRefreshReason::ModifiersChanged(_)
5154 );
5155 let (invalidate_cache, required_languages) = match reason {
5156 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5157 match self.inlay_hint_cache.modifiers_override(enabled) {
5158 Some(enabled) => {
5159 if enabled {
5160 (InvalidationStrategy::RefreshRequested, None)
5161 } else {
5162 self.splice_inlays(
5163 &self
5164 .visible_inlay_hints(cx)
5165 .iter()
5166 .map(|inlay| inlay.id)
5167 .collect::<Vec<InlayId>>(),
5168 Vec::new(),
5169 cx,
5170 );
5171 return;
5172 }
5173 }
5174 None => return,
5175 }
5176 }
5177 InlayHintRefreshReason::Toggle(enabled) => {
5178 if self.inlay_hint_cache.toggle(enabled) {
5179 if enabled {
5180 (InvalidationStrategy::RefreshRequested, None)
5181 } else {
5182 self.splice_inlays(
5183 &self
5184 .visible_inlay_hints(cx)
5185 .iter()
5186 .map(|inlay| inlay.id)
5187 .collect::<Vec<InlayId>>(),
5188 Vec::new(),
5189 cx,
5190 );
5191 return;
5192 }
5193 } else {
5194 return;
5195 }
5196 }
5197 InlayHintRefreshReason::SettingsChange(new_settings) => {
5198 match self.inlay_hint_cache.update_settings(
5199 &self.buffer,
5200 new_settings,
5201 self.visible_inlay_hints(cx),
5202 cx,
5203 ) {
5204 ControlFlow::Break(Some(InlaySplice {
5205 to_remove,
5206 to_insert,
5207 })) => {
5208 self.splice_inlays(&to_remove, to_insert, cx);
5209 return;
5210 }
5211 ControlFlow::Break(None) => return,
5212 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5213 }
5214 }
5215 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5216 if let Some(InlaySplice {
5217 to_remove,
5218 to_insert,
5219 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5220 {
5221 self.splice_inlays(&to_remove, to_insert, cx);
5222 }
5223 self.display_map.update(cx, |display_map, _| {
5224 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5225 });
5226 return;
5227 }
5228 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5229 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5230 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5231 }
5232 InlayHintRefreshReason::RefreshRequested => {
5233 (InvalidationStrategy::RefreshRequested, None)
5234 }
5235 };
5236
5237 if let Some(InlaySplice {
5238 to_remove,
5239 to_insert,
5240 }) = self.inlay_hint_cache.spawn_hint_refresh(
5241 reason_description,
5242 self.visible_excerpts(required_languages.as_ref(), cx),
5243 invalidate_cache,
5244 ignore_debounce,
5245 cx,
5246 ) {
5247 self.splice_inlays(&to_remove, to_insert, cx);
5248 }
5249 }
5250
5251 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5252 self.display_map
5253 .read(cx)
5254 .current_inlays()
5255 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5256 .cloned()
5257 .collect()
5258 }
5259
5260 pub fn visible_excerpts(
5261 &self,
5262 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5263 cx: &mut Context<Editor>,
5264 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5265 let Some(project) = self.project() else {
5266 return HashMap::default();
5267 };
5268 let project = project.read(cx);
5269 let multi_buffer = self.buffer().read(cx);
5270 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5271 let multi_buffer_visible_start = self
5272 .scroll_manager
5273 .anchor()
5274 .anchor
5275 .to_point(&multi_buffer_snapshot);
5276 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5277 multi_buffer_visible_start
5278 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5279 Bias::Left,
5280 );
5281 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5282 multi_buffer_snapshot
5283 .range_to_buffer_ranges(multi_buffer_visible_range)
5284 .into_iter()
5285 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5286 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5287 let buffer_file = project::File::from_dyn(buffer.file())?;
5288 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5289 let worktree_entry = buffer_worktree
5290 .read(cx)
5291 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5292 if worktree_entry.is_ignored {
5293 return None;
5294 }
5295
5296 let language = buffer.language()?;
5297 if let Some(restrict_to_languages) = restrict_to_languages
5298 && !restrict_to_languages.contains(language)
5299 {
5300 return None;
5301 }
5302 Some((
5303 excerpt_id,
5304 (
5305 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5306 buffer.version().clone(),
5307 excerpt_visible_range,
5308 ),
5309 ))
5310 })
5311 .collect()
5312 }
5313
5314 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5315 TextLayoutDetails {
5316 text_system: window.text_system().clone(),
5317 editor_style: self.style.clone().unwrap(),
5318 rem_size: window.rem_size(),
5319 scroll_anchor: self.scroll_manager.anchor(),
5320 visible_rows: self.visible_line_count(),
5321 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5322 }
5323 }
5324
5325 pub fn splice_inlays(
5326 &self,
5327 to_remove: &[InlayId],
5328 to_insert: Vec<Inlay>,
5329 cx: &mut Context<Self>,
5330 ) {
5331 self.display_map.update(cx, |display_map, cx| {
5332 display_map.splice_inlays(to_remove, to_insert, cx)
5333 });
5334 cx.notify();
5335 }
5336
5337 fn trigger_on_type_formatting(
5338 &self,
5339 input: String,
5340 window: &mut Window,
5341 cx: &mut Context<Self>,
5342 ) -> Option<Task<Result<()>>> {
5343 if input.len() != 1 {
5344 return None;
5345 }
5346
5347 let project = self.project()?;
5348 let position = self.selections.newest_anchor().head();
5349 let (buffer, buffer_position) = self
5350 .buffer
5351 .read(cx)
5352 .text_anchor_for_position(position, cx)?;
5353
5354 let settings = language_settings::language_settings(
5355 buffer
5356 .read(cx)
5357 .language_at(buffer_position)
5358 .map(|l| l.name()),
5359 buffer.read(cx).file(),
5360 cx,
5361 );
5362 if !settings.use_on_type_format {
5363 return None;
5364 }
5365
5366 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5367 // hence we do LSP request & edit on host side only — add formats to host's history.
5368 let push_to_lsp_host_history = true;
5369 // If this is not the host, append its history with new edits.
5370 let push_to_client_history = project.read(cx).is_via_collab();
5371
5372 let on_type_formatting = project.update(cx, |project, cx| {
5373 project.on_type_format(
5374 buffer.clone(),
5375 buffer_position,
5376 input,
5377 push_to_lsp_host_history,
5378 cx,
5379 )
5380 });
5381 Some(cx.spawn_in(window, async move |editor, cx| {
5382 if let Some(transaction) = on_type_formatting.await? {
5383 if push_to_client_history {
5384 buffer
5385 .update(cx, |buffer, _| {
5386 buffer.push_transaction(transaction, Instant::now());
5387 buffer.finalize_last_transaction();
5388 })
5389 .ok();
5390 }
5391 editor.update(cx, |editor, cx| {
5392 editor.refresh_document_highlights(cx);
5393 })?;
5394 }
5395 Ok(())
5396 }))
5397 }
5398
5399 pub fn show_word_completions(
5400 &mut self,
5401 _: &ShowWordCompletions,
5402 window: &mut Window,
5403 cx: &mut Context<Self>,
5404 ) {
5405 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5406 }
5407
5408 pub fn show_completions(
5409 &mut self,
5410 options: &ShowCompletions,
5411 window: &mut Window,
5412 cx: &mut Context<Self>,
5413 ) {
5414 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5415 }
5416
5417 fn open_or_update_completions_menu(
5418 &mut self,
5419 requested_source: Option<CompletionsMenuSource>,
5420 trigger: Option<&str>,
5421 window: &mut Window,
5422 cx: &mut Context<Self>,
5423 ) {
5424 if self.pending_rename.is_some() {
5425 return;
5426 }
5427
5428 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5429
5430 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5431 // inserted and selected. To handle that case, the start of the selection is used so that
5432 // the menu starts with all choices.
5433 let position = self
5434 .selections
5435 .newest_anchor()
5436 .start
5437 .bias_right(&multibuffer_snapshot);
5438 if position.diff_base_anchor.is_some() {
5439 return;
5440 }
5441 let (buffer, buffer_position) =
5442 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5443 output
5444 } else {
5445 return;
5446 };
5447 let buffer_snapshot = buffer.read(cx).snapshot();
5448
5449 let query: Option<Arc<String>> =
5450 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5451
5452 drop(multibuffer_snapshot);
5453
5454 let provider = match requested_source {
5455 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5456 Some(CompletionsMenuSource::Words) => None,
5457 Some(CompletionsMenuSource::SnippetChoices) => {
5458 log::error!("bug: SnippetChoices requested_source is not handled");
5459 None
5460 }
5461 };
5462
5463 let sort_completions = provider
5464 .as_ref()
5465 .is_some_and(|provider| provider.sort_completions());
5466
5467 let filter_completions = provider
5468 .as_ref()
5469 .is_none_or(|provider| provider.filter_completions());
5470
5471 let trigger_kind = match trigger {
5472 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5473 CompletionTriggerKind::TRIGGER_CHARACTER
5474 }
5475 _ => CompletionTriggerKind::INVOKED,
5476 };
5477 let completion_context = CompletionContext {
5478 trigger_character: trigger.and_then(|trigger| {
5479 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5480 Some(String::from(trigger))
5481 } else {
5482 None
5483 }
5484 }),
5485 trigger_kind,
5486 };
5487
5488 // Hide the current completions menu when a trigger char is typed. Without this, cached
5489 // completions from before the trigger char may be reused (#32774). Snippet choices could
5490 // involve trigger chars, so this is skipped in that case.
5491 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5492 {
5493 let menu_is_open = matches!(
5494 self.context_menu.borrow().as_ref(),
5495 Some(CodeContextMenu::Completions(_))
5496 );
5497 if menu_is_open {
5498 self.hide_context_menu(window, cx);
5499 }
5500 }
5501
5502 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5503 if filter_completions {
5504 menu.filter(query.clone(), provider.clone(), window, cx);
5505 }
5506 // When `is_incomplete` is false, no need to re-query completions when the current query
5507 // is a suffix of the initial query.
5508 if !menu.is_incomplete {
5509 // If the new query is a suffix of the old query (typing more characters) and
5510 // the previous result was complete, the existing completions can be filtered.
5511 //
5512 // Note that this is always true for snippet completions.
5513 let query_matches = match (&menu.initial_query, &query) {
5514 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5515 (None, _) => true,
5516 _ => false,
5517 };
5518 if query_matches {
5519 let position_matches = if menu.initial_position == position {
5520 true
5521 } else {
5522 let snapshot = self.buffer.read(cx).read(cx);
5523 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5524 };
5525 if position_matches {
5526 return;
5527 }
5528 }
5529 }
5530 };
5531
5532 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5533 buffer_snapshot.surrounding_word(buffer_position, false)
5534 {
5535 let word_to_exclude = buffer_snapshot
5536 .text_for_range(word_range.clone())
5537 .collect::<String>();
5538 (
5539 buffer_snapshot.anchor_before(word_range.start)
5540 ..buffer_snapshot.anchor_after(buffer_position),
5541 Some(word_to_exclude),
5542 )
5543 } else {
5544 (buffer_position..buffer_position, None)
5545 };
5546
5547 let language = buffer_snapshot
5548 .language_at(buffer_position)
5549 .map(|language| language.name());
5550
5551 let completion_settings =
5552 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5553
5554 let show_completion_documentation = buffer_snapshot
5555 .settings_at(buffer_position, cx)
5556 .show_completion_documentation;
5557
5558 // The document can be large, so stay in reasonable bounds when searching for words,
5559 // otherwise completion pop-up might be slow to appear.
5560 const WORD_LOOKUP_ROWS: u32 = 5_000;
5561 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5562 let min_word_search = buffer_snapshot.clip_point(
5563 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5564 Bias::Left,
5565 );
5566 let max_word_search = buffer_snapshot.clip_point(
5567 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5568 Bias::Right,
5569 );
5570 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5571 ..buffer_snapshot.point_to_offset(max_word_search);
5572
5573 let skip_digits = query
5574 .as_ref()
5575 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5576
5577 let omit_word_completions = match &query {
5578 Some(query) => query.chars().count() < completion_settings.words_min_length,
5579 None => completion_settings.words_min_length != 0,
5580 };
5581
5582 let (mut words, provider_responses) = match &provider {
5583 Some(provider) => {
5584 let provider_responses = provider.completions(
5585 position.excerpt_id,
5586 &buffer,
5587 buffer_position,
5588 completion_context,
5589 window,
5590 cx,
5591 );
5592
5593 let words = match (omit_word_completions, completion_settings.words) {
5594 (true, _) | (_, WordsCompletionMode::Disabled) => {
5595 Task::ready(BTreeMap::default())
5596 }
5597 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5598 .background_spawn(async move {
5599 buffer_snapshot.words_in_range(WordsQuery {
5600 fuzzy_contents: None,
5601 range: word_search_range,
5602 skip_digits,
5603 })
5604 }),
5605 };
5606
5607 (words, provider_responses)
5608 }
5609 None => {
5610 let words = if omit_word_completions {
5611 Task::ready(BTreeMap::default())
5612 } else {
5613 cx.background_spawn(async move {
5614 buffer_snapshot.words_in_range(WordsQuery {
5615 fuzzy_contents: None,
5616 range: word_search_range,
5617 skip_digits,
5618 })
5619 })
5620 };
5621 (words, Task::ready(Ok(Vec::new())))
5622 }
5623 };
5624
5625 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5626
5627 let id = post_inc(&mut self.next_completion_id);
5628 let task = cx.spawn_in(window, async move |editor, cx| {
5629 let Ok(()) = editor.update(cx, |this, _| {
5630 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5631 }) else {
5632 return;
5633 };
5634
5635 // TODO: Ideally completions from different sources would be selectively re-queried, so
5636 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5637 let mut completions = Vec::new();
5638 let mut is_incomplete = false;
5639 if let Some(provider_responses) = provider_responses.await.log_err()
5640 && !provider_responses.is_empty()
5641 {
5642 for response in provider_responses {
5643 completions.extend(response.completions);
5644 is_incomplete = is_incomplete || response.is_incomplete;
5645 }
5646 if completion_settings.words == WordsCompletionMode::Fallback {
5647 words = Task::ready(BTreeMap::default());
5648 }
5649 }
5650
5651 let mut words = words.await;
5652 if let Some(word_to_exclude) = &word_to_exclude {
5653 words.remove(word_to_exclude);
5654 }
5655 for lsp_completion in &completions {
5656 words.remove(&lsp_completion.new_text);
5657 }
5658 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5659 replace_range: word_replace_range.clone(),
5660 new_text: word.clone(),
5661 label: CodeLabel::plain(word, None),
5662 icon_path: None,
5663 documentation: None,
5664 source: CompletionSource::BufferWord {
5665 word_range,
5666 resolved: false,
5667 },
5668 insert_text_mode: Some(InsertTextMode::AS_IS),
5669 confirm: None,
5670 }));
5671
5672 let menu = if completions.is_empty() {
5673 None
5674 } else {
5675 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5676 let languages = editor
5677 .workspace
5678 .as_ref()
5679 .and_then(|(workspace, _)| workspace.upgrade())
5680 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5681 let menu = CompletionsMenu::new(
5682 id,
5683 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5684 sort_completions,
5685 show_completion_documentation,
5686 position,
5687 query.clone(),
5688 is_incomplete,
5689 buffer.clone(),
5690 completions.into(),
5691 snippet_sort_order,
5692 languages,
5693 language,
5694 cx,
5695 );
5696
5697 let query = if filter_completions { query } else { None };
5698 let matches_task = if let Some(query) = query {
5699 menu.do_async_filtering(query, cx)
5700 } else {
5701 Task::ready(menu.unfiltered_matches())
5702 };
5703 (menu, matches_task)
5704 }) else {
5705 return;
5706 };
5707
5708 let matches = matches_task.await;
5709
5710 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5711 // Newer menu already set, so exit.
5712 if let Some(CodeContextMenu::Completions(prev_menu)) =
5713 editor.context_menu.borrow().as_ref()
5714 && prev_menu.id > id
5715 {
5716 return;
5717 };
5718
5719 // Only valid to take prev_menu because it the new menu is immediately set
5720 // below, or the menu is hidden.
5721 if let Some(CodeContextMenu::Completions(prev_menu)) =
5722 editor.context_menu.borrow_mut().take()
5723 {
5724 let position_matches =
5725 if prev_menu.initial_position == menu.initial_position {
5726 true
5727 } else {
5728 let snapshot = editor.buffer.read(cx).read(cx);
5729 prev_menu.initial_position.to_offset(&snapshot)
5730 == menu.initial_position.to_offset(&snapshot)
5731 };
5732 if position_matches {
5733 // Preserve markdown cache before `set_filter_results` because it will
5734 // try to populate the documentation cache.
5735 menu.preserve_markdown_cache(prev_menu);
5736 }
5737 };
5738
5739 menu.set_filter_results(matches, provider, window, cx);
5740 }) else {
5741 return;
5742 };
5743
5744 menu.visible().then_some(menu)
5745 };
5746
5747 editor
5748 .update_in(cx, |editor, window, cx| {
5749 if editor.focus_handle.is_focused(window)
5750 && let Some(menu) = menu
5751 {
5752 *editor.context_menu.borrow_mut() =
5753 Some(CodeContextMenu::Completions(menu));
5754
5755 crate::hover_popover::hide_hover(editor, cx);
5756 if editor.show_edit_predictions_in_menu() {
5757 editor.update_visible_edit_prediction(window, cx);
5758 } else {
5759 editor.discard_edit_prediction(false, cx);
5760 }
5761
5762 cx.notify();
5763 return;
5764 }
5765
5766 if editor.completion_tasks.len() <= 1 {
5767 // If there are no more completion tasks and the last menu was empty, we should hide it.
5768 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5769 // If it was already hidden and we don't show edit predictions in the menu,
5770 // we should also show the edit prediction when available.
5771 if was_hidden && editor.show_edit_predictions_in_menu() {
5772 editor.update_visible_edit_prediction(window, cx);
5773 }
5774 }
5775 })
5776 .ok();
5777 });
5778
5779 self.completion_tasks.push((id, task));
5780 }
5781
5782 #[cfg(feature = "test-support")]
5783 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5784 let menu = self.context_menu.borrow();
5785 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5786 let completions = menu.completions.borrow();
5787 Some(completions.to_vec())
5788 } else {
5789 None
5790 }
5791 }
5792
5793 pub fn with_completions_menu_matching_id<R>(
5794 &self,
5795 id: CompletionId,
5796 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5797 ) -> R {
5798 let mut context_menu = self.context_menu.borrow_mut();
5799 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5800 return f(None);
5801 };
5802 if completions_menu.id != id {
5803 return f(None);
5804 }
5805 f(Some(completions_menu))
5806 }
5807
5808 pub fn confirm_completion(
5809 &mut self,
5810 action: &ConfirmCompletion,
5811 window: &mut Window,
5812 cx: &mut Context<Self>,
5813 ) -> Option<Task<Result<()>>> {
5814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5815 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5816 }
5817
5818 pub fn confirm_completion_insert(
5819 &mut self,
5820 _: &ConfirmCompletionInsert,
5821 window: &mut Window,
5822 cx: &mut Context<Self>,
5823 ) -> Option<Task<Result<()>>> {
5824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5825 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5826 }
5827
5828 pub fn confirm_completion_replace(
5829 &mut self,
5830 _: &ConfirmCompletionReplace,
5831 window: &mut Window,
5832 cx: &mut Context<Self>,
5833 ) -> Option<Task<Result<()>>> {
5834 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5835 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5836 }
5837
5838 pub fn compose_completion(
5839 &mut self,
5840 action: &ComposeCompletion,
5841 window: &mut Window,
5842 cx: &mut Context<Self>,
5843 ) -> Option<Task<Result<()>>> {
5844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5845 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5846 }
5847
5848 fn do_completion(
5849 &mut self,
5850 item_ix: Option<usize>,
5851 intent: CompletionIntent,
5852 window: &mut Window,
5853 cx: &mut Context<Editor>,
5854 ) -> Option<Task<Result<()>>> {
5855 use language::ToOffset as _;
5856
5857 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5858 else {
5859 return None;
5860 };
5861
5862 let candidate_id = {
5863 let entries = completions_menu.entries.borrow();
5864 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5865 if self.show_edit_predictions_in_menu() {
5866 self.discard_edit_prediction(true, cx);
5867 }
5868 mat.candidate_id
5869 };
5870
5871 let completion = completions_menu
5872 .completions
5873 .borrow()
5874 .get(candidate_id)?
5875 .clone();
5876 cx.stop_propagation();
5877
5878 let buffer_handle = completions_menu.buffer.clone();
5879
5880 let CompletionEdit {
5881 new_text,
5882 snippet,
5883 replace_range,
5884 } = process_completion_for_edit(
5885 &completion,
5886 intent,
5887 &buffer_handle,
5888 &completions_menu.initial_position.text_anchor,
5889 cx,
5890 );
5891
5892 let buffer = buffer_handle.read(cx);
5893 let snapshot = self.buffer.read(cx).snapshot(cx);
5894 let newest_anchor = self.selections.newest_anchor();
5895 let replace_range_multibuffer = {
5896 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5897 let multibuffer_anchor = snapshot
5898 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5899 .unwrap()
5900 ..snapshot
5901 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5902 .unwrap();
5903 multibuffer_anchor.start.to_offset(&snapshot)
5904 ..multibuffer_anchor.end.to_offset(&snapshot)
5905 };
5906 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5907 return None;
5908 }
5909
5910 let old_text = buffer
5911 .text_for_range(replace_range.clone())
5912 .collect::<String>();
5913 let lookbehind = newest_anchor
5914 .start
5915 .text_anchor
5916 .to_offset(buffer)
5917 .saturating_sub(replace_range.start);
5918 let lookahead = replace_range
5919 .end
5920 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5921 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5922 let suffix = &old_text[lookbehind.min(old_text.len())..];
5923
5924 let selections = self.selections.all::<usize>(cx);
5925 let mut ranges = Vec::new();
5926 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5927
5928 for selection in &selections {
5929 let range = if selection.id == newest_anchor.id {
5930 replace_range_multibuffer.clone()
5931 } else {
5932 let mut range = selection.range();
5933
5934 // if prefix is present, don't duplicate it
5935 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5936 range.start = range.start.saturating_sub(lookbehind);
5937
5938 // if suffix is also present, mimic the newest cursor and replace it
5939 if selection.id != newest_anchor.id
5940 && snapshot.contains_str_at(range.end, suffix)
5941 {
5942 range.end += lookahead;
5943 }
5944 }
5945 range
5946 };
5947
5948 ranges.push(range.clone());
5949
5950 if !self.linked_edit_ranges.is_empty() {
5951 let start_anchor = snapshot.anchor_before(range.start);
5952 let end_anchor = snapshot.anchor_after(range.end);
5953 if let Some(ranges) = self
5954 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5955 {
5956 for (buffer, edits) in ranges {
5957 linked_edits
5958 .entry(buffer.clone())
5959 .or_default()
5960 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5961 }
5962 }
5963 }
5964 }
5965
5966 let common_prefix_len = old_text
5967 .chars()
5968 .zip(new_text.chars())
5969 .take_while(|(a, b)| a == b)
5970 .map(|(a, _)| a.len_utf8())
5971 .sum::<usize>();
5972
5973 cx.emit(EditorEvent::InputHandled {
5974 utf16_range_to_replace: None,
5975 text: new_text[common_prefix_len..].into(),
5976 });
5977
5978 self.transact(window, cx, |editor, window, cx| {
5979 if let Some(mut snippet) = snippet {
5980 snippet.text = new_text.to_string();
5981 editor
5982 .insert_snippet(&ranges, snippet, window, cx)
5983 .log_err();
5984 } else {
5985 editor.buffer.update(cx, |multi_buffer, cx| {
5986 let auto_indent = match completion.insert_text_mode {
5987 Some(InsertTextMode::AS_IS) => None,
5988 _ => editor.autoindent_mode.clone(),
5989 };
5990 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5991 multi_buffer.edit(edits, auto_indent, cx);
5992 });
5993 }
5994 for (buffer, edits) in linked_edits {
5995 buffer.update(cx, |buffer, cx| {
5996 let snapshot = buffer.snapshot();
5997 let edits = edits
5998 .into_iter()
5999 .map(|(range, text)| {
6000 use text::ToPoint as TP;
6001 let end_point = TP::to_point(&range.end, &snapshot);
6002 let start_point = TP::to_point(&range.start, &snapshot);
6003 (start_point..end_point, text)
6004 })
6005 .sorted_by_key(|(range, _)| range.start);
6006 buffer.edit(edits, None, cx);
6007 })
6008 }
6009
6010 editor.refresh_edit_prediction(true, false, window, cx);
6011 });
6012 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6013
6014 let show_new_completions_on_confirm = completion
6015 .confirm
6016 .as_ref()
6017 .is_some_and(|confirm| confirm(intent, window, cx));
6018 if show_new_completions_on_confirm {
6019 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6020 }
6021
6022 let provider = self.completion_provider.as_ref()?;
6023 drop(completion);
6024 let apply_edits = provider.apply_additional_edits_for_completion(
6025 buffer_handle,
6026 completions_menu.completions.clone(),
6027 candidate_id,
6028 true,
6029 cx,
6030 );
6031
6032 let editor_settings = EditorSettings::get_global(cx);
6033 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6034 // After the code completion is finished, users often want to know what signatures are needed.
6035 // so we should automatically call signature_help
6036 self.show_signature_help(&ShowSignatureHelp, window, cx);
6037 }
6038
6039 Some(cx.foreground_executor().spawn(async move {
6040 apply_edits.await?;
6041 Ok(())
6042 }))
6043 }
6044
6045 pub fn toggle_code_actions(
6046 &mut self,
6047 action: &ToggleCodeActions,
6048 window: &mut Window,
6049 cx: &mut Context<Self>,
6050 ) {
6051 let quick_launch = action.quick_launch;
6052 let mut context_menu = self.context_menu.borrow_mut();
6053 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6054 if code_actions.deployed_from == action.deployed_from {
6055 // Toggle if we're selecting the same one
6056 *context_menu = None;
6057 cx.notify();
6058 return;
6059 } else {
6060 // Otherwise, clear it and start a new one
6061 *context_menu = None;
6062 cx.notify();
6063 }
6064 }
6065 drop(context_menu);
6066 let snapshot = self.snapshot(window, cx);
6067 let deployed_from = action.deployed_from.clone();
6068 let action = action.clone();
6069 self.completion_tasks.clear();
6070 self.discard_edit_prediction(false, cx);
6071
6072 let multibuffer_point = match &action.deployed_from {
6073 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6074 DisplayPoint::new(*row, 0).to_point(&snapshot)
6075 }
6076 _ => self.selections.newest::<Point>(cx).head(),
6077 };
6078 let Some((buffer, buffer_row)) = snapshot
6079 .buffer_snapshot
6080 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6081 .and_then(|(buffer_snapshot, range)| {
6082 self.buffer()
6083 .read(cx)
6084 .buffer(buffer_snapshot.remote_id())
6085 .map(|buffer| (buffer, range.start.row))
6086 })
6087 else {
6088 return;
6089 };
6090 let buffer_id = buffer.read(cx).remote_id();
6091 let tasks = self
6092 .tasks
6093 .get(&(buffer_id, buffer_row))
6094 .map(|t| Arc::new(t.to_owned()));
6095
6096 if !self.focus_handle.is_focused(window) {
6097 return;
6098 }
6099 let project = self.project.clone();
6100
6101 let code_actions_task = match deployed_from {
6102 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6103 _ => self.code_actions(buffer_row, window, cx),
6104 };
6105
6106 let runnable_task = match deployed_from {
6107 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6108 _ => {
6109 let mut task_context_task = Task::ready(None);
6110 if let Some(tasks) = &tasks
6111 && let Some(project) = project
6112 {
6113 task_context_task =
6114 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6115 }
6116
6117 cx.spawn_in(window, {
6118 let buffer = buffer.clone();
6119 async move |editor, cx| {
6120 let task_context = task_context_task.await;
6121
6122 let resolved_tasks =
6123 tasks
6124 .zip(task_context.clone())
6125 .map(|(tasks, task_context)| ResolvedTasks {
6126 templates: tasks.resolve(&task_context).collect(),
6127 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6128 multibuffer_point.row,
6129 tasks.column,
6130 )),
6131 });
6132 let debug_scenarios = editor
6133 .update(cx, |editor, cx| {
6134 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6135 })?
6136 .await;
6137 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6138 }
6139 })
6140 }
6141 };
6142
6143 cx.spawn_in(window, async move |editor, cx| {
6144 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6145 let code_actions = code_actions_task.await;
6146 let spawn_straight_away = quick_launch
6147 && resolved_tasks
6148 .as_ref()
6149 .is_some_and(|tasks| tasks.templates.len() == 1)
6150 && code_actions
6151 .as_ref()
6152 .is_none_or(|actions| actions.is_empty())
6153 && debug_scenarios.is_empty();
6154
6155 editor.update_in(cx, |editor, window, cx| {
6156 crate::hover_popover::hide_hover(editor, cx);
6157 let actions = CodeActionContents::new(
6158 resolved_tasks,
6159 code_actions,
6160 debug_scenarios,
6161 task_context.unwrap_or_default(),
6162 );
6163
6164 // Don't show the menu if there are no actions available
6165 if actions.is_empty() {
6166 cx.notify();
6167 return Task::ready(Ok(()));
6168 }
6169
6170 *editor.context_menu.borrow_mut() =
6171 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6172 buffer,
6173 actions,
6174 selected_item: Default::default(),
6175 scroll_handle: UniformListScrollHandle::default(),
6176 deployed_from,
6177 }));
6178 cx.notify();
6179 if spawn_straight_away
6180 && let Some(task) = editor.confirm_code_action(
6181 &ConfirmCodeAction { item_ix: Some(0) },
6182 window,
6183 cx,
6184 )
6185 {
6186 return task;
6187 }
6188
6189 Task::ready(Ok(()))
6190 })
6191 })
6192 .detach_and_log_err(cx);
6193 }
6194
6195 fn debug_scenarios(
6196 &mut self,
6197 resolved_tasks: &Option<ResolvedTasks>,
6198 buffer: &Entity<Buffer>,
6199 cx: &mut App,
6200 ) -> Task<Vec<task::DebugScenario>> {
6201 maybe!({
6202 let project = self.project()?;
6203 let dap_store = project.read(cx).dap_store();
6204 let mut scenarios = vec![];
6205 let resolved_tasks = resolved_tasks.as_ref()?;
6206 let buffer = buffer.read(cx);
6207 let language = buffer.language()?;
6208 let file = buffer.file();
6209 let debug_adapter = language_settings(language.name().into(), file, cx)
6210 .debuggers
6211 .first()
6212 .map(SharedString::from)
6213 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6214
6215 dap_store.update(cx, |dap_store, cx| {
6216 for (_, task) in &resolved_tasks.templates {
6217 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6218 task.original_task().clone(),
6219 debug_adapter.clone().into(),
6220 task.display_label().to_owned().into(),
6221 cx,
6222 );
6223 scenarios.push(maybe_scenario);
6224 }
6225 });
6226 Some(cx.background_spawn(async move {
6227 futures::future::join_all(scenarios)
6228 .await
6229 .into_iter()
6230 .flatten()
6231 .collect::<Vec<_>>()
6232 }))
6233 })
6234 .unwrap_or_else(|| Task::ready(vec![]))
6235 }
6236
6237 fn code_actions(
6238 &mut self,
6239 buffer_row: u32,
6240 window: &mut Window,
6241 cx: &mut Context<Self>,
6242 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6243 let mut task = self.code_actions_task.take();
6244 cx.spawn_in(window, async move |editor, cx| {
6245 while let Some(prev_task) = task {
6246 prev_task.await.log_err();
6247 task = editor
6248 .update(cx, |this, _| this.code_actions_task.take())
6249 .ok()?;
6250 }
6251
6252 editor
6253 .update(cx, |editor, cx| {
6254 editor
6255 .available_code_actions
6256 .clone()
6257 .and_then(|(location, code_actions)| {
6258 let snapshot = location.buffer.read(cx).snapshot();
6259 let point_range = location.range.to_point(&snapshot);
6260 let point_range = point_range.start.row..=point_range.end.row;
6261 if point_range.contains(&buffer_row) {
6262 Some(code_actions)
6263 } else {
6264 None
6265 }
6266 })
6267 })
6268 .ok()
6269 .flatten()
6270 })
6271 }
6272
6273 pub fn confirm_code_action(
6274 &mut self,
6275 action: &ConfirmCodeAction,
6276 window: &mut Window,
6277 cx: &mut Context<Self>,
6278 ) -> Option<Task<Result<()>>> {
6279 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6280
6281 let actions_menu =
6282 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6283 menu
6284 } else {
6285 return None;
6286 };
6287
6288 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6289 let action = actions_menu.actions.get(action_ix)?;
6290 let title = action.label();
6291 let buffer = actions_menu.buffer;
6292 let workspace = self.workspace()?;
6293
6294 match action {
6295 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6296 workspace.update(cx, |workspace, cx| {
6297 workspace.schedule_resolved_task(
6298 task_source_kind,
6299 resolved_task,
6300 false,
6301 window,
6302 cx,
6303 );
6304
6305 Some(Task::ready(Ok(())))
6306 })
6307 }
6308 CodeActionsItem::CodeAction {
6309 excerpt_id,
6310 action,
6311 provider,
6312 } => {
6313 let apply_code_action =
6314 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6315 let workspace = workspace.downgrade();
6316 Some(cx.spawn_in(window, async move |editor, cx| {
6317 let project_transaction = apply_code_action.await?;
6318 Self::open_project_transaction(
6319 &editor,
6320 workspace,
6321 project_transaction,
6322 title,
6323 cx,
6324 )
6325 .await
6326 }))
6327 }
6328 CodeActionsItem::DebugScenario(scenario) => {
6329 let context = actions_menu.actions.context;
6330
6331 workspace.update(cx, |workspace, cx| {
6332 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6333 workspace.start_debug_session(
6334 scenario,
6335 context,
6336 Some(buffer),
6337 None,
6338 window,
6339 cx,
6340 );
6341 });
6342 Some(Task::ready(Ok(())))
6343 }
6344 }
6345 }
6346
6347 pub async fn open_project_transaction(
6348 editor: &WeakEntity<Editor>,
6349 workspace: WeakEntity<Workspace>,
6350 transaction: ProjectTransaction,
6351 title: String,
6352 cx: &mut AsyncWindowContext,
6353 ) -> Result<()> {
6354 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6355 cx.update(|_, cx| {
6356 entries.sort_unstable_by_key(|(buffer, _)| {
6357 buffer.read(cx).file().map(|f| f.path().clone())
6358 });
6359 })?;
6360
6361 // If the project transaction's edits are all contained within this editor, then
6362 // avoid opening a new editor to display them.
6363
6364 if let Some((buffer, transaction)) = entries.first() {
6365 if entries.len() == 1 {
6366 let excerpt = editor.update(cx, |editor, cx| {
6367 editor
6368 .buffer()
6369 .read(cx)
6370 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6371 })?;
6372 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6373 && excerpted_buffer == *buffer
6374 {
6375 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6376 let excerpt_range = excerpt_range.to_offset(buffer);
6377 buffer
6378 .edited_ranges_for_transaction::<usize>(transaction)
6379 .all(|range| {
6380 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6381 })
6382 })?;
6383
6384 if all_edits_within_excerpt {
6385 return Ok(());
6386 }
6387 }
6388 }
6389 } else {
6390 return Ok(());
6391 }
6392
6393 let mut ranges_to_highlight = Vec::new();
6394 let excerpt_buffer = cx.new(|cx| {
6395 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6396 for (buffer_handle, transaction) in &entries {
6397 let edited_ranges = buffer_handle
6398 .read(cx)
6399 .edited_ranges_for_transaction::<Point>(transaction)
6400 .collect::<Vec<_>>();
6401 let (ranges, _) = multibuffer.set_excerpts_for_path(
6402 PathKey::for_buffer(buffer_handle, cx),
6403 buffer_handle.clone(),
6404 edited_ranges,
6405 DEFAULT_MULTIBUFFER_CONTEXT,
6406 cx,
6407 );
6408
6409 ranges_to_highlight.extend(ranges);
6410 }
6411 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6412 multibuffer
6413 })?;
6414
6415 workspace.update_in(cx, |workspace, window, cx| {
6416 let project = workspace.project().clone();
6417 let editor =
6418 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6419 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6420 editor.update(cx, |editor, cx| {
6421 editor.highlight_background::<Self>(
6422 &ranges_to_highlight,
6423 |theme| theme.colors().editor_highlighted_line_background,
6424 cx,
6425 );
6426 });
6427 })?;
6428
6429 Ok(())
6430 }
6431
6432 pub fn clear_code_action_providers(&mut self) {
6433 self.code_action_providers.clear();
6434 self.available_code_actions.take();
6435 }
6436
6437 pub fn add_code_action_provider(
6438 &mut self,
6439 provider: Rc<dyn CodeActionProvider>,
6440 window: &mut Window,
6441 cx: &mut Context<Self>,
6442 ) {
6443 if self
6444 .code_action_providers
6445 .iter()
6446 .any(|existing_provider| existing_provider.id() == provider.id())
6447 {
6448 return;
6449 }
6450
6451 self.code_action_providers.push(provider);
6452 self.refresh_code_actions(window, cx);
6453 }
6454
6455 pub fn remove_code_action_provider(
6456 &mut self,
6457 id: Arc<str>,
6458 window: &mut Window,
6459 cx: &mut Context<Self>,
6460 ) {
6461 self.code_action_providers
6462 .retain(|provider| provider.id() != id);
6463 self.refresh_code_actions(window, cx);
6464 }
6465
6466 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6467 !self.code_action_providers.is_empty()
6468 && EditorSettings::get_global(cx).toolbar.code_actions
6469 }
6470
6471 pub fn has_available_code_actions(&self) -> bool {
6472 self.available_code_actions
6473 .as_ref()
6474 .is_some_and(|(_, actions)| !actions.is_empty())
6475 }
6476
6477 fn render_inline_code_actions(
6478 &self,
6479 icon_size: ui::IconSize,
6480 display_row: DisplayRow,
6481 is_active: bool,
6482 cx: &mut Context<Self>,
6483 ) -> AnyElement {
6484 let show_tooltip = !self.context_menu_visible();
6485 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6486 .icon_size(icon_size)
6487 .shape(ui::IconButtonShape::Square)
6488 .icon_color(ui::Color::Hidden)
6489 .toggle_state(is_active)
6490 .when(show_tooltip, |this| {
6491 this.tooltip({
6492 let focus_handle = self.focus_handle.clone();
6493 move |window, cx| {
6494 Tooltip::for_action_in(
6495 "Toggle Code Actions",
6496 &ToggleCodeActions {
6497 deployed_from: None,
6498 quick_launch: false,
6499 },
6500 &focus_handle,
6501 window,
6502 cx,
6503 )
6504 }
6505 })
6506 })
6507 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6508 window.focus(&editor.focus_handle(cx));
6509 editor.toggle_code_actions(
6510 &crate::actions::ToggleCodeActions {
6511 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6512 display_row,
6513 )),
6514 quick_launch: false,
6515 },
6516 window,
6517 cx,
6518 );
6519 }))
6520 .into_any_element()
6521 }
6522
6523 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6524 &self.context_menu
6525 }
6526
6527 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6528 let newest_selection = self.selections.newest_anchor().clone();
6529 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6530 let buffer = self.buffer.read(cx);
6531 if newest_selection.head().diff_base_anchor.is_some() {
6532 return None;
6533 }
6534 let (start_buffer, start) =
6535 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6536 let (end_buffer, end) =
6537 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6538 if start_buffer != end_buffer {
6539 return None;
6540 }
6541
6542 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6543 cx.background_executor()
6544 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6545 .await;
6546
6547 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6548 let providers = this.code_action_providers.clone();
6549 let tasks = this
6550 .code_action_providers
6551 .iter()
6552 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6553 .collect::<Vec<_>>();
6554 (providers, tasks)
6555 })?;
6556
6557 let mut actions = Vec::new();
6558 for (provider, provider_actions) in
6559 providers.into_iter().zip(future::join_all(tasks).await)
6560 {
6561 if let Some(provider_actions) = provider_actions.log_err() {
6562 actions.extend(provider_actions.into_iter().map(|action| {
6563 AvailableCodeAction {
6564 excerpt_id: newest_selection.start.excerpt_id,
6565 action,
6566 provider: provider.clone(),
6567 }
6568 }));
6569 }
6570 }
6571
6572 this.update(cx, |this, cx| {
6573 this.available_code_actions = if actions.is_empty() {
6574 None
6575 } else {
6576 Some((
6577 Location {
6578 buffer: start_buffer,
6579 range: start..end,
6580 },
6581 actions.into(),
6582 ))
6583 };
6584 cx.notify();
6585 })
6586 }));
6587 None
6588 }
6589
6590 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6591 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6592 self.show_git_blame_inline = false;
6593
6594 self.show_git_blame_inline_delay_task =
6595 Some(cx.spawn_in(window, async move |this, cx| {
6596 cx.background_executor().timer(delay).await;
6597
6598 this.update(cx, |this, cx| {
6599 this.show_git_blame_inline = true;
6600 cx.notify();
6601 })
6602 .log_err();
6603 }));
6604 }
6605 }
6606
6607 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6608 let snapshot = self.snapshot(window, cx);
6609 let cursor = self.selections.newest::<Point>(cx).head();
6610 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6611 else {
6612 return;
6613 };
6614
6615 let Some(blame) = self.blame.as_ref() else {
6616 return;
6617 };
6618
6619 let row_info = RowInfo {
6620 buffer_id: Some(buffer.remote_id()),
6621 buffer_row: Some(point.row),
6622 ..Default::default()
6623 };
6624 let Some(blame_entry) = blame
6625 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6626 .flatten()
6627 else {
6628 return;
6629 };
6630
6631 let anchor = self.selections.newest_anchor().head();
6632 let position = self.to_pixel_point(anchor, &snapshot, window);
6633 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6634 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6635 };
6636 }
6637
6638 fn show_blame_popover(
6639 &mut self,
6640 blame_entry: &BlameEntry,
6641 position: gpui::Point<Pixels>,
6642 ignore_timeout: bool,
6643 cx: &mut Context<Self>,
6644 ) {
6645 if let Some(state) = &mut self.inline_blame_popover {
6646 state.hide_task.take();
6647 } else {
6648 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6649 let blame_entry = blame_entry.clone();
6650 let show_task = cx.spawn(async move |editor, cx| {
6651 if !ignore_timeout {
6652 cx.background_executor()
6653 .timer(std::time::Duration::from_millis(blame_popover_delay))
6654 .await;
6655 }
6656 editor
6657 .update(cx, |editor, cx| {
6658 editor.inline_blame_popover_show_task.take();
6659 let Some(blame) = editor.blame.as_ref() else {
6660 return;
6661 };
6662 let blame = blame.read(cx);
6663 let details = blame.details_for_entry(&blame_entry);
6664 let markdown = cx.new(|cx| {
6665 Markdown::new(
6666 details
6667 .as_ref()
6668 .map(|message| message.message.clone())
6669 .unwrap_or_default(),
6670 None,
6671 None,
6672 cx,
6673 )
6674 });
6675 editor.inline_blame_popover = Some(InlineBlamePopover {
6676 position,
6677 hide_task: None,
6678 popover_bounds: None,
6679 popover_state: InlineBlamePopoverState {
6680 scroll_handle: ScrollHandle::new(),
6681 commit_message: details,
6682 markdown,
6683 },
6684 keyboard_grace: ignore_timeout,
6685 });
6686 cx.notify();
6687 })
6688 .ok();
6689 });
6690 self.inline_blame_popover_show_task = Some(show_task);
6691 }
6692 }
6693
6694 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6695 self.inline_blame_popover_show_task.take();
6696 if let Some(state) = &mut self.inline_blame_popover {
6697 let hide_task = cx.spawn(async move |editor, cx| {
6698 cx.background_executor()
6699 .timer(std::time::Duration::from_millis(100))
6700 .await;
6701 editor
6702 .update(cx, |editor, cx| {
6703 editor.inline_blame_popover.take();
6704 cx.notify();
6705 })
6706 .ok();
6707 });
6708 state.hide_task = Some(hide_task);
6709 }
6710 }
6711
6712 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6713 if self.pending_rename.is_some() {
6714 return None;
6715 }
6716
6717 let provider = self.semantics_provider.clone()?;
6718 let buffer = self.buffer.read(cx);
6719 let newest_selection = self.selections.newest_anchor().clone();
6720 let cursor_position = newest_selection.head();
6721 let (cursor_buffer, cursor_buffer_position) =
6722 buffer.text_anchor_for_position(cursor_position, cx)?;
6723 let (tail_buffer, tail_buffer_position) =
6724 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6725 if cursor_buffer != tail_buffer {
6726 return None;
6727 }
6728
6729 let snapshot = cursor_buffer.read(cx).snapshot();
6730 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6731 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6732 if start_word_range != end_word_range {
6733 self.document_highlights_task.take();
6734 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6735 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6736 return None;
6737 }
6738
6739 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6740 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6741 cx.background_executor()
6742 .timer(Duration::from_millis(debounce))
6743 .await;
6744
6745 let highlights = if let Some(highlights) = cx
6746 .update(|cx| {
6747 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6748 })
6749 .ok()
6750 .flatten()
6751 {
6752 highlights.await.log_err()
6753 } else {
6754 None
6755 };
6756
6757 if let Some(highlights) = highlights {
6758 this.update(cx, |this, cx| {
6759 if this.pending_rename.is_some() {
6760 return;
6761 }
6762
6763 let buffer = this.buffer.read(cx);
6764 if buffer
6765 .text_anchor_for_position(cursor_position, cx)
6766 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6767 {
6768 return;
6769 }
6770
6771 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6772 let mut write_ranges = Vec::new();
6773 let mut read_ranges = Vec::new();
6774 for highlight in highlights {
6775 let buffer_id = cursor_buffer.read(cx).remote_id();
6776 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6777 {
6778 let start = highlight
6779 .range
6780 .start
6781 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6782 let end = highlight
6783 .range
6784 .end
6785 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6786 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6787 continue;
6788 }
6789
6790 let range = Anchor {
6791 buffer_id: Some(buffer_id),
6792 excerpt_id,
6793 text_anchor: start,
6794 diff_base_anchor: None,
6795 }..Anchor {
6796 buffer_id: Some(buffer_id),
6797 excerpt_id,
6798 text_anchor: end,
6799 diff_base_anchor: None,
6800 };
6801 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6802 write_ranges.push(range);
6803 } else {
6804 read_ranges.push(range);
6805 }
6806 }
6807 }
6808
6809 this.highlight_background::<DocumentHighlightRead>(
6810 &read_ranges,
6811 |theme| theme.colors().editor_document_highlight_read_background,
6812 cx,
6813 );
6814 this.highlight_background::<DocumentHighlightWrite>(
6815 &write_ranges,
6816 |theme| theme.colors().editor_document_highlight_write_background,
6817 cx,
6818 );
6819 cx.notify();
6820 })
6821 .log_err();
6822 }
6823 }));
6824 None
6825 }
6826
6827 fn prepare_highlight_query_from_selection(
6828 &mut self,
6829 cx: &mut Context<Editor>,
6830 ) -> Option<(String, Range<Anchor>)> {
6831 if matches!(self.mode, EditorMode::SingleLine) {
6832 return None;
6833 }
6834 if !EditorSettings::get_global(cx).selection_highlight {
6835 return None;
6836 }
6837 if self.selections.count() != 1 || self.selections.line_mode {
6838 return None;
6839 }
6840 let selection = self.selections.newest::<Point>(cx);
6841 if selection.is_empty() || selection.start.row != selection.end.row {
6842 return None;
6843 }
6844 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6845 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6846 let query = multi_buffer_snapshot
6847 .text_for_range(selection_anchor_range.clone())
6848 .collect::<String>();
6849 if query.trim().is_empty() {
6850 return None;
6851 }
6852 Some((query, selection_anchor_range))
6853 }
6854
6855 fn update_selection_occurrence_highlights(
6856 &mut self,
6857 query_text: String,
6858 query_range: Range<Anchor>,
6859 multi_buffer_range_to_query: Range<Point>,
6860 use_debounce: bool,
6861 window: &mut Window,
6862 cx: &mut Context<Editor>,
6863 ) -> Task<()> {
6864 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6865 cx.spawn_in(window, async move |editor, cx| {
6866 if use_debounce {
6867 cx.background_executor()
6868 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6869 .await;
6870 }
6871 let match_task = cx.background_spawn(async move {
6872 let buffer_ranges = multi_buffer_snapshot
6873 .range_to_buffer_ranges(multi_buffer_range_to_query)
6874 .into_iter()
6875 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6876 let mut match_ranges = Vec::new();
6877 let Ok(regex) = project::search::SearchQuery::text(
6878 query_text.clone(),
6879 false,
6880 false,
6881 false,
6882 Default::default(),
6883 Default::default(),
6884 false,
6885 None,
6886 ) else {
6887 return Vec::default();
6888 };
6889 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6890 match_ranges.extend(
6891 regex
6892 .search(buffer_snapshot, Some(search_range.clone()))
6893 .await
6894 .into_iter()
6895 .filter_map(|match_range| {
6896 let match_start = buffer_snapshot
6897 .anchor_after(search_range.start + match_range.start);
6898 let match_end = buffer_snapshot
6899 .anchor_before(search_range.start + match_range.end);
6900 let match_anchor_range = Anchor::range_in_buffer(
6901 excerpt_id,
6902 buffer_snapshot.remote_id(),
6903 match_start..match_end,
6904 );
6905 (match_anchor_range != query_range).then_some(match_anchor_range)
6906 }),
6907 );
6908 }
6909 match_ranges
6910 });
6911 let match_ranges = match_task.await;
6912 editor
6913 .update_in(cx, |editor, _, cx| {
6914 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6915 if !match_ranges.is_empty() {
6916 editor.highlight_background::<SelectedTextHighlight>(
6917 &match_ranges,
6918 |theme| theme.colors().editor_document_highlight_bracket_background,
6919 cx,
6920 )
6921 }
6922 })
6923 .log_err();
6924 })
6925 }
6926
6927 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6928 struct NewlineFold;
6929 let type_id = std::any::TypeId::of::<NewlineFold>();
6930 if !self.mode.is_single_line() {
6931 return;
6932 }
6933 let snapshot = self.snapshot(window, cx);
6934 if snapshot.buffer_snapshot.max_point().row == 0 {
6935 return;
6936 }
6937 let task = cx.background_spawn(async move {
6938 let new_newlines = snapshot
6939 .buffer_chars_at(0)
6940 .filter_map(|(c, i)| {
6941 if c == '\n' {
6942 Some(
6943 snapshot.buffer_snapshot.anchor_after(i)
6944 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6945 )
6946 } else {
6947 None
6948 }
6949 })
6950 .collect::<Vec<_>>();
6951 let existing_newlines = snapshot
6952 .folds_in_range(0..snapshot.buffer_snapshot.len())
6953 .filter_map(|fold| {
6954 if fold.placeholder.type_tag == Some(type_id) {
6955 Some(fold.range.start..fold.range.end)
6956 } else {
6957 None
6958 }
6959 })
6960 .collect::<Vec<_>>();
6961
6962 (new_newlines, existing_newlines)
6963 });
6964 self.folding_newlines = cx.spawn(async move |this, cx| {
6965 let (new_newlines, existing_newlines) = task.await;
6966 if new_newlines == existing_newlines {
6967 return;
6968 }
6969 let placeholder = FoldPlaceholder {
6970 render: Arc::new(move |_, _, cx| {
6971 div()
6972 .bg(cx.theme().status().hint_background)
6973 .border_b_1()
6974 .size_full()
6975 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6976 .border_color(cx.theme().status().hint)
6977 .child("\\n")
6978 .into_any()
6979 }),
6980 constrain_width: false,
6981 merge_adjacent: false,
6982 type_tag: Some(type_id),
6983 };
6984 let creases = new_newlines
6985 .into_iter()
6986 .map(|range| Crease::simple(range, placeholder.clone()))
6987 .collect();
6988 this.update(cx, |this, cx| {
6989 this.display_map.update(cx, |display_map, cx| {
6990 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6991 display_map.fold(creases, cx);
6992 });
6993 })
6994 .ok();
6995 });
6996 }
6997
6998 fn refresh_selected_text_highlights(
6999 &mut self,
7000 on_buffer_edit: bool,
7001 window: &mut Window,
7002 cx: &mut Context<Editor>,
7003 ) {
7004 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7005 else {
7006 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7007 self.quick_selection_highlight_task.take();
7008 self.debounced_selection_highlight_task.take();
7009 return;
7010 };
7011 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7012 if on_buffer_edit
7013 || self
7014 .quick_selection_highlight_task
7015 .as_ref()
7016 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7017 {
7018 let multi_buffer_visible_start = self
7019 .scroll_manager
7020 .anchor()
7021 .anchor
7022 .to_point(&multi_buffer_snapshot);
7023 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7024 multi_buffer_visible_start
7025 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7026 Bias::Left,
7027 );
7028 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7029 self.quick_selection_highlight_task = Some((
7030 query_range.clone(),
7031 self.update_selection_occurrence_highlights(
7032 query_text.clone(),
7033 query_range.clone(),
7034 multi_buffer_visible_range,
7035 false,
7036 window,
7037 cx,
7038 ),
7039 ));
7040 }
7041 if on_buffer_edit
7042 || self
7043 .debounced_selection_highlight_task
7044 .as_ref()
7045 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7046 {
7047 let multi_buffer_start = multi_buffer_snapshot
7048 .anchor_before(0)
7049 .to_point(&multi_buffer_snapshot);
7050 let multi_buffer_end = multi_buffer_snapshot
7051 .anchor_after(multi_buffer_snapshot.len())
7052 .to_point(&multi_buffer_snapshot);
7053 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7054 self.debounced_selection_highlight_task = Some((
7055 query_range.clone(),
7056 self.update_selection_occurrence_highlights(
7057 query_text,
7058 query_range,
7059 multi_buffer_full_range,
7060 true,
7061 window,
7062 cx,
7063 ),
7064 ));
7065 }
7066 }
7067
7068 pub fn refresh_edit_prediction(
7069 &mut self,
7070 debounce: bool,
7071 user_requested: bool,
7072 window: &mut Window,
7073 cx: &mut Context<Self>,
7074 ) -> Option<()> {
7075 if DisableAiSettings::get_global(cx).disable_ai {
7076 return None;
7077 }
7078
7079 let provider = self.edit_prediction_provider()?;
7080 let cursor = self.selections.newest_anchor().head();
7081 let (buffer, cursor_buffer_position) =
7082 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7083
7084 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7085 self.discard_edit_prediction(false, cx);
7086 return None;
7087 }
7088
7089 if !user_requested
7090 && (!self.should_show_edit_predictions()
7091 || !self.is_focused(window)
7092 || buffer.read(cx).is_empty())
7093 {
7094 self.discard_edit_prediction(false, cx);
7095 return None;
7096 }
7097
7098 self.update_visible_edit_prediction(window, cx);
7099 provider.refresh(
7100 self.project.clone(),
7101 buffer,
7102 cursor_buffer_position,
7103 debounce,
7104 cx,
7105 );
7106 Some(())
7107 }
7108
7109 fn show_edit_predictions_in_menu(&self) -> bool {
7110 match self.edit_prediction_settings {
7111 EditPredictionSettings::Disabled => false,
7112 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7113 }
7114 }
7115
7116 pub fn edit_predictions_enabled(&self) -> bool {
7117 match self.edit_prediction_settings {
7118 EditPredictionSettings::Disabled => false,
7119 EditPredictionSettings::Enabled { .. } => true,
7120 }
7121 }
7122
7123 fn edit_prediction_requires_modifier(&self) -> bool {
7124 match self.edit_prediction_settings {
7125 EditPredictionSettings::Disabled => false,
7126 EditPredictionSettings::Enabled {
7127 preview_requires_modifier,
7128 ..
7129 } => preview_requires_modifier,
7130 }
7131 }
7132
7133 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7134 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7135 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7136 self.discard_edit_prediction(false, cx);
7137 } else {
7138 let selection = self.selections.newest_anchor();
7139 let cursor = selection.head();
7140
7141 if let Some((buffer, cursor_buffer_position)) =
7142 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7143 {
7144 self.edit_prediction_settings =
7145 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7146 }
7147 }
7148 }
7149
7150 fn edit_prediction_settings_at_position(
7151 &self,
7152 buffer: &Entity<Buffer>,
7153 buffer_position: language::Anchor,
7154 cx: &App,
7155 ) -> EditPredictionSettings {
7156 if !self.mode.is_full()
7157 || !self.show_edit_predictions_override.unwrap_or(true)
7158 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7159 {
7160 return EditPredictionSettings::Disabled;
7161 }
7162
7163 let buffer = buffer.read(cx);
7164
7165 let file = buffer.file();
7166
7167 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7168 return EditPredictionSettings::Disabled;
7169 };
7170
7171 let by_provider = matches!(
7172 self.menu_edit_predictions_policy,
7173 MenuEditPredictionsPolicy::ByProvider
7174 );
7175
7176 let show_in_menu = by_provider
7177 && self
7178 .edit_prediction_provider
7179 .as_ref()
7180 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7181
7182 let preview_requires_modifier =
7183 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7184
7185 EditPredictionSettings::Enabled {
7186 show_in_menu,
7187 preview_requires_modifier,
7188 }
7189 }
7190
7191 fn should_show_edit_predictions(&self) -> bool {
7192 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7193 }
7194
7195 pub fn edit_prediction_preview_is_active(&self) -> bool {
7196 matches!(
7197 self.edit_prediction_preview,
7198 EditPredictionPreview::Active { .. }
7199 )
7200 }
7201
7202 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7203 let cursor = self.selections.newest_anchor().head();
7204 if let Some((buffer, cursor_position)) =
7205 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7206 {
7207 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7208 } else {
7209 false
7210 }
7211 }
7212
7213 pub fn supports_minimap(&self, cx: &App) -> bool {
7214 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7215 }
7216
7217 fn edit_predictions_enabled_in_buffer(
7218 &self,
7219 buffer: &Entity<Buffer>,
7220 buffer_position: language::Anchor,
7221 cx: &App,
7222 ) -> bool {
7223 maybe!({
7224 if self.read_only(cx) {
7225 return Some(false);
7226 }
7227 let provider = self.edit_prediction_provider()?;
7228 if !provider.is_enabled(buffer, buffer_position, cx) {
7229 return Some(false);
7230 }
7231 let buffer = buffer.read(cx);
7232 let Some(file) = buffer.file() else {
7233 return Some(true);
7234 };
7235 let settings = all_language_settings(Some(file), cx);
7236 Some(settings.edit_predictions_enabled_for_file(file, cx))
7237 })
7238 .unwrap_or(false)
7239 }
7240
7241 fn cycle_edit_prediction(
7242 &mut self,
7243 direction: Direction,
7244 window: &mut Window,
7245 cx: &mut Context<Self>,
7246 ) -> Option<()> {
7247 let provider = self.edit_prediction_provider()?;
7248 let cursor = self.selections.newest_anchor().head();
7249 let (buffer, cursor_buffer_position) =
7250 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7251 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7252 return None;
7253 }
7254
7255 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7256 self.update_visible_edit_prediction(window, cx);
7257
7258 Some(())
7259 }
7260
7261 pub fn show_edit_prediction(
7262 &mut self,
7263 _: &ShowEditPrediction,
7264 window: &mut Window,
7265 cx: &mut Context<Self>,
7266 ) {
7267 if !self.has_active_edit_prediction() {
7268 self.refresh_edit_prediction(false, true, window, cx);
7269 return;
7270 }
7271
7272 self.update_visible_edit_prediction(window, cx);
7273 }
7274
7275 pub fn display_cursor_names(
7276 &mut self,
7277 _: &DisplayCursorNames,
7278 window: &mut Window,
7279 cx: &mut Context<Self>,
7280 ) {
7281 self.show_cursor_names(window, cx);
7282 }
7283
7284 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7285 self.show_cursor_names = true;
7286 cx.notify();
7287 cx.spawn_in(window, async move |this, cx| {
7288 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7289 this.update(cx, |this, cx| {
7290 this.show_cursor_names = false;
7291 cx.notify()
7292 })
7293 .ok()
7294 })
7295 .detach();
7296 }
7297
7298 pub fn next_edit_prediction(
7299 &mut self,
7300 _: &NextEditPrediction,
7301 window: &mut Window,
7302 cx: &mut Context<Self>,
7303 ) {
7304 if self.has_active_edit_prediction() {
7305 self.cycle_edit_prediction(Direction::Next, window, cx);
7306 } else {
7307 let is_copilot_disabled = self
7308 .refresh_edit_prediction(false, true, window, cx)
7309 .is_none();
7310 if is_copilot_disabled {
7311 cx.propagate();
7312 }
7313 }
7314 }
7315
7316 pub fn previous_edit_prediction(
7317 &mut self,
7318 _: &PreviousEditPrediction,
7319 window: &mut Window,
7320 cx: &mut Context<Self>,
7321 ) {
7322 if self.has_active_edit_prediction() {
7323 self.cycle_edit_prediction(Direction::Prev, window, cx);
7324 } else {
7325 let is_copilot_disabled = self
7326 .refresh_edit_prediction(false, true, window, cx)
7327 .is_none();
7328 if is_copilot_disabled {
7329 cx.propagate();
7330 }
7331 }
7332 }
7333
7334 pub fn accept_edit_prediction(
7335 &mut self,
7336 _: &AcceptEditPrediction,
7337 window: &mut Window,
7338 cx: &mut Context<Self>,
7339 ) {
7340 if self.show_edit_predictions_in_menu() {
7341 self.hide_context_menu(window, cx);
7342 }
7343
7344 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7345 return;
7346 };
7347
7348 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7349
7350 match &active_edit_prediction.completion {
7351 EditPrediction::Move { target, .. } => {
7352 let target = *target;
7353
7354 if let Some(position_map) = &self.last_position_map {
7355 if position_map
7356 .visible_row_range
7357 .contains(&target.to_display_point(&position_map.snapshot).row())
7358 || !self.edit_prediction_requires_modifier()
7359 {
7360 self.unfold_ranges(&[target..target], true, false, cx);
7361 // Note that this is also done in vim's handler of the Tab action.
7362 self.change_selections(
7363 SelectionEffects::scroll(Autoscroll::newest()),
7364 window,
7365 cx,
7366 |selections| {
7367 selections.select_anchor_ranges([target..target]);
7368 },
7369 );
7370 self.clear_row_highlights::<EditPredictionPreview>();
7371
7372 self.edit_prediction_preview
7373 .set_previous_scroll_position(None);
7374 } else {
7375 self.edit_prediction_preview
7376 .set_previous_scroll_position(Some(
7377 position_map.snapshot.scroll_anchor,
7378 ));
7379
7380 self.highlight_rows::<EditPredictionPreview>(
7381 target..target,
7382 cx.theme().colors().editor_highlighted_line_background,
7383 RowHighlightOptions {
7384 autoscroll: true,
7385 ..Default::default()
7386 },
7387 cx,
7388 );
7389 self.request_autoscroll(Autoscroll::fit(), cx);
7390 }
7391 }
7392 }
7393 EditPrediction::Edit { edits, .. } => {
7394 if let Some(provider) = self.edit_prediction_provider() {
7395 provider.accept(cx);
7396 }
7397
7398 // Store the transaction ID and selections before applying the edit
7399 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7400
7401 let snapshot = self.buffer.read(cx).snapshot(cx);
7402 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7403
7404 self.buffer.update(cx, |buffer, cx| {
7405 buffer.edit(edits.iter().cloned(), None, cx)
7406 });
7407
7408 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7409 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7410 });
7411
7412 let selections = self.selections.disjoint_anchors();
7413 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7414 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7415 if has_new_transaction {
7416 self.selection_history
7417 .insert_transaction(transaction_id_now, selections);
7418 }
7419 }
7420
7421 self.update_visible_edit_prediction(window, cx);
7422 if self.active_edit_prediction.is_none() {
7423 self.refresh_edit_prediction(true, true, window, cx);
7424 }
7425
7426 cx.notify();
7427 }
7428 }
7429
7430 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7431 }
7432
7433 pub fn accept_partial_edit_prediction(
7434 &mut self,
7435 _: &AcceptPartialEditPrediction,
7436 window: &mut Window,
7437 cx: &mut Context<Self>,
7438 ) {
7439 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7440 return;
7441 };
7442 if self.selections.count() != 1 {
7443 return;
7444 }
7445
7446 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7447
7448 match &active_edit_prediction.completion {
7449 EditPrediction::Move { target, .. } => {
7450 let target = *target;
7451 self.change_selections(
7452 SelectionEffects::scroll(Autoscroll::newest()),
7453 window,
7454 cx,
7455 |selections| {
7456 selections.select_anchor_ranges([target..target]);
7457 },
7458 );
7459 }
7460 EditPrediction::Edit { edits, .. } => {
7461 // Find an insertion that starts at the cursor position.
7462 let snapshot = self.buffer.read(cx).snapshot(cx);
7463 let cursor_offset = self.selections.newest::<usize>(cx).head();
7464 let insertion = edits.iter().find_map(|(range, text)| {
7465 let range = range.to_offset(&snapshot);
7466 if range.is_empty() && range.start == cursor_offset {
7467 Some(text)
7468 } else {
7469 None
7470 }
7471 });
7472
7473 if let Some(text) = insertion {
7474 let mut partial_completion = text
7475 .chars()
7476 .by_ref()
7477 .take_while(|c| c.is_alphabetic())
7478 .collect::<String>();
7479 if partial_completion.is_empty() {
7480 partial_completion = text
7481 .chars()
7482 .by_ref()
7483 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7484 .collect::<String>();
7485 }
7486
7487 cx.emit(EditorEvent::InputHandled {
7488 utf16_range_to_replace: None,
7489 text: partial_completion.clone().into(),
7490 });
7491
7492 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7493
7494 self.refresh_edit_prediction(true, true, window, cx);
7495 cx.notify();
7496 } else {
7497 self.accept_edit_prediction(&Default::default(), window, cx);
7498 }
7499 }
7500 }
7501 }
7502
7503 fn discard_edit_prediction(
7504 &mut self,
7505 should_report_edit_prediction_event: bool,
7506 cx: &mut Context<Self>,
7507 ) -> bool {
7508 if should_report_edit_prediction_event {
7509 let completion_id = self
7510 .active_edit_prediction
7511 .as_ref()
7512 .and_then(|active_completion| active_completion.completion_id.clone());
7513
7514 self.report_edit_prediction_event(completion_id, false, cx);
7515 }
7516
7517 if let Some(provider) = self.edit_prediction_provider() {
7518 provider.discard(cx);
7519 }
7520
7521 self.take_active_edit_prediction(cx)
7522 }
7523
7524 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7525 let Some(provider) = self.edit_prediction_provider() else {
7526 return;
7527 };
7528
7529 let Some((_, buffer, _)) = self
7530 .buffer
7531 .read(cx)
7532 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7533 else {
7534 return;
7535 };
7536
7537 let extension = buffer
7538 .read(cx)
7539 .file()
7540 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7541
7542 let event_type = match accepted {
7543 true => "Edit Prediction Accepted",
7544 false => "Edit Prediction Discarded",
7545 };
7546 telemetry::event!(
7547 event_type,
7548 provider = provider.name(),
7549 prediction_id = id,
7550 suggestion_accepted = accepted,
7551 file_extension = extension,
7552 );
7553 }
7554
7555 pub fn has_active_edit_prediction(&self) -> bool {
7556 self.active_edit_prediction.is_some()
7557 }
7558
7559 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7560 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7561 return false;
7562 };
7563
7564 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7565 self.clear_highlights::<EditPredictionHighlight>(cx);
7566 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7567 true
7568 }
7569
7570 /// Returns true when we're displaying the edit prediction popover below the cursor
7571 /// like we are not previewing and the LSP autocomplete menu is visible
7572 /// or we are in `when_holding_modifier` mode.
7573 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7574 if self.edit_prediction_preview_is_active()
7575 || !self.show_edit_predictions_in_menu()
7576 || !self.edit_predictions_enabled()
7577 {
7578 return false;
7579 }
7580
7581 if self.has_visible_completions_menu() {
7582 return true;
7583 }
7584
7585 has_completion && self.edit_prediction_requires_modifier()
7586 }
7587
7588 fn handle_modifiers_changed(
7589 &mut self,
7590 modifiers: Modifiers,
7591 position_map: &PositionMap,
7592 window: &mut Window,
7593 cx: &mut Context<Self>,
7594 ) {
7595 if self.show_edit_predictions_in_menu() {
7596 self.update_edit_prediction_preview(&modifiers, window, cx);
7597 }
7598
7599 self.update_selection_mode(&modifiers, position_map, window, cx);
7600
7601 let mouse_position = window.mouse_position();
7602 if !position_map.text_hitbox.is_hovered(window) {
7603 return;
7604 }
7605
7606 self.update_hovered_link(
7607 position_map.point_for_position(mouse_position),
7608 &position_map.snapshot,
7609 modifiers,
7610 window,
7611 cx,
7612 )
7613 }
7614
7615 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7616 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7617 if invert {
7618 match multi_cursor_setting {
7619 MultiCursorModifier::Alt => modifiers.alt,
7620 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7621 }
7622 } else {
7623 match multi_cursor_setting {
7624 MultiCursorModifier::Alt => modifiers.secondary(),
7625 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7626 }
7627 }
7628 }
7629
7630 fn columnar_selection_mode(
7631 modifiers: &Modifiers,
7632 cx: &mut Context<Self>,
7633 ) -> Option<ColumnarMode> {
7634 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7635 if Self::multi_cursor_modifier(false, modifiers, cx) {
7636 Some(ColumnarMode::FromMouse)
7637 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7638 Some(ColumnarMode::FromSelection)
7639 } else {
7640 None
7641 }
7642 } else {
7643 None
7644 }
7645 }
7646
7647 fn update_selection_mode(
7648 &mut self,
7649 modifiers: &Modifiers,
7650 position_map: &PositionMap,
7651 window: &mut Window,
7652 cx: &mut Context<Self>,
7653 ) {
7654 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7655 return;
7656 };
7657 if self.selections.pending.is_none() {
7658 return;
7659 }
7660
7661 let mouse_position = window.mouse_position();
7662 let point_for_position = position_map.point_for_position(mouse_position);
7663 let position = point_for_position.previous_valid;
7664
7665 self.select(
7666 SelectPhase::BeginColumnar {
7667 position,
7668 reset: false,
7669 mode,
7670 goal_column: point_for_position.exact_unclipped.column(),
7671 },
7672 window,
7673 cx,
7674 );
7675 }
7676
7677 fn update_edit_prediction_preview(
7678 &mut self,
7679 modifiers: &Modifiers,
7680 window: &mut Window,
7681 cx: &mut Context<Self>,
7682 ) {
7683 let mut modifiers_held = false;
7684 if let Some(accept_keystroke) = self
7685 .accept_edit_prediction_keybind(false, window, cx)
7686 .keystroke()
7687 {
7688 modifiers_held = modifiers_held
7689 || (&accept_keystroke.modifiers == modifiers
7690 && accept_keystroke.modifiers.modified());
7691 };
7692 if let Some(accept_partial_keystroke) = self
7693 .accept_edit_prediction_keybind(true, window, cx)
7694 .keystroke()
7695 {
7696 modifiers_held = modifiers_held
7697 || (&accept_partial_keystroke.modifiers == modifiers
7698 && accept_partial_keystroke.modifiers.modified());
7699 }
7700
7701 if modifiers_held {
7702 if matches!(
7703 self.edit_prediction_preview,
7704 EditPredictionPreview::Inactive { .. }
7705 ) {
7706 self.edit_prediction_preview = EditPredictionPreview::Active {
7707 previous_scroll_position: None,
7708 since: Instant::now(),
7709 };
7710
7711 self.update_visible_edit_prediction(window, cx);
7712 cx.notify();
7713 }
7714 } else if let EditPredictionPreview::Active {
7715 previous_scroll_position,
7716 since,
7717 } = self.edit_prediction_preview
7718 {
7719 if let (Some(previous_scroll_position), Some(position_map)) =
7720 (previous_scroll_position, self.last_position_map.as_ref())
7721 {
7722 self.set_scroll_position(
7723 previous_scroll_position
7724 .scroll_position(&position_map.snapshot.display_snapshot),
7725 window,
7726 cx,
7727 );
7728 }
7729
7730 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7731 released_too_fast: since.elapsed() < Duration::from_millis(200),
7732 };
7733 self.clear_row_highlights::<EditPredictionPreview>();
7734 self.update_visible_edit_prediction(window, cx);
7735 cx.notify();
7736 }
7737 }
7738
7739 fn update_visible_edit_prediction(
7740 &mut self,
7741 _window: &mut Window,
7742 cx: &mut Context<Self>,
7743 ) -> Option<()> {
7744 if DisableAiSettings::get_global(cx).disable_ai {
7745 return None;
7746 }
7747
7748 let selection = self.selections.newest_anchor();
7749 let cursor = selection.head();
7750 let multibuffer = self.buffer.read(cx).snapshot(cx);
7751 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7752 let excerpt_id = cursor.excerpt_id;
7753
7754 let show_in_menu = self.show_edit_predictions_in_menu();
7755 let completions_menu_has_precedence = !show_in_menu
7756 && (self.context_menu.borrow().is_some()
7757 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7758
7759 if completions_menu_has_precedence
7760 || !offset_selection.is_empty()
7761 || self
7762 .active_edit_prediction
7763 .as_ref()
7764 .is_some_and(|completion| {
7765 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7766 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7767 !invalidation_range.contains(&offset_selection.head())
7768 })
7769 {
7770 self.discard_edit_prediction(false, cx);
7771 return None;
7772 }
7773
7774 self.take_active_edit_prediction(cx);
7775 let Some(provider) = self.edit_prediction_provider() else {
7776 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7777 return None;
7778 };
7779
7780 let (buffer, cursor_buffer_position) =
7781 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7782
7783 self.edit_prediction_settings =
7784 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7785
7786 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7787 self.discard_edit_prediction(false, cx);
7788 return None;
7789 };
7790
7791 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7792
7793 if self.edit_prediction_indent_conflict {
7794 let cursor_point = cursor.to_point(&multibuffer);
7795
7796 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7797
7798 if let Some((_, indent)) = indents.iter().next()
7799 && indent.len == cursor_point.column
7800 {
7801 self.edit_prediction_indent_conflict = false;
7802 }
7803 }
7804
7805 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7806 let edits = edit_prediction
7807 .edits
7808 .into_iter()
7809 .flat_map(|(range, new_text)| {
7810 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7811 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7812 Some((start..end, new_text))
7813 })
7814 .collect::<Vec<_>>();
7815 if edits.is_empty() {
7816 return None;
7817 }
7818
7819 let first_edit_start = edits.first().unwrap().0.start;
7820 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7821 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7822
7823 let last_edit_end = edits.last().unwrap().0.end;
7824 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7825 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7826
7827 let cursor_row = cursor.to_point(&multibuffer).row;
7828
7829 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7830
7831 let mut inlay_ids = Vec::new();
7832 let invalidation_row_range;
7833 let move_invalidation_row_range = if cursor_row < edit_start_row {
7834 Some(cursor_row..edit_end_row)
7835 } else if cursor_row > edit_end_row {
7836 Some(edit_start_row..cursor_row)
7837 } else {
7838 None
7839 };
7840 let supports_jump = self
7841 .edit_prediction_provider
7842 .as_ref()
7843 .map(|provider| provider.provider.supports_jump_to_edit())
7844 .unwrap_or(true);
7845
7846 let is_move = supports_jump
7847 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7848 let completion = if is_move {
7849 invalidation_row_range =
7850 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7851 let target = first_edit_start;
7852 EditPrediction::Move { target, snapshot }
7853 } else {
7854 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7855 && !self.edit_predictions_hidden_for_vim_mode;
7856
7857 if show_completions_in_buffer {
7858 if edits
7859 .iter()
7860 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7861 {
7862 let mut inlays = Vec::new();
7863 for (range, new_text) in &edits {
7864 let inlay = Inlay::edit_prediction(
7865 post_inc(&mut self.next_inlay_id),
7866 range.start,
7867 new_text.as_str(),
7868 );
7869 inlay_ids.push(inlay.id);
7870 inlays.push(inlay);
7871 }
7872
7873 self.splice_inlays(&[], inlays, cx);
7874 } else {
7875 let background_color = cx.theme().status().deleted_background;
7876 self.highlight_text::<EditPredictionHighlight>(
7877 edits.iter().map(|(range, _)| range.clone()).collect(),
7878 HighlightStyle {
7879 background_color: Some(background_color),
7880 ..Default::default()
7881 },
7882 cx,
7883 );
7884 }
7885 }
7886
7887 invalidation_row_range = edit_start_row..edit_end_row;
7888
7889 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7890 if provider.show_tab_accept_marker() {
7891 EditDisplayMode::TabAccept
7892 } else {
7893 EditDisplayMode::Inline
7894 }
7895 } else {
7896 EditDisplayMode::DiffPopover
7897 };
7898
7899 EditPrediction::Edit {
7900 edits,
7901 edit_preview: edit_prediction.edit_preview,
7902 display_mode,
7903 snapshot,
7904 }
7905 };
7906
7907 let invalidation_range = multibuffer
7908 .anchor_before(Point::new(invalidation_row_range.start, 0))
7909 ..multibuffer.anchor_after(Point::new(
7910 invalidation_row_range.end,
7911 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7912 ));
7913
7914 self.stale_edit_prediction_in_menu = None;
7915 self.active_edit_prediction = Some(EditPredictionState {
7916 inlay_ids,
7917 completion,
7918 completion_id: edit_prediction.id,
7919 invalidation_range,
7920 });
7921
7922 cx.notify();
7923
7924 Some(())
7925 }
7926
7927 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7928 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7929 }
7930
7931 fn clear_tasks(&mut self) {
7932 self.tasks.clear()
7933 }
7934
7935 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7936 if self.tasks.insert(key, value).is_some() {
7937 // This case should hopefully be rare, but just in case...
7938 log::error!(
7939 "multiple different run targets found on a single line, only the last target will be rendered"
7940 )
7941 }
7942 }
7943
7944 /// Get all display points of breakpoints that will be rendered within editor
7945 ///
7946 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7947 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7948 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7949 fn active_breakpoints(
7950 &self,
7951 range: Range<DisplayRow>,
7952 window: &mut Window,
7953 cx: &mut Context<Self>,
7954 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7955 let mut breakpoint_display_points = HashMap::default();
7956
7957 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7958 return breakpoint_display_points;
7959 };
7960
7961 let snapshot = self.snapshot(window, cx);
7962
7963 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7964 let Some(project) = self.project() else {
7965 return breakpoint_display_points;
7966 };
7967
7968 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7969 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7970
7971 for (buffer_snapshot, range, excerpt_id) in
7972 multi_buffer_snapshot.range_to_buffer_ranges(range)
7973 {
7974 let Some(buffer) = project
7975 .read(cx)
7976 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7977 else {
7978 continue;
7979 };
7980 let breakpoints = breakpoint_store.read(cx).breakpoints(
7981 &buffer,
7982 Some(
7983 buffer_snapshot.anchor_before(range.start)
7984 ..buffer_snapshot.anchor_after(range.end),
7985 ),
7986 buffer_snapshot,
7987 cx,
7988 );
7989 for (breakpoint, state) in breakpoints {
7990 let multi_buffer_anchor =
7991 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7992 let position = multi_buffer_anchor
7993 .to_point(multi_buffer_snapshot)
7994 .to_display_point(&snapshot);
7995
7996 breakpoint_display_points.insert(
7997 position.row(),
7998 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7999 );
8000 }
8001 }
8002
8003 breakpoint_display_points
8004 }
8005
8006 fn breakpoint_context_menu(
8007 &self,
8008 anchor: Anchor,
8009 window: &mut Window,
8010 cx: &mut Context<Self>,
8011 ) -> Entity<ui::ContextMenu> {
8012 let weak_editor = cx.weak_entity();
8013 let focus_handle = self.focus_handle(cx);
8014
8015 let row = self
8016 .buffer
8017 .read(cx)
8018 .snapshot(cx)
8019 .summary_for_anchor::<Point>(&anchor)
8020 .row;
8021
8022 let breakpoint = self
8023 .breakpoint_at_row(row, window, cx)
8024 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8025
8026 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8027 "Edit Log Breakpoint"
8028 } else {
8029 "Set Log Breakpoint"
8030 };
8031
8032 let condition_breakpoint_msg = if breakpoint
8033 .as_ref()
8034 .is_some_and(|bp| bp.1.condition.is_some())
8035 {
8036 "Edit Condition Breakpoint"
8037 } else {
8038 "Set Condition Breakpoint"
8039 };
8040
8041 let hit_condition_breakpoint_msg = if breakpoint
8042 .as_ref()
8043 .is_some_and(|bp| bp.1.hit_condition.is_some())
8044 {
8045 "Edit Hit Condition Breakpoint"
8046 } else {
8047 "Set Hit Condition Breakpoint"
8048 };
8049
8050 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8051 "Unset Breakpoint"
8052 } else {
8053 "Set Breakpoint"
8054 };
8055
8056 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8057
8058 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8059 BreakpointState::Enabled => Some("Disable"),
8060 BreakpointState::Disabled => Some("Enable"),
8061 });
8062
8063 let (anchor, breakpoint) =
8064 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8065
8066 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8067 menu.on_blur_subscription(Subscription::new(|| {}))
8068 .context(focus_handle)
8069 .when(run_to_cursor, |this| {
8070 let weak_editor = weak_editor.clone();
8071 this.entry("Run to cursor", None, move |window, cx| {
8072 weak_editor
8073 .update(cx, |editor, cx| {
8074 editor.change_selections(
8075 SelectionEffects::no_scroll(),
8076 window,
8077 cx,
8078 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8079 );
8080 })
8081 .ok();
8082
8083 window.dispatch_action(Box::new(RunToCursor), cx);
8084 })
8085 .separator()
8086 })
8087 .when_some(toggle_state_msg, |this, msg| {
8088 this.entry(msg, None, {
8089 let weak_editor = weak_editor.clone();
8090 let breakpoint = breakpoint.clone();
8091 move |_window, cx| {
8092 weak_editor
8093 .update(cx, |this, cx| {
8094 this.edit_breakpoint_at_anchor(
8095 anchor,
8096 breakpoint.as_ref().clone(),
8097 BreakpointEditAction::InvertState,
8098 cx,
8099 );
8100 })
8101 .log_err();
8102 }
8103 })
8104 })
8105 .entry(set_breakpoint_msg, None, {
8106 let weak_editor = weak_editor.clone();
8107 let breakpoint = breakpoint.clone();
8108 move |_window, cx| {
8109 weak_editor
8110 .update(cx, |this, cx| {
8111 this.edit_breakpoint_at_anchor(
8112 anchor,
8113 breakpoint.as_ref().clone(),
8114 BreakpointEditAction::Toggle,
8115 cx,
8116 );
8117 })
8118 .log_err();
8119 }
8120 })
8121 .entry(log_breakpoint_msg, None, {
8122 let breakpoint = breakpoint.clone();
8123 let weak_editor = weak_editor.clone();
8124 move |window, cx| {
8125 weak_editor
8126 .update(cx, |this, cx| {
8127 this.add_edit_breakpoint_block(
8128 anchor,
8129 breakpoint.as_ref(),
8130 BreakpointPromptEditAction::Log,
8131 window,
8132 cx,
8133 );
8134 })
8135 .log_err();
8136 }
8137 })
8138 .entry(condition_breakpoint_msg, None, {
8139 let breakpoint = breakpoint.clone();
8140 let weak_editor = weak_editor.clone();
8141 move |window, cx| {
8142 weak_editor
8143 .update(cx, |this, cx| {
8144 this.add_edit_breakpoint_block(
8145 anchor,
8146 breakpoint.as_ref(),
8147 BreakpointPromptEditAction::Condition,
8148 window,
8149 cx,
8150 );
8151 })
8152 .log_err();
8153 }
8154 })
8155 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8156 weak_editor
8157 .update(cx, |this, cx| {
8158 this.add_edit_breakpoint_block(
8159 anchor,
8160 breakpoint.as_ref(),
8161 BreakpointPromptEditAction::HitCondition,
8162 window,
8163 cx,
8164 );
8165 })
8166 .log_err();
8167 })
8168 })
8169 }
8170
8171 fn render_breakpoint(
8172 &self,
8173 position: Anchor,
8174 row: DisplayRow,
8175 breakpoint: &Breakpoint,
8176 state: Option<BreakpointSessionState>,
8177 cx: &mut Context<Self>,
8178 ) -> IconButton {
8179 let is_rejected = state.is_some_and(|s| !s.verified);
8180 // Is it a breakpoint that shows up when hovering over gutter?
8181 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8182 (false, false),
8183 |PhantomBreakpointIndicator {
8184 is_active,
8185 display_row,
8186 collides_with_existing_breakpoint,
8187 }| {
8188 (
8189 is_active && display_row == row,
8190 collides_with_existing_breakpoint,
8191 )
8192 },
8193 );
8194
8195 let (color, icon) = {
8196 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8197 (false, false) => ui::IconName::DebugBreakpoint,
8198 (true, false) => ui::IconName::DebugLogBreakpoint,
8199 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8200 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8201 };
8202
8203 let color = if is_phantom {
8204 Color::Hint
8205 } else if is_rejected {
8206 Color::Disabled
8207 } else {
8208 Color::Debugger
8209 };
8210
8211 (color, icon)
8212 };
8213
8214 let breakpoint = Arc::from(breakpoint.clone());
8215
8216 let alt_as_text = gpui::Keystroke {
8217 modifiers: Modifiers::secondary_key(),
8218 ..Default::default()
8219 };
8220 let primary_action_text = if breakpoint.is_disabled() {
8221 "Enable breakpoint"
8222 } else if is_phantom && !collides_with_existing {
8223 "Set breakpoint"
8224 } else {
8225 "Unset breakpoint"
8226 };
8227 let focus_handle = self.focus_handle.clone();
8228
8229 let meta = if is_rejected {
8230 SharedString::from("No executable code is associated with this line.")
8231 } else if collides_with_existing && !breakpoint.is_disabled() {
8232 SharedString::from(format!(
8233 "{alt_as_text}-click to disable,\nright-click for more options."
8234 ))
8235 } else {
8236 SharedString::from("Right-click for more options.")
8237 };
8238 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8239 .icon_size(IconSize::XSmall)
8240 .size(ui::ButtonSize::None)
8241 .when(is_rejected, |this| {
8242 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8243 })
8244 .icon_color(color)
8245 .style(ButtonStyle::Transparent)
8246 .on_click(cx.listener({
8247 move |editor, event: &ClickEvent, window, cx| {
8248 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8249 BreakpointEditAction::InvertState
8250 } else {
8251 BreakpointEditAction::Toggle
8252 };
8253
8254 window.focus(&editor.focus_handle(cx));
8255 editor.edit_breakpoint_at_anchor(
8256 position,
8257 breakpoint.as_ref().clone(),
8258 edit_action,
8259 cx,
8260 );
8261 }
8262 }))
8263 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8264 editor.set_breakpoint_context_menu(
8265 row,
8266 Some(position),
8267 event.position(),
8268 window,
8269 cx,
8270 );
8271 }))
8272 .tooltip(move |window, cx| {
8273 Tooltip::with_meta_in(
8274 primary_action_text,
8275 Some(&ToggleBreakpoint),
8276 meta.clone(),
8277 &focus_handle,
8278 window,
8279 cx,
8280 )
8281 })
8282 }
8283
8284 fn build_tasks_context(
8285 project: &Entity<Project>,
8286 buffer: &Entity<Buffer>,
8287 buffer_row: u32,
8288 tasks: &Arc<RunnableTasks>,
8289 cx: &mut Context<Self>,
8290 ) -> Task<Option<task::TaskContext>> {
8291 let position = Point::new(buffer_row, tasks.column);
8292 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8293 let location = Location {
8294 buffer: buffer.clone(),
8295 range: range_start..range_start,
8296 };
8297 // Fill in the environmental variables from the tree-sitter captures
8298 let mut captured_task_variables = TaskVariables::default();
8299 for (capture_name, value) in tasks.extra_variables.clone() {
8300 captured_task_variables.insert(
8301 task::VariableName::Custom(capture_name.into()),
8302 value.clone(),
8303 );
8304 }
8305 project.update(cx, |project, cx| {
8306 project.task_store().update(cx, |task_store, cx| {
8307 task_store.task_context_for_location(captured_task_variables, location, cx)
8308 })
8309 })
8310 }
8311
8312 pub fn spawn_nearest_task(
8313 &mut self,
8314 action: &SpawnNearestTask,
8315 window: &mut Window,
8316 cx: &mut Context<Self>,
8317 ) {
8318 let Some((workspace, _)) = self.workspace.clone() else {
8319 return;
8320 };
8321 let Some(project) = self.project.clone() else {
8322 return;
8323 };
8324
8325 // Try to find a closest, enclosing node using tree-sitter that has a task
8326 let Some((buffer, buffer_row, tasks)) = self
8327 .find_enclosing_node_task(cx)
8328 // Or find the task that's closest in row-distance.
8329 .or_else(|| self.find_closest_task(cx))
8330 else {
8331 return;
8332 };
8333
8334 let reveal_strategy = action.reveal;
8335 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8336 cx.spawn_in(window, async move |_, cx| {
8337 let context = task_context.await?;
8338 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8339
8340 let resolved = &mut resolved_task.resolved;
8341 resolved.reveal = reveal_strategy;
8342
8343 workspace
8344 .update_in(cx, |workspace, window, cx| {
8345 workspace.schedule_resolved_task(
8346 task_source_kind,
8347 resolved_task,
8348 false,
8349 window,
8350 cx,
8351 );
8352 })
8353 .ok()
8354 })
8355 .detach();
8356 }
8357
8358 fn find_closest_task(
8359 &mut self,
8360 cx: &mut Context<Self>,
8361 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8362 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8363
8364 let ((buffer_id, row), tasks) = self
8365 .tasks
8366 .iter()
8367 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8368
8369 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8370 let tasks = Arc::new(tasks.to_owned());
8371 Some((buffer, *row, tasks))
8372 }
8373
8374 fn find_enclosing_node_task(
8375 &mut self,
8376 cx: &mut Context<Self>,
8377 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8378 let snapshot = self.buffer.read(cx).snapshot(cx);
8379 let offset = self.selections.newest::<usize>(cx).head();
8380 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8381 let buffer_id = excerpt.buffer().remote_id();
8382
8383 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8384 let mut cursor = layer.node().walk();
8385
8386 while cursor.goto_first_child_for_byte(offset).is_some() {
8387 if cursor.node().end_byte() == offset {
8388 cursor.goto_next_sibling();
8389 }
8390 }
8391
8392 // Ascend to the smallest ancestor that contains the range and has a task.
8393 loop {
8394 let node = cursor.node();
8395 let node_range = node.byte_range();
8396 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8397
8398 // Check if this node contains our offset
8399 if node_range.start <= offset && node_range.end >= offset {
8400 // If it contains offset, check for task
8401 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8402 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8403 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8404 }
8405 }
8406
8407 if !cursor.goto_parent() {
8408 break;
8409 }
8410 }
8411 None
8412 }
8413
8414 fn render_run_indicator(
8415 &self,
8416 _style: &EditorStyle,
8417 is_active: bool,
8418 row: DisplayRow,
8419 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8420 cx: &mut Context<Self>,
8421 ) -> IconButton {
8422 let color = Color::Muted;
8423 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8424
8425 IconButton::new(
8426 ("run_indicator", row.0 as usize),
8427 ui::IconName::PlayOutlined,
8428 )
8429 .shape(ui::IconButtonShape::Square)
8430 .icon_size(IconSize::XSmall)
8431 .icon_color(color)
8432 .toggle_state(is_active)
8433 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8434 let quick_launch = match e {
8435 ClickEvent::Keyboard(_) => true,
8436 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8437 };
8438
8439 window.focus(&editor.focus_handle(cx));
8440 editor.toggle_code_actions(
8441 &ToggleCodeActions {
8442 deployed_from: Some(CodeActionSource::RunMenu(row)),
8443 quick_launch,
8444 },
8445 window,
8446 cx,
8447 );
8448 }))
8449 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8450 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8451 }))
8452 }
8453
8454 pub fn context_menu_visible(&self) -> bool {
8455 !self.edit_prediction_preview_is_active()
8456 && self
8457 .context_menu
8458 .borrow()
8459 .as_ref()
8460 .is_some_and(|menu| menu.visible())
8461 }
8462
8463 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8464 self.context_menu
8465 .borrow()
8466 .as_ref()
8467 .map(|menu| menu.origin())
8468 }
8469
8470 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8471 self.context_menu_options = Some(options);
8472 }
8473
8474 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8475 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8476
8477 fn render_edit_prediction_popover(
8478 &mut self,
8479 text_bounds: &Bounds<Pixels>,
8480 content_origin: gpui::Point<Pixels>,
8481 right_margin: Pixels,
8482 editor_snapshot: &EditorSnapshot,
8483 visible_row_range: Range<DisplayRow>,
8484 scroll_top: f32,
8485 scroll_bottom: f32,
8486 line_layouts: &[LineWithInvisibles],
8487 line_height: Pixels,
8488 scroll_pixel_position: gpui::Point<Pixels>,
8489 newest_selection_head: Option<DisplayPoint>,
8490 editor_width: Pixels,
8491 style: &EditorStyle,
8492 window: &mut Window,
8493 cx: &mut App,
8494 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8495 if self.mode().is_minimap() {
8496 return None;
8497 }
8498 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8499
8500 if self.edit_prediction_visible_in_cursor_popover(true) {
8501 return None;
8502 }
8503
8504 match &active_edit_prediction.completion {
8505 EditPrediction::Move { target, .. } => {
8506 let target_display_point = target.to_display_point(editor_snapshot);
8507
8508 if self.edit_prediction_requires_modifier() {
8509 if !self.edit_prediction_preview_is_active() {
8510 return None;
8511 }
8512
8513 self.render_edit_prediction_modifier_jump_popover(
8514 text_bounds,
8515 content_origin,
8516 visible_row_range,
8517 line_layouts,
8518 line_height,
8519 scroll_pixel_position,
8520 newest_selection_head,
8521 target_display_point,
8522 window,
8523 cx,
8524 )
8525 } else {
8526 self.render_edit_prediction_eager_jump_popover(
8527 text_bounds,
8528 content_origin,
8529 editor_snapshot,
8530 visible_row_range,
8531 scroll_top,
8532 scroll_bottom,
8533 line_height,
8534 scroll_pixel_position,
8535 target_display_point,
8536 editor_width,
8537 window,
8538 cx,
8539 )
8540 }
8541 }
8542 EditPrediction::Edit {
8543 display_mode: EditDisplayMode::Inline,
8544 ..
8545 } => None,
8546 EditPrediction::Edit {
8547 display_mode: EditDisplayMode::TabAccept,
8548 edits,
8549 ..
8550 } => {
8551 let range = &edits.first()?.0;
8552 let target_display_point = range.end.to_display_point(editor_snapshot);
8553
8554 self.render_edit_prediction_end_of_line_popover(
8555 "Accept",
8556 editor_snapshot,
8557 visible_row_range,
8558 target_display_point,
8559 line_height,
8560 scroll_pixel_position,
8561 content_origin,
8562 editor_width,
8563 window,
8564 cx,
8565 )
8566 }
8567 EditPrediction::Edit {
8568 edits,
8569 edit_preview,
8570 display_mode: EditDisplayMode::DiffPopover,
8571 snapshot,
8572 } => self.render_edit_prediction_diff_popover(
8573 text_bounds,
8574 content_origin,
8575 right_margin,
8576 editor_snapshot,
8577 visible_row_range,
8578 line_layouts,
8579 line_height,
8580 scroll_pixel_position,
8581 newest_selection_head,
8582 editor_width,
8583 style,
8584 edits,
8585 edit_preview,
8586 snapshot,
8587 window,
8588 cx,
8589 ),
8590 }
8591 }
8592
8593 fn render_edit_prediction_modifier_jump_popover(
8594 &mut self,
8595 text_bounds: &Bounds<Pixels>,
8596 content_origin: gpui::Point<Pixels>,
8597 visible_row_range: Range<DisplayRow>,
8598 line_layouts: &[LineWithInvisibles],
8599 line_height: Pixels,
8600 scroll_pixel_position: gpui::Point<Pixels>,
8601 newest_selection_head: Option<DisplayPoint>,
8602 target_display_point: DisplayPoint,
8603 window: &mut Window,
8604 cx: &mut App,
8605 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8606 let scrolled_content_origin =
8607 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8608
8609 const SCROLL_PADDING_Y: Pixels = px(12.);
8610
8611 if target_display_point.row() < visible_row_range.start {
8612 return self.render_edit_prediction_scroll_popover(
8613 |_| SCROLL_PADDING_Y,
8614 IconName::ArrowUp,
8615 visible_row_range,
8616 line_layouts,
8617 newest_selection_head,
8618 scrolled_content_origin,
8619 window,
8620 cx,
8621 );
8622 } else if target_display_point.row() >= visible_row_range.end {
8623 return self.render_edit_prediction_scroll_popover(
8624 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8625 IconName::ArrowDown,
8626 visible_row_range,
8627 line_layouts,
8628 newest_selection_head,
8629 scrolled_content_origin,
8630 window,
8631 cx,
8632 );
8633 }
8634
8635 const POLE_WIDTH: Pixels = px(2.);
8636
8637 let line_layout =
8638 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8639 let target_column = target_display_point.column() as usize;
8640
8641 let target_x = line_layout.x_for_index(target_column);
8642 let target_y =
8643 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8644
8645 let flag_on_right = target_x < text_bounds.size.width / 2.;
8646
8647 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8648 border_color.l += 0.001;
8649
8650 let mut element = v_flex()
8651 .items_end()
8652 .when(flag_on_right, |el| el.items_start())
8653 .child(if flag_on_right {
8654 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8655 .rounded_bl(px(0.))
8656 .rounded_tl(px(0.))
8657 .border_l_2()
8658 .border_color(border_color)
8659 } else {
8660 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8661 .rounded_br(px(0.))
8662 .rounded_tr(px(0.))
8663 .border_r_2()
8664 .border_color(border_color)
8665 })
8666 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8667 .into_any();
8668
8669 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8670
8671 let mut origin = scrolled_content_origin + point(target_x, target_y)
8672 - point(
8673 if flag_on_right {
8674 POLE_WIDTH
8675 } else {
8676 size.width - POLE_WIDTH
8677 },
8678 size.height - line_height,
8679 );
8680
8681 origin.x = origin.x.max(content_origin.x);
8682
8683 element.prepaint_at(origin, window, cx);
8684
8685 Some((element, origin))
8686 }
8687
8688 fn render_edit_prediction_scroll_popover(
8689 &mut self,
8690 to_y: impl Fn(Size<Pixels>) -> Pixels,
8691 scroll_icon: IconName,
8692 visible_row_range: Range<DisplayRow>,
8693 line_layouts: &[LineWithInvisibles],
8694 newest_selection_head: Option<DisplayPoint>,
8695 scrolled_content_origin: gpui::Point<Pixels>,
8696 window: &mut Window,
8697 cx: &mut App,
8698 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8699 let mut element = self
8700 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8701 .into_any();
8702
8703 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8704
8705 let cursor = newest_selection_head?;
8706 let cursor_row_layout =
8707 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8708 let cursor_column = cursor.column() as usize;
8709
8710 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8711
8712 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8713
8714 element.prepaint_at(origin, window, cx);
8715 Some((element, origin))
8716 }
8717
8718 fn render_edit_prediction_eager_jump_popover(
8719 &mut self,
8720 text_bounds: &Bounds<Pixels>,
8721 content_origin: gpui::Point<Pixels>,
8722 editor_snapshot: &EditorSnapshot,
8723 visible_row_range: Range<DisplayRow>,
8724 scroll_top: f32,
8725 scroll_bottom: f32,
8726 line_height: Pixels,
8727 scroll_pixel_position: gpui::Point<Pixels>,
8728 target_display_point: DisplayPoint,
8729 editor_width: Pixels,
8730 window: &mut Window,
8731 cx: &mut App,
8732 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8733 if target_display_point.row().as_f32() < scroll_top {
8734 let mut element = self
8735 .render_edit_prediction_line_popover(
8736 "Jump to Edit",
8737 Some(IconName::ArrowUp),
8738 window,
8739 cx,
8740 )?
8741 .into_any();
8742
8743 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8744 let offset = point(
8745 (text_bounds.size.width - size.width) / 2.,
8746 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8747 );
8748
8749 let origin = text_bounds.origin + offset;
8750 element.prepaint_at(origin, window, cx);
8751 Some((element, origin))
8752 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8753 let mut element = self
8754 .render_edit_prediction_line_popover(
8755 "Jump to Edit",
8756 Some(IconName::ArrowDown),
8757 window,
8758 cx,
8759 )?
8760 .into_any();
8761
8762 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8763 let offset = point(
8764 (text_bounds.size.width - size.width) / 2.,
8765 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8766 );
8767
8768 let origin = text_bounds.origin + offset;
8769 element.prepaint_at(origin, window, cx);
8770 Some((element, origin))
8771 } else {
8772 self.render_edit_prediction_end_of_line_popover(
8773 "Jump to Edit",
8774 editor_snapshot,
8775 visible_row_range,
8776 target_display_point,
8777 line_height,
8778 scroll_pixel_position,
8779 content_origin,
8780 editor_width,
8781 window,
8782 cx,
8783 )
8784 }
8785 }
8786
8787 fn render_edit_prediction_end_of_line_popover(
8788 self: &mut Editor,
8789 label: &'static str,
8790 editor_snapshot: &EditorSnapshot,
8791 visible_row_range: Range<DisplayRow>,
8792 target_display_point: DisplayPoint,
8793 line_height: Pixels,
8794 scroll_pixel_position: gpui::Point<Pixels>,
8795 content_origin: gpui::Point<Pixels>,
8796 editor_width: Pixels,
8797 window: &mut Window,
8798 cx: &mut App,
8799 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8800 let target_line_end = DisplayPoint::new(
8801 target_display_point.row(),
8802 editor_snapshot.line_len(target_display_point.row()),
8803 );
8804
8805 let mut element = self
8806 .render_edit_prediction_line_popover(label, None, window, cx)?
8807 .into_any();
8808
8809 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8810
8811 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8812
8813 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8814 let mut origin = start_point
8815 + line_origin
8816 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8817 origin.x = origin.x.max(content_origin.x);
8818
8819 let max_x = content_origin.x + editor_width - size.width;
8820
8821 if origin.x > max_x {
8822 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8823
8824 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8825 origin.y += offset;
8826 IconName::ArrowUp
8827 } else {
8828 origin.y -= offset;
8829 IconName::ArrowDown
8830 };
8831
8832 element = self
8833 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8834 .into_any();
8835
8836 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8837
8838 origin.x = content_origin.x + editor_width - size.width - px(2.);
8839 }
8840
8841 element.prepaint_at(origin, window, cx);
8842 Some((element, origin))
8843 }
8844
8845 fn render_edit_prediction_diff_popover(
8846 self: &Editor,
8847 text_bounds: &Bounds<Pixels>,
8848 content_origin: gpui::Point<Pixels>,
8849 right_margin: Pixels,
8850 editor_snapshot: &EditorSnapshot,
8851 visible_row_range: Range<DisplayRow>,
8852 line_layouts: &[LineWithInvisibles],
8853 line_height: Pixels,
8854 scroll_pixel_position: gpui::Point<Pixels>,
8855 newest_selection_head: Option<DisplayPoint>,
8856 editor_width: Pixels,
8857 style: &EditorStyle,
8858 edits: &Vec<(Range<Anchor>, String)>,
8859 edit_preview: &Option<language::EditPreview>,
8860 snapshot: &language::BufferSnapshot,
8861 window: &mut Window,
8862 cx: &mut App,
8863 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8864 let edit_start = edits
8865 .first()
8866 .unwrap()
8867 .0
8868 .start
8869 .to_display_point(editor_snapshot);
8870 let edit_end = edits
8871 .last()
8872 .unwrap()
8873 .0
8874 .end
8875 .to_display_point(editor_snapshot);
8876
8877 let is_visible = visible_row_range.contains(&edit_start.row())
8878 || visible_row_range.contains(&edit_end.row());
8879 if !is_visible {
8880 return None;
8881 }
8882
8883 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8884 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8885 } else {
8886 // Fallback for providers without edit_preview
8887 crate::edit_prediction_fallback_text(edits, cx)
8888 };
8889
8890 let styled_text = highlighted_edits.to_styled_text(&style.text);
8891 let line_count = highlighted_edits.text.lines().count();
8892
8893 const BORDER_WIDTH: Pixels = px(1.);
8894
8895 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8896 let has_keybind = keybind.is_some();
8897
8898 let mut element = h_flex()
8899 .items_start()
8900 .child(
8901 h_flex()
8902 .bg(cx.theme().colors().editor_background)
8903 .border(BORDER_WIDTH)
8904 .shadow_xs()
8905 .border_color(cx.theme().colors().border)
8906 .rounded_l_lg()
8907 .when(line_count > 1, |el| el.rounded_br_lg())
8908 .pr_1()
8909 .child(styled_text),
8910 )
8911 .child(
8912 h_flex()
8913 .h(line_height + BORDER_WIDTH * 2.)
8914 .px_1p5()
8915 .gap_1()
8916 // Workaround: For some reason, there's a gap if we don't do this
8917 .ml(-BORDER_WIDTH)
8918 .shadow(vec![gpui::BoxShadow {
8919 color: gpui::black().opacity(0.05),
8920 offset: point(px(1.), px(1.)),
8921 blur_radius: px(2.),
8922 spread_radius: px(0.),
8923 }])
8924 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8925 .border(BORDER_WIDTH)
8926 .border_color(cx.theme().colors().border)
8927 .rounded_r_lg()
8928 .id("edit_prediction_diff_popover_keybind")
8929 .when(!has_keybind, |el| {
8930 let status_colors = cx.theme().status();
8931
8932 el.bg(status_colors.error_background)
8933 .border_color(status_colors.error.opacity(0.6))
8934 .child(Icon::new(IconName::Info).color(Color::Error))
8935 .cursor_default()
8936 .hoverable_tooltip(move |_window, cx| {
8937 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8938 })
8939 })
8940 .children(keybind),
8941 )
8942 .into_any();
8943
8944 let longest_row =
8945 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8946 let longest_line_width = if visible_row_range.contains(&longest_row) {
8947 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8948 } else {
8949 layout_line(
8950 longest_row,
8951 editor_snapshot,
8952 style,
8953 editor_width,
8954 |_| false,
8955 window,
8956 cx,
8957 )
8958 .width
8959 };
8960
8961 let viewport_bounds =
8962 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8963 right: -right_margin,
8964 ..Default::default()
8965 });
8966
8967 let x_after_longest =
8968 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8969 - scroll_pixel_position.x;
8970
8971 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8972
8973 // Fully visible if it can be displayed within the window (allow overlapping other
8974 // panes). However, this is only allowed if the popover starts within text_bounds.
8975 let can_position_to_the_right = x_after_longest < text_bounds.right()
8976 && x_after_longest + element_bounds.width < viewport_bounds.right();
8977
8978 let mut origin = if can_position_to_the_right {
8979 point(
8980 x_after_longest,
8981 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8982 - scroll_pixel_position.y,
8983 )
8984 } else {
8985 let cursor_row = newest_selection_head.map(|head| head.row());
8986 let above_edit = edit_start
8987 .row()
8988 .0
8989 .checked_sub(line_count as u32)
8990 .map(DisplayRow);
8991 let below_edit = Some(edit_end.row() + 1);
8992 let above_cursor =
8993 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8994 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8995
8996 // Place the edit popover adjacent to the edit if there is a location
8997 // available that is onscreen and does not obscure the cursor. Otherwise,
8998 // place it adjacent to the cursor.
8999 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9000 .into_iter()
9001 .flatten()
9002 .find(|&start_row| {
9003 let end_row = start_row + line_count as u32;
9004 visible_row_range.contains(&start_row)
9005 && visible_row_range.contains(&end_row)
9006 && cursor_row
9007 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9008 })?;
9009
9010 content_origin
9011 + point(
9012 -scroll_pixel_position.x,
9013 row_target.as_f32() * line_height - scroll_pixel_position.y,
9014 )
9015 };
9016
9017 origin.x -= BORDER_WIDTH;
9018
9019 window.defer_draw(element, origin, 1);
9020
9021 // Do not return an element, since it will already be drawn due to defer_draw.
9022 None
9023 }
9024
9025 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9026 px(30.)
9027 }
9028
9029 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9030 if self.read_only(cx) {
9031 cx.theme().players().read_only()
9032 } else {
9033 self.style.as_ref().unwrap().local_player
9034 }
9035 }
9036
9037 fn render_edit_prediction_accept_keybind(
9038 &self,
9039 window: &mut Window,
9040 cx: &App,
9041 ) -> Option<AnyElement> {
9042 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9043 let accept_keystroke = accept_binding.keystroke()?;
9044
9045 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9046
9047 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
9048 Color::Accent
9049 } else {
9050 Color::Muted
9051 };
9052
9053 h_flex()
9054 .px_0p5()
9055 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9056 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9057 .text_size(TextSize::XSmall.rems(cx))
9058 .child(h_flex().children(ui::render_modifiers(
9059 &accept_keystroke.modifiers,
9060 PlatformStyle::platform(),
9061 Some(modifiers_color),
9062 Some(IconSize::XSmall.rems().into()),
9063 true,
9064 )))
9065 .when(is_platform_style_mac, |parent| {
9066 parent.child(accept_keystroke.key.clone())
9067 })
9068 .when(!is_platform_style_mac, |parent| {
9069 parent.child(
9070 Key::new(
9071 util::capitalize(&accept_keystroke.key),
9072 Some(Color::Default),
9073 )
9074 .size(Some(IconSize::XSmall.rems().into())),
9075 )
9076 })
9077 .into_any()
9078 .into()
9079 }
9080
9081 fn render_edit_prediction_line_popover(
9082 &self,
9083 label: impl Into<SharedString>,
9084 icon: Option<IconName>,
9085 window: &mut Window,
9086 cx: &App,
9087 ) -> Option<Stateful<Div>> {
9088 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9089
9090 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9091 let has_keybind = keybind.is_some();
9092
9093 let result = h_flex()
9094 .id("ep-line-popover")
9095 .py_0p5()
9096 .pl_1()
9097 .pr(padding_right)
9098 .gap_1()
9099 .rounded_md()
9100 .border_1()
9101 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9102 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9103 .shadow_xs()
9104 .when(!has_keybind, |el| {
9105 let status_colors = cx.theme().status();
9106
9107 el.bg(status_colors.error_background)
9108 .border_color(status_colors.error.opacity(0.6))
9109 .pl_2()
9110 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9111 .cursor_default()
9112 .hoverable_tooltip(move |_window, cx| {
9113 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9114 })
9115 })
9116 .children(keybind)
9117 .child(
9118 Label::new(label)
9119 .size(LabelSize::Small)
9120 .when(!has_keybind, |el| {
9121 el.color(cx.theme().status().error.into()).strikethrough()
9122 }),
9123 )
9124 .when(!has_keybind, |el| {
9125 el.child(
9126 h_flex().ml_1().child(
9127 Icon::new(IconName::Info)
9128 .size(IconSize::Small)
9129 .color(cx.theme().status().error.into()),
9130 ),
9131 )
9132 })
9133 .when_some(icon, |element, icon| {
9134 element.child(
9135 div()
9136 .mt(px(1.5))
9137 .child(Icon::new(icon).size(IconSize::Small)),
9138 )
9139 });
9140
9141 Some(result)
9142 }
9143
9144 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9145 let accent_color = cx.theme().colors().text_accent;
9146 let editor_bg_color = cx.theme().colors().editor_background;
9147 editor_bg_color.blend(accent_color.opacity(0.1))
9148 }
9149
9150 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9151 let accent_color = cx.theme().colors().text_accent;
9152 let editor_bg_color = cx.theme().colors().editor_background;
9153 editor_bg_color.blend(accent_color.opacity(0.6))
9154 }
9155 fn get_prediction_provider_icon_name(
9156 provider: &Option<RegisteredEditPredictionProvider>,
9157 ) -> IconName {
9158 match provider {
9159 Some(provider) => match provider.provider.name() {
9160 "copilot" => IconName::Copilot,
9161 "supermaven" => IconName::Supermaven,
9162 _ => IconName::ZedPredict,
9163 },
9164 None => IconName::ZedPredict,
9165 }
9166 }
9167
9168 fn render_edit_prediction_cursor_popover(
9169 &self,
9170 min_width: Pixels,
9171 max_width: Pixels,
9172 cursor_point: Point,
9173 style: &EditorStyle,
9174 accept_keystroke: Option<&gpui::Keystroke>,
9175 _window: &Window,
9176 cx: &mut Context<Editor>,
9177 ) -> Option<AnyElement> {
9178 let provider = self.edit_prediction_provider.as_ref()?;
9179 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9180
9181 let is_refreshing = provider.provider.is_refreshing(cx);
9182
9183 fn pending_completion_container(icon: IconName) -> Div {
9184 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9185 }
9186
9187 let completion = match &self.active_edit_prediction {
9188 Some(prediction) => {
9189 if !self.has_visible_completions_menu() {
9190 const RADIUS: Pixels = px(6.);
9191 const BORDER_WIDTH: Pixels = px(1.);
9192
9193 return Some(
9194 h_flex()
9195 .elevation_2(cx)
9196 .border(BORDER_WIDTH)
9197 .border_color(cx.theme().colors().border)
9198 .when(accept_keystroke.is_none(), |el| {
9199 el.border_color(cx.theme().status().error)
9200 })
9201 .rounded(RADIUS)
9202 .rounded_tl(px(0.))
9203 .overflow_hidden()
9204 .child(div().px_1p5().child(match &prediction.completion {
9205 EditPrediction::Move { target, snapshot } => {
9206 use text::ToPoint as _;
9207 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9208 {
9209 Icon::new(IconName::ZedPredictDown)
9210 } else {
9211 Icon::new(IconName::ZedPredictUp)
9212 }
9213 }
9214 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9215 }))
9216 .child(
9217 h_flex()
9218 .gap_1()
9219 .py_1()
9220 .px_2()
9221 .rounded_r(RADIUS - BORDER_WIDTH)
9222 .border_l_1()
9223 .border_color(cx.theme().colors().border)
9224 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9225 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9226 el.child(
9227 Label::new("Hold")
9228 .size(LabelSize::Small)
9229 .when(accept_keystroke.is_none(), |el| {
9230 el.strikethrough()
9231 })
9232 .line_height_style(LineHeightStyle::UiLabel),
9233 )
9234 })
9235 .id("edit_prediction_cursor_popover_keybind")
9236 .when(accept_keystroke.is_none(), |el| {
9237 let status_colors = cx.theme().status();
9238
9239 el.bg(status_colors.error_background)
9240 .border_color(status_colors.error.opacity(0.6))
9241 .child(Icon::new(IconName::Info).color(Color::Error))
9242 .cursor_default()
9243 .hoverable_tooltip(move |_window, cx| {
9244 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9245 .into()
9246 })
9247 })
9248 .when_some(
9249 accept_keystroke.as_ref(),
9250 |el, accept_keystroke| {
9251 el.child(h_flex().children(ui::render_modifiers(
9252 &accept_keystroke.modifiers,
9253 PlatformStyle::platform(),
9254 Some(Color::Default),
9255 Some(IconSize::XSmall.rems().into()),
9256 false,
9257 )))
9258 },
9259 ),
9260 )
9261 .into_any(),
9262 );
9263 }
9264
9265 self.render_edit_prediction_cursor_popover_preview(
9266 prediction,
9267 cursor_point,
9268 style,
9269 cx,
9270 )?
9271 }
9272
9273 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9274 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9275 stale_completion,
9276 cursor_point,
9277 style,
9278 cx,
9279 )?,
9280
9281 None => pending_completion_container(provider_icon)
9282 .child(Label::new("...").size(LabelSize::Small)),
9283 },
9284
9285 None => pending_completion_container(provider_icon)
9286 .child(Label::new("...").size(LabelSize::Small)),
9287 };
9288
9289 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9290 completion
9291 .with_animation(
9292 "loading-completion",
9293 Animation::new(Duration::from_secs(2))
9294 .repeat()
9295 .with_easing(pulsating_between(0.4, 0.8)),
9296 |label, delta| label.opacity(delta),
9297 )
9298 .into_any_element()
9299 } else {
9300 completion.into_any_element()
9301 };
9302
9303 let has_completion = self.active_edit_prediction.is_some();
9304
9305 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9306 Some(
9307 h_flex()
9308 .min_w(min_width)
9309 .max_w(max_width)
9310 .flex_1()
9311 .elevation_2(cx)
9312 .border_color(cx.theme().colors().border)
9313 .child(
9314 div()
9315 .flex_1()
9316 .py_1()
9317 .px_2()
9318 .overflow_hidden()
9319 .child(completion),
9320 )
9321 .when_some(accept_keystroke, |el, accept_keystroke| {
9322 if !accept_keystroke.modifiers.modified() {
9323 return el;
9324 }
9325
9326 el.child(
9327 h_flex()
9328 .h_full()
9329 .border_l_1()
9330 .rounded_r_lg()
9331 .border_color(cx.theme().colors().border)
9332 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9333 .gap_1()
9334 .py_1()
9335 .px_2()
9336 .child(
9337 h_flex()
9338 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9339 .when(is_platform_style_mac, |parent| parent.gap_1())
9340 .child(h_flex().children(ui::render_modifiers(
9341 &accept_keystroke.modifiers,
9342 PlatformStyle::platform(),
9343 Some(if !has_completion {
9344 Color::Muted
9345 } else {
9346 Color::Default
9347 }),
9348 None,
9349 false,
9350 ))),
9351 )
9352 .child(Label::new("Preview").into_any_element())
9353 .opacity(if has_completion { 1.0 } else { 0.4 }),
9354 )
9355 })
9356 .into_any(),
9357 )
9358 }
9359
9360 fn render_edit_prediction_cursor_popover_preview(
9361 &self,
9362 completion: &EditPredictionState,
9363 cursor_point: Point,
9364 style: &EditorStyle,
9365 cx: &mut Context<Editor>,
9366 ) -> Option<Div> {
9367 use text::ToPoint as _;
9368
9369 fn render_relative_row_jump(
9370 prefix: impl Into<String>,
9371 current_row: u32,
9372 target_row: u32,
9373 ) -> Div {
9374 let (row_diff, arrow) = if target_row < current_row {
9375 (current_row - target_row, IconName::ArrowUp)
9376 } else {
9377 (target_row - current_row, IconName::ArrowDown)
9378 };
9379
9380 h_flex()
9381 .child(
9382 Label::new(format!("{}{}", prefix.into(), row_diff))
9383 .color(Color::Muted)
9384 .size(LabelSize::Small),
9385 )
9386 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9387 }
9388
9389 let supports_jump = self
9390 .edit_prediction_provider
9391 .as_ref()
9392 .map(|provider| provider.provider.supports_jump_to_edit())
9393 .unwrap_or(true);
9394
9395 match &completion.completion {
9396 EditPrediction::Move {
9397 target, snapshot, ..
9398 } => {
9399 if !supports_jump {
9400 return None;
9401 }
9402
9403 Some(
9404 h_flex()
9405 .px_2()
9406 .gap_2()
9407 .flex_1()
9408 .child(
9409 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9410 Icon::new(IconName::ZedPredictDown)
9411 } else {
9412 Icon::new(IconName::ZedPredictUp)
9413 },
9414 )
9415 .child(Label::new("Jump to Edit")),
9416 )
9417 }
9418
9419 EditPrediction::Edit {
9420 edits,
9421 edit_preview,
9422 snapshot,
9423 display_mode: _,
9424 } => {
9425 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9426
9427 let (highlighted_edits, has_more_lines) =
9428 if let Some(edit_preview) = edit_preview.as_ref() {
9429 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9430 .first_line_preview()
9431 } else {
9432 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9433 };
9434
9435 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9436 .with_default_highlights(&style.text, highlighted_edits.highlights);
9437
9438 let preview = h_flex()
9439 .gap_1()
9440 .min_w_16()
9441 .child(styled_text)
9442 .when(has_more_lines, |parent| parent.child("…"));
9443
9444 let left = if supports_jump && first_edit_row != cursor_point.row {
9445 render_relative_row_jump("", cursor_point.row, first_edit_row)
9446 .into_any_element()
9447 } else {
9448 let icon_name =
9449 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9450 Icon::new(icon_name).into_any_element()
9451 };
9452
9453 Some(
9454 h_flex()
9455 .h_full()
9456 .flex_1()
9457 .gap_2()
9458 .pr_1()
9459 .overflow_x_hidden()
9460 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9461 .child(left)
9462 .child(preview),
9463 )
9464 }
9465 }
9466 }
9467
9468 pub fn render_context_menu(
9469 &self,
9470 style: &EditorStyle,
9471 max_height_in_lines: u32,
9472 window: &mut Window,
9473 cx: &mut Context<Editor>,
9474 ) -> Option<AnyElement> {
9475 let menu = self.context_menu.borrow();
9476 let menu = menu.as_ref()?;
9477 if !menu.visible() {
9478 return None;
9479 };
9480 Some(menu.render(style, max_height_in_lines, window, cx))
9481 }
9482
9483 fn render_context_menu_aside(
9484 &mut self,
9485 max_size: Size<Pixels>,
9486 window: &mut Window,
9487 cx: &mut Context<Editor>,
9488 ) -> Option<AnyElement> {
9489 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9490 if menu.visible() {
9491 menu.render_aside(max_size, window, cx)
9492 } else {
9493 None
9494 }
9495 })
9496 }
9497
9498 fn hide_context_menu(
9499 &mut self,
9500 window: &mut Window,
9501 cx: &mut Context<Self>,
9502 ) -> Option<CodeContextMenu> {
9503 cx.notify();
9504 self.completion_tasks.clear();
9505 let context_menu = self.context_menu.borrow_mut().take();
9506 self.stale_edit_prediction_in_menu.take();
9507 self.update_visible_edit_prediction(window, cx);
9508 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9509 && let Some(completion_provider) = &self.completion_provider
9510 {
9511 completion_provider.selection_changed(None, window, cx);
9512 }
9513 context_menu
9514 }
9515
9516 fn show_snippet_choices(
9517 &mut self,
9518 choices: &Vec<String>,
9519 selection: Range<Anchor>,
9520 cx: &mut Context<Self>,
9521 ) {
9522 let Some((_, buffer, _)) = self
9523 .buffer()
9524 .read(cx)
9525 .excerpt_containing(selection.start, cx)
9526 else {
9527 return;
9528 };
9529 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9530 else {
9531 return;
9532 };
9533 if buffer != end_buffer {
9534 log::error!("expected anchor range to have matching buffer IDs");
9535 return;
9536 }
9537
9538 let id = post_inc(&mut self.next_completion_id);
9539 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9540 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9541 CompletionsMenu::new_snippet_choices(
9542 id,
9543 true,
9544 choices,
9545 selection,
9546 buffer,
9547 snippet_sort_order,
9548 ),
9549 ));
9550 }
9551
9552 pub fn insert_snippet(
9553 &mut self,
9554 insertion_ranges: &[Range<usize>],
9555 snippet: Snippet,
9556 window: &mut Window,
9557 cx: &mut Context<Self>,
9558 ) -> Result<()> {
9559 struct Tabstop<T> {
9560 is_end_tabstop: bool,
9561 ranges: Vec<Range<T>>,
9562 choices: Option<Vec<String>>,
9563 }
9564
9565 let tabstops = self.buffer.update(cx, |buffer, cx| {
9566 let snippet_text: Arc<str> = snippet.text.clone().into();
9567 let edits = insertion_ranges
9568 .iter()
9569 .cloned()
9570 .map(|range| (range, snippet_text.clone()));
9571 let autoindent_mode = AutoindentMode::Block {
9572 original_indent_columns: Vec::new(),
9573 };
9574 buffer.edit(edits, Some(autoindent_mode), cx);
9575
9576 let snapshot = &*buffer.read(cx);
9577 let snippet = &snippet;
9578 snippet
9579 .tabstops
9580 .iter()
9581 .map(|tabstop| {
9582 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9583 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9584 });
9585 let mut tabstop_ranges = tabstop
9586 .ranges
9587 .iter()
9588 .flat_map(|tabstop_range| {
9589 let mut delta = 0_isize;
9590 insertion_ranges.iter().map(move |insertion_range| {
9591 let insertion_start = insertion_range.start as isize + delta;
9592 delta +=
9593 snippet.text.len() as isize - insertion_range.len() as isize;
9594
9595 let start = ((insertion_start + tabstop_range.start) as usize)
9596 .min(snapshot.len());
9597 let end = ((insertion_start + tabstop_range.end) as usize)
9598 .min(snapshot.len());
9599 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9600 })
9601 })
9602 .collect::<Vec<_>>();
9603 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9604
9605 Tabstop {
9606 is_end_tabstop,
9607 ranges: tabstop_ranges,
9608 choices: tabstop.choices.clone(),
9609 }
9610 })
9611 .collect::<Vec<_>>()
9612 });
9613 if let Some(tabstop) = tabstops.first() {
9614 self.change_selections(Default::default(), window, cx, |s| {
9615 // Reverse order so that the first range is the newest created selection.
9616 // Completions will use it and autoscroll will prioritize it.
9617 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9618 });
9619
9620 if let Some(choices) = &tabstop.choices
9621 && let Some(selection) = tabstop.ranges.first()
9622 {
9623 self.show_snippet_choices(choices, selection.clone(), cx)
9624 }
9625
9626 // If we're already at the last tabstop and it's at the end of the snippet,
9627 // we're done, we don't need to keep the state around.
9628 if !tabstop.is_end_tabstop {
9629 let choices = tabstops
9630 .iter()
9631 .map(|tabstop| tabstop.choices.clone())
9632 .collect();
9633
9634 let ranges = tabstops
9635 .into_iter()
9636 .map(|tabstop| tabstop.ranges)
9637 .collect::<Vec<_>>();
9638
9639 self.snippet_stack.push(SnippetState {
9640 active_index: 0,
9641 ranges,
9642 choices,
9643 });
9644 }
9645
9646 // Check whether the just-entered snippet ends with an auto-closable bracket.
9647 if self.autoclose_regions.is_empty() {
9648 let snapshot = self.buffer.read(cx).snapshot(cx);
9649 let mut all_selections = self.selections.all::<Point>(cx);
9650 for selection in &mut all_selections {
9651 let selection_head = selection.head();
9652 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9653 continue;
9654 };
9655
9656 let mut bracket_pair = None;
9657 let max_lookup_length = scope
9658 .brackets()
9659 .map(|(pair, _)| {
9660 pair.start
9661 .as_str()
9662 .chars()
9663 .count()
9664 .max(pair.end.as_str().chars().count())
9665 })
9666 .max();
9667 if let Some(max_lookup_length) = max_lookup_length {
9668 let next_text = snapshot
9669 .chars_at(selection_head)
9670 .take(max_lookup_length)
9671 .collect::<String>();
9672 let prev_text = snapshot
9673 .reversed_chars_at(selection_head)
9674 .take(max_lookup_length)
9675 .collect::<String>();
9676
9677 for (pair, enabled) in scope.brackets() {
9678 if enabled
9679 && pair.close
9680 && prev_text.starts_with(pair.start.as_str())
9681 && next_text.starts_with(pair.end.as_str())
9682 {
9683 bracket_pair = Some(pair.clone());
9684 break;
9685 }
9686 }
9687 }
9688
9689 if let Some(pair) = bracket_pair {
9690 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9691 let autoclose_enabled =
9692 self.use_autoclose && snapshot_settings.use_autoclose;
9693 if autoclose_enabled {
9694 let start = snapshot.anchor_after(selection_head);
9695 let end = snapshot.anchor_after(selection_head);
9696 self.autoclose_regions.push(AutocloseRegion {
9697 selection_id: selection.id,
9698 range: start..end,
9699 pair,
9700 });
9701 }
9702 }
9703 }
9704 }
9705 }
9706 Ok(())
9707 }
9708
9709 pub fn move_to_next_snippet_tabstop(
9710 &mut self,
9711 window: &mut Window,
9712 cx: &mut Context<Self>,
9713 ) -> bool {
9714 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9715 }
9716
9717 pub fn move_to_prev_snippet_tabstop(
9718 &mut self,
9719 window: &mut Window,
9720 cx: &mut Context<Self>,
9721 ) -> bool {
9722 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9723 }
9724
9725 pub fn move_to_snippet_tabstop(
9726 &mut self,
9727 bias: Bias,
9728 window: &mut Window,
9729 cx: &mut Context<Self>,
9730 ) -> bool {
9731 if let Some(mut snippet) = self.snippet_stack.pop() {
9732 match bias {
9733 Bias::Left => {
9734 if snippet.active_index > 0 {
9735 snippet.active_index -= 1;
9736 } else {
9737 self.snippet_stack.push(snippet);
9738 return false;
9739 }
9740 }
9741 Bias::Right => {
9742 if snippet.active_index + 1 < snippet.ranges.len() {
9743 snippet.active_index += 1;
9744 } else {
9745 self.snippet_stack.push(snippet);
9746 return false;
9747 }
9748 }
9749 }
9750 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9751 self.change_selections(Default::default(), window, cx, |s| {
9752 // Reverse order so that the first range is the newest created selection.
9753 // Completions will use it and autoscroll will prioritize it.
9754 s.select_ranges(current_ranges.iter().rev().cloned())
9755 });
9756
9757 if let Some(choices) = &snippet.choices[snippet.active_index]
9758 && let Some(selection) = current_ranges.first()
9759 {
9760 self.show_snippet_choices(choices, selection.clone(), cx);
9761 }
9762
9763 // If snippet state is not at the last tabstop, push it back on the stack
9764 if snippet.active_index + 1 < snippet.ranges.len() {
9765 self.snippet_stack.push(snippet);
9766 }
9767 return true;
9768 }
9769 }
9770
9771 false
9772 }
9773
9774 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9775 self.transact(window, cx, |this, window, cx| {
9776 this.select_all(&SelectAll, window, cx);
9777 this.insert("", window, cx);
9778 });
9779 }
9780
9781 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9782 if self.read_only(cx) {
9783 return;
9784 }
9785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9786 self.transact(window, cx, |this, window, cx| {
9787 this.select_autoclose_pair(window, cx);
9788 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9789 if !this.linked_edit_ranges.is_empty() {
9790 let selections = this.selections.all::<MultiBufferPoint>(cx);
9791 let snapshot = this.buffer.read(cx).snapshot(cx);
9792
9793 for selection in selections.iter() {
9794 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9795 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9796 if selection_start.buffer_id != selection_end.buffer_id {
9797 continue;
9798 }
9799 if let Some(ranges) =
9800 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9801 {
9802 for (buffer, entries) in ranges {
9803 linked_ranges.entry(buffer).or_default().extend(entries);
9804 }
9805 }
9806 }
9807 }
9808
9809 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9810 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9811 for selection in &mut selections {
9812 if selection.is_empty() {
9813 let old_head = selection.head();
9814 let mut new_head =
9815 movement::left(&display_map, old_head.to_display_point(&display_map))
9816 .to_point(&display_map);
9817 if let Some((buffer, line_buffer_range)) = display_map
9818 .buffer_snapshot
9819 .buffer_line_for_row(MultiBufferRow(old_head.row))
9820 {
9821 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9822 let indent_len = match indent_size.kind {
9823 IndentKind::Space => {
9824 buffer.settings_at(line_buffer_range.start, cx).tab_size
9825 }
9826 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9827 };
9828 if old_head.column <= indent_size.len && old_head.column > 0 {
9829 let indent_len = indent_len.get();
9830 new_head = cmp::min(
9831 new_head,
9832 MultiBufferPoint::new(
9833 old_head.row,
9834 ((old_head.column - 1) / indent_len) * indent_len,
9835 ),
9836 );
9837 }
9838 }
9839
9840 selection.set_head(new_head, SelectionGoal::None);
9841 }
9842 }
9843
9844 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9845 this.insert("", window, cx);
9846 let empty_str: Arc<str> = Arc::from("");
9847 for (buffer, edits) in linked_ranges {
9848 let snapshot = buffer.read(cx).snapshot();
9849 use text::ToPoint as TP;
9850
9851 let edits = edits
9852 .into_iter()
9853 .map(|range| {
9854 let end_point = TP::to_point(&range.end, &snapshot);
9855 let mut start_point = TP::to_point(&range.start, &snapshot);
9856
9857 if end_point == start_point {
9858 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9859 .saturating_sub(1);
9860 start_point =
9861 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9862 };
9863
9864 (start_point..end_point, empty_str.clone())
9865 })
9866 .sorted_by_key(|(range, _)| range.start)
9867 .collect::<Vec<_>>();
9868 buffer.update(cx, |this, cx| {
9869 this.edit(edits, None, cx);
9870 })
9871 }
9872 this.refresh_edit_prediction(true, false, window, cx);
9873 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9874 });
9875 }
9876
9877 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9878 if self.read_only(cx) {
9879 return;
9880 }
9881 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9882 self.transact(window, cx, |this, window, cx| {
9883 this.change_selections(Default::default(), window, cx, |s| {
9884 s.move_with(|map, selection| {
9885 if selection.is_empty() {
9886 let cursor = movement::right(map, selection.head());
9887 selection.end = cursor;
9888 selection.reversed = true;
9889 selection.goal = SelectionGoal::None;
9890 }
9891 })
9892 });
9893 this.insert("", window, cx);
9894 this.refresh_edit_prediction(true, false, window, cx);
9895 });
9896 }
9897
9898 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9899 if self.mode.is_single_line() {
9900 cx.propagate();
9901 return;
9902 }
9903
9904 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9905 if self.move_to_prev_snippet_tabstop(window, cx) {
9906 return;
9907 }
9908 self.outdent(&Outdent, window, cx);
9909 }
9910
9911 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9912 if self.mode.is_single_line() {
9913 cx.propagate();
9914 return;
9915 }
9916
9917 if self.move_to_next_snippet_tabstop(window, cx) {
9918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9919 return;
9920 }
9921 if self.read_only(cx) {
9922 return;
9923 }
9924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9925 let mut selections = self.selections.all_adjusted(cx);
9926 let buffer = self.buffer.read(cx);
9927 let snapshot = buffer.snapshot(cx);
9928 let rows_iter = selections.iter().map(|s| s.head().row);
9929 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9930
9931 let has_some_cursor_in_whitespace = selections
9932 .iter()
9933 .filter(|selection| selection.is_empty())
9934 .any(|selection| {
9935 let cursor = selection.head();
9936 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9937 cursor.column < current_indent.len
9938 });
9939
9940 let mut edits = Vec::new();
9941 let mut prev_edited_row = 0;
9942 let mut row_delta = 0;
9943 for selection in &mut selections {
9944 if selection.start.row != prev_edited_row {
9945 row_delta = 0;
9946 }
9947 prev_edited_row = selection.end.row;
9948
9949 // If the selection is non-empty, then increase the indentation of the selected lines.
9950 if !selection.is_empty() {
9951 row_delta =
9952 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9953 continue;
9954 }
9955
9956 let cursor = selection.head();
9957 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9958 if let Some(suggested_indent) =
9959 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9960 {
9961 // Don't do anything if already at suggested indent
9962 // and there is any other cursor which is not
9963 if has_some_cursor_in_whitespace
9964 && cursor.column == current_indent.len
9965 && current_indent.len == suggested_indent.len
9966 {
9967 continue;
9968 }
9969
9970 // Adjust line and move cursor to suggested indent
9971 // if cursor is not at suggested indent
9972 if cursor.column < suggested_indent.len
9973 && cursor.column <= current_indent.len
9974 && current_indent.len <= suggested_indent.len
9975 {
9976 selection.start = Point::new(cursor.row, suggested_indent.len);
9977 selection.end = selection.start;
9978 if row_delta == 0 {
9979 edits.extend(Buffer::edit_for_indent_size_adjustment(
9980 cursor.row,
9981 current_indent,
9982 suggested_indent,
9983 ));
9984 row_delta = suggested_indent.len - current_indent.len;
9985 }
9986 continue;
9987 }
9988
9989 // If current indent is more than suggested indent
9990 // only move cursor to current indent and skip indent
9991 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9992 selection.start = Point::new(cursor.row, current_indent.len);
9993 selection.end = selection.start;
9994 continue;
9995 }
9996 }
9997
9998 // Otherwise, insert a hard or soft tab.
9999 let settings = buffer.language_settings_at(cursor, cx);
10000 let tab_size = if settings.hard_tabs {
10001 IndentSize::tab()
10002 } else {
10003 let tab_size = settings.tab_size.get();
10004 let indent_remainder = snapshot
10005 .text_for_range(Point::new(cursor.row, 0)..cursor)
10006 .flat_map(str::chars)
10007 .fold(row_delta % tab_size, |counter: u32, c| {
10008 if c == '\t' {
10009 0
10010 } else {
10011 (counter + 1) % tab_size
10012 }
10013 });
10014
10015 let chars_to_next_tab_stop = tab_size - indent_remainder;
10016 IndentSize::spaces(chars_to_next_tab_stop)
10017 };
10018 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10019 selection.end = selection.start;
10020 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10021 row_delta += tab_size.len;
10022 }
10023
10024 self.transact(window, cx, |this, window, cx| {
10025 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10026 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10027 this.refresh_edit_prediction(true, false, window, cx);
10028 });
10029 }
10030
10031 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10032 if self.read_only(cx) {
10033 return;
10034 }
10035 if self.mode.is_single_line() {
10036 cx.propagate();
10037 return;
10038 }
10039
10040 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10041 let mut selections = self.selections.all::<Point>(cx);
10042 let mut prev_edited_row = 0;
10043 let mut row_delta = 0;
10044 let mut edits = Vec::new();
10045 let buffer = self.buffer.read(cx);
10046 let snapshot = buffer.snapshot(cx);
10047 for selection in &mut selections {
10048 if selection.start.row != prev_edited_row {
10049 row_delta = 0;
10050 }
10051 prev_edited_row = selection.end.row;
10052
10053 row_delta =
10054 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10055 }
10056
10057 self.transact(window, cx, |this, window, cx| {
10058 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10059 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10060 });
10061 }
10062
10063 fn indent_selection(
10064 buffer: &MultiBuffer,
10065 snapshot: &MultiBufferSnapshot,
10066 selection: &mut Selection<Point>,
10067 edits: &mut Vec<(Range<Point>, String)>,
10068 delta_for_start_row: u32,
10069 cx: &App,
10070 ) -> u32 {
10071 let settings = buffer.language_settings_at(selection.start, cx);
10072 let tab_size = settings.tab_size.get();
10073 let indent_kind = if settings.hard_tabs {
10074 IndentKind::Tab
10075 } else {
10076 IndentKind::Space
10077 };
10078 let mut start_row = selection.start.row;
10079 let mut end_row = selection.end.row + 1;
10080
10081 // If a selection ends at the beginning of a line, don't indent
10082 // that last line.
10083 if selection.end.column == 0 && selection.end.row > selection.start.row {
10084 end_row -= 1;
10085 }
10086
10087 // Avoid re-indenting a row that has already been indented by a
10088 // previous selection, but still update this selection's column
10089 // to reflect that indentation.
10090 if delta_for_start_row > 0 {
10091 start_row += 1;
10092 selection.start.column += delta_for_start_row;
10093 if selection.end.row == selection.start.row {
10094 selection.end.column += delta_for_start_row;
10095 }
10096 }
10097
10098 let mut delta_for_end_row = 0;
10099 let has_multiple_rows = start_row + 1 != end_row;
10100 for row in start_row..end_row {
10101 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10102 let indent_delta = match (current_indent.kind, indent_kind) {
10103 (IndentKind::Space, IndentKind::Space) => {
10104 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10105 IndentSize::spaces(columns_to_next_tab_stop)
10106 }
10107 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10108 (_, IndentKind::Tab) => IndentSize::tab(),
10109 };
10110
10111 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10112 0
10113 } else {
10114 selection.start.column
10115 };
10116 let row_start = Point::new(row, start);
10117 edits.push((
10118 row_start..row_start,
10119 indent_delta.chars().collect::<String>(),
10120 ));
10121
10122 // Update this selection's endpoints to reflect the indentation.
10123 if row == selection.start.row {
10124 selection.start.column += indent_delta.len;
10125 }
10126 if row == selection.end.row {
10127 selection.end.column += indent_delta.len;
10128 delta_for_end_row = indent_delta.len;
10129 }
10130 }
10131
10132 if selection.start.row == selection.end.row {
10133 delta_for_start_row + delta_for_end_row
10134 } else {
10135 delta_for_end_row
10136 }
10137 }
10138
10139 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10140 if self.read_only(cx) {
10141 return;
10142 }
10143 if self.mode.is_single_line() {
10144 cx.propagate();
10145 return;
10146 }
10147
10148 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10149 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10150 let selections = self.selections.all::<Point>(cx);
10151 let mut deletion_ranges = Vec::new();
10152 let mut last_outdent = None;
10153 {
10154 let buffer = self.buffer.read(cx);
10155 let snapshot = buffer.snapshot(cx);
10156 for selection in &selections {
10157 let settings = buffer.language_settings_at(selection.start, cx);
10158 let tab_size = settings.tab_size.get();
10159 let mut rows = selection.spanned_rows(false, &display_map);
10160
10161 // Avoid re-outdenting a row that has already been outdented by a
10162 // previous selection.
10163 if let Some(last_row) = last_outdent
10164 && last_row == rows.start
10165 {
10166 rows.start = rows.start.next_row();
10167 }
10168 let has_multiple_rows = rows.len() > 1;
10169 for row in rows.iter_rows() {
10170 let indent_size = snapshot.indent_size_for_line(row);
10171 if indent_size.len > 0 {
10172 let deletion_len = match indent_size.kind {
10173 IndentKind::Space => {
10174 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10175 if columns_to_prev_tab_stop == 0 {
10176 tab_size
10177 } else {
10178 columns_to_prev_tab_stop
10179 }
10180 }
10181 IndentKind::Tab => 1,
10182 };
10183 let start = if has_multiple_rows
10184 || deletion_len > selection.start.column
10185 || indent_size.len < selection.start.column
10186 {
10187 0
10188 } else {
10189 selection.start.column - deletion_len
10190 };
10191 deletion_ranges.push(
10192 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10193 );
10194 last_outdent = Some(row);
10195 }
10196 }
10197 }
10198 }
10199
10200 self.transact(window, cx, |this, window, cx| {
10201 this.buffer.update(cx, |buffer, cx| {
10202 let empty_str: Arc<str> = Arc::default();
10203 buffer.edit(
10204 deletion_ranges
10205 .into_iter()
10206 .map(|range| (range, empty_str.clone())),
10207 None,
10208 cx,
10209 );
10210 });
10211 let selections = this.selections.all::<usize>(cx);
10212 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10213 });
10214 }
10215
10216 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10217 if self.read_only(cx) {
10218 return;
10219 }
10220 if self.mode.is_single_line() {
10221 cx.propagate();
10222 return;
10223 }
10224
10225 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10226 let selections = self
10227 .selections
10228 .all::<usize>(cx)
10229 .into_iter()
10230 .map(|s| s.range());
10231
10232 self.transact(window, cx, |this, window, cx| {
10233 this.buffer.update(cx, |buffer, cx| {
10234 buffer.autoindent_ranges(selections, cx);
10235 });
10236 let selections = this.selections.all::<usize>(cx);
10237 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10238 });
10239 }
10240
10241 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10242 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10243 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10244 let selections = self.selections.all::<Point>(cx);
10245
10246 let mut new_cursors = Vec::new();
10247 let mut edit_ranges = Vec::new();
10248 let mut selections = selections.iter().peekable();
10249 while let Some(selection) = selections.next() {
10250 let mut rows = selection.spanned_rows(false, &display_map);
10251 let goal_display_column = selection.head().to_display_point(&display_map).column();
10252
10253 // Accumulate contiguous regions of rows that we want to delete.
10254 while let Some(next_selection) = selections.peek() {
10255 let next_rows = next_selection.spanned_rows(false, &display_map);
10256 if next_rows.start <= rows.end {
10257 rows.end = next_rows.end;
10258 selections.next().unwrap();
10259 } else {
10260 break;
10261 }
10262 }
10263
10264 let buffer = &display_map.buffer_snapshot;
10265 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10266 let edit_end;
10267 let cursor_buffer_row;
10268 if buffer.max_point().row >= rows.end.0 {
10269 // If there's a line after the range, delete the \n from the end of the row range
10270 // and position the cursor on the next line.
10271 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10272 cursor_buffer_row = rows.end;
10273 } else {
10274 // If there isn't a line after the range, delete the \n from the line before the
10275 // start of the row range and position the cursor there.
10276 edit_start = edit_start.saturating_sub(1);
10277 edit_end = buffer.len();
10278 cursor_buffer_row = rows.start.previous_row();
10279 }
10280
10281 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10282 *cursor.column_mut() =
10283 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10284
10285 new_cursors.push((
10286 selection.id,
10287 buffer.anchor_after(cursor.to_point(&display_map)),
10288 ));
10289 edit_ranges.push(edit_start..edit_end);
10290 }
10291
10292 self.transact(window, cx, |this, window, cx| {
10293 let buffer = this.buffer.update(cx, |buffer, cx| {
10294 let empty_str: Arc<str> = Arc::default();
10295 buffer.edit(
10296 edit_ranges
10297 .into_iter()
10298 .map(|range| (range, empty_str.clone())),
10299 None,
10300 cx,
10301 );
10302 buffer.snapshot(cx)
10303 });
10304 let new_selections = new_cursors
10305 .into_iter()
10306 .map(|(id, cursor)| {
10307 let cursor = cursor.to_point(&buffer);
10308 Selection {
10309 id,
10310 start: cursor,
10311 end: cursor,
10312 reversed: false,
10313 goal: SelectionGoal::None,
10314 }
10315 })
10316 .collect();
10317
10318 this.change_selections(Default::default(), window, cx, |s| {
10319 s.select(new_selections);
10320 });
10321 });
10322 }
10323
10324 pub fn join_lines_impl(
10325 &mut self,
10326 insert_whitespace: bool,
10327 window: &mut Window,
10328 cx: &mut Context<Self>,
10329 ) {
10330 if self.read_only(cx) {
10331 return;
10332 }
10333 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10334 for selection in self.selections.all::<Point>(cx) {
10335 let start = MultiBufferRow(selection.start.row);
10336 // Treat single line selections as if they include the next line. Otherwise this action
10337 // would do nothing for single line selections individual cursors.
10338 let end = if selection.start.row == selection.end.row {
10339 MultiBufferRow(selection.start.row + 1)
10340 } else {
10341 MultiBufferRow(selection.end.row)
10342 };
10343
10344 if let Some(last_row_range) = row_ranges.last_mut()
10345 && start <= last_row_range.end
10346 {
10347 last_row_range.end = end;
10348 continue;
10349 }
10350 row_ranges.push(start..end);
10351 }
10352
10353 let snapshot = self.buffer.read(cx).snapshot(cx);
10354 let mut cursor_positions = Vec::new();
10355 for row_range in &row_ranges {
10356 let anchor = snapshot.anchor_before(Point::new(
10357 row_range.end.previous_row().0,
10358 snapshot.line_len(row_range.end.previous_row()),
10359 ));
10360 cursor_positions.push(anchor..anchor);
10361 }
10362
10363 self.transact(window, cx, |this, window, cx| {
10364 for row_range in row_ranges.into_iter().rev() {
10365 for row in row_range.iter_rows().rev() {
10366 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10367 let next_line_row = row.next_row();
10368 let indent = snapshot.indent_size_for_line(next_line_row);
10369 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10370
10371 let replace =
10372 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10373 " "
10374 } else {
10375 ""
10376 };
10377
10378 this.buffer.update(cx, |buffer, cx| {
10379 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10380 });
10381 }
10382 }
10383
10384 this.change_selections(Default::default(), window, cx, |s| {
10385 s.select_anchor_ranges(cursor_positions)
10386 });
10387 });
10388 }
10389
10390 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10392 self.join_lines_impl(true, window, cx);
10393 }
10394
10395 pub fn sort_lines_case_sensitive(
10396 &mut self,
10397 _: &SortLinesCaseSensitive,
10398 window: &mut Window,
10399 cx: &mut Context<Self>,
10400 ) {
10401 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10402 }
10403
10404 pub fn sort_lines_by_length(
10405 &mut self,
10406 _: &SortLinesByLength,
10407 window: &mut Window,
10408 cx: &mut Context<Self>,
10409 ) {
10410 self.manipulate_immutable_lines(window, cx, |lines| {
10411 lines.sort_by_key(|&line| line.chars().count())
10412 })
10413 }
10414
10415 pub fn sort_lines_case_insensitive(
10416 &mut self,
10417 _: &SortLinesCaseInsensitive,
10418 window: &mut Window,
10419 cx: &mut Context<Self>,
10420 ) {
10421 self.manipulate_immutable_lines(window, cx, |lines| {
10422 lines.sort_by_key(|line| line.to_lowercase())
10423 })
10424 }
10425
10426 pub fn unique_lines_case_insensitive(
10427 &mut self,
10428 _: &UniqueLinesCaseInsensitive,
10429 window: &mut Window,
10430 cx: &mut Context<Self>,
10431 ) {
10432 self.manipulate_immutable_lines(window, cx, |lines| {
10433 let mut seen = HashSet::default();
10434 lines.retain(|line| seen.insert(line.to_lowercase()));
10435 })
10436 }
10437
10438 pub fn unique_lines_case_sensitive(
10439 &mut self,
10440 _: &UniqueLinesCaseSensitive,
10441 window: &mut Window,
10442 cx: &mut Context<Self>,
10443 ) {
10444 self.manipulate_immutable_lines(window, cx, |lines| {
10445 let mut seen = HashSet::default();
10446 lines.retain(|line| seen.insert(*line));
10447 })
10448 }
10449
10450 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10451 let Some(project) = self.project.clone() else {
10452 return;
10453 };
10454 self.reload(project, window, cx)
10455 .detach_and_notify_err(window, cx);
10456 }
10457
10458 pub fn restore_file(
10459 &mut self,
10460 _: &::git::RestoreFile,
10461 window: &mut Window,
10462 cx: &mut Context<Self>,
10463 ) {
10464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10465 let mut buffer_ids = HashSet::default();
10466 let snapshot = self.buffer().read(cx).snapshot(cx);
10467 for selection in self.selections.all::<usize>(cx) {
10468 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10469 }
10470
10471 let buffer = self.buffer().read(cx);
10472 let ranges = buffer_ids
10473 .into_iter()
10474 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10475 .collect::<Vec<_>>();
10476
10477 self.restore_hunks_in_ranges(ranges, window, cx);
10478 }
10479
10480 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10482 let selections = self
10483 .selections
10484 .all(cx)
10485 .into_iter()
10486 .map(|s| s.range())
10487 .collect();
10488 self.restore_hunks_in_ranges(selections, window, cx);
10489 }
10490
10491 pub fn restore_hunks_in_ranges(
10492 &mut self,
10493 ranges: Vec<Range<Point>>,
10494 window: &mut Window,
10495 cx: &mut Context<Editor>,
10496 ) {
10497 let mut revert_changes = HashMap::default();
10498 let chunk_by = self
10499 .snapshot(window, cx)
10500 .hunks_for_ranges(ranges)
10501 .into_iter()
10502 .chunk_by(|hunk| hunk.buffer_id);
10503 for (buffer_id, hunks) in &chunk_by {
10504 let hunks = hunks.collect::<Vec<_>>();
10505 for hunk in &hunks {
10506 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10507 }
10508 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10509 }
10510 drop(chunk_by);
10511 if !revert_changes.is_empty() {
10512 self.transact(window, cx, |editor, window, cx| {
10513 editor.restore(revert_changes, window, cx);
10514 });
10515 }
10516 }
10517
10518 pub fn open_active_item_in_terminal(
10519 &mut self,
10520 _: &OpenInTerminal,
10521 window: &mut Window,
10522 cx: &mut Context<Self>,
10523 ) {
10524 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10525 let project_path = buffer.read(cx).project_path(cx)?;
10526 let project = self.project()?.read(cx);
10527 let entry = project.entry_for_path(&project_path, cx)?;
10528 let parent = match &entry.canonical_path {
10529 Some(canonical_path) => canonical_path.to_path_buf(),
10530 None => project.absolute_path(&project_path, cx)?,
10531 }
10532 .parent()?
10533 .to_path_buf();
10534 Some(parent)
10535 }) {
10536 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10537 }
10538 }
10539
10540 fn set_breakpoint_context_menu(
10541 &mut self,
10542 display_row: DisplayRow,
10543 position: Option<Anchor>,
10544 clicked_point: gpui::Point<Pixels>,
10545 window: &mut Window,
10546 cx: &mut Context<Self>,
10547 ) {
10548 let source = self
10549 .buffer
10550 .read(cx)
10551 .snapshot(cx)
10552 .anchor_before(Point::new(display_row.0, 0u32));
10553
10554 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10555
10556 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10557 self,
10558 source,
10559 clicked_point,
10560 context_menu,
10561 window,
10562 cx,
10563 );
10564 }
10565
10566 fn add_edit_breakpoint_block(
10567 &mut self,
10568 anchor: Anchor,
10569 breakpoint: &Breakpoint,
10570 edit_action: BreakpointPromptEditAction,
10571 window: &mut Window,
10572 cx: &mut Context<Self>,
10573 ) {
10574 let weak_editor = cx.weak_entity();
10575 let bp_prompt = cx.new(|cx| {
10576 BreakpointPromptEditor::new(
10577 weak_editor,
10578 anchor,
10579 breakpoint.clone(),
10580 edit_action,
10581 window,
10582 cx,
10583 )
10584 });
10585
10586 let height = bp_prompt.update(cx, |this, cx| {
10587 this.prompt
10588 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10589 });
10590 let cloned_prompt = bp_prompt.clone();
10591 let blocks = vec![BlockProperties {
10592 style: BlockStyle::Sticky,
10593 placement: BlockPlacement::Above(anchor),
10594 height: Some(height),
10595 render: Arc::new(move |cx| {
10596 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10597 cloned_prompt.clone().into_any_element()
10598 }),
10599 priority: 0,
10600 }];
10601
10602 let focus_handle = bp_prompt.focus_handle(cx);
10603 window.focus(&focus_handle);
10604
10605 let block_ids = self.insert_blocks(blocks, None, cx);
10606 bp_prompt.update(cx, |prompt, _| {
10607 prompt.add_block_ids(block_ids);
10608 });
10609 }
10610
10611 pub(crate) fn breakpoint_at_row(
10612 &self,
10613 row: u32,
10614 window: &mut Window,
10615 cx: &mut Context<Self>,
10616 ) -> Option<(Anchor, Breakpoint)> {
10617 let snapshot = self.snapshot(window, cx);
10618 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10619
10620 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10621 }
10622
10623 pub(crate) fn breakpoint_at_anchor(
10624 &self,
10625 breakpoint_position: Anchor,
10626 snapshot: &EditorSnapshot,
10627 cx: &mut Context<Self>,
10628 ) -> Option<(Anchor, Breakpoint)> {
10629 let buffer = self
10630 .buffer
10631 .read(cx)
10632 .buffer_for_anchor(breakpoint_position, cx)?;
10633
10634 let enclosing_excerpt = breakpoint_position.excerpt_id;
10635 let buffer_snapshot = buffer.read(cx).snapshot();
10636
10637 let row = buffer_snapshot
10638 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10639 .row;
10640
10641 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10642 let anchor_end = snapshot
10643 .buffer_snapshot
10644 .anchor_after(Point::new(row, line_len));
10645
10646 self.breakpoint_store
10647 .as_ref()?
10648 .read_with(cx, |breakpoint_store, cx| {
10649 breakpoint_store
10650 .breakpoints(
10651 &buffer,
10652 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10653 &buffer_snapshot,
10654 cx,
10655 )
10656 .next()
10657 .and_then(|(bp, _)| {
10658 let breakpoint_row = buffer_snapshot
10659 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10660 .row;
10661
10662 if breakpoint_row == row {
10663 snapshot
10664 .buffer_snapshot
10665 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10666 .map(|position| (position, bp.bp.clone()))
10667 } else {
10668 None
10669 }
10670 })
10671 })
10672 }
10673
10674 pub fn edit_log_breakpoint(
10675 &mut self,
10676 _: &EditLogBreakpoint,
10677 window: &mut Window,
10678 cx: &mut Context<Self>,
10679 ) {
10680 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10681 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10682 message: None,
10683 state: BreakpointState::Enabled,
10684 condition: None,
10685 hit_condition: None,
10686 });
10687
10688 self.add_edit_breakpoint_block(
10689 anchor,
10690 &breakpoint,
10691 BreakpointPromptEditAction::Log,
10692 window,
10693 cx,
10694 );
10695 }
10696 }
10697
10698 fn breakpoints_at_cursors(
10699 &self,
10700 window: &mut Window,
10701 cx: &mut Context<Self>,
10702 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10703 let snapshot = self.snapshot(window, cx);
10704 let cursors = self
10705 .selections
10706 .disjoint_anchors()
10707 .iter()
10708 .map(|selection| {
10709 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10710
10711 let breakpoint_position = self
10712 .breakpoint_at_row(cursor_position.row, window, cx)
10713 .map(|bp| bp.0)
10714 .unwrap_or_else(|| {
10715 snapshot
10716 .display_snapshot
10717 .buffer_snapshot
10718 .anchor_after(Point::new(cursor_position.row, 0))
10719 });
10720
10721 let breakpoint = self
10722 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10723 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10724
10725 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10726 })
10727 // 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.
10728 .collect::<HashMap<Anchor, _>>();
10729
10730 cursors.into_iter().collect()
10731 }
10732
10733 pub fn enable_breakpoint(
10734 &mut self,
10735 _: &crate::actions::EnableBreakpoint,
10736 window: &mut Window,
10737 cx: &mut Context<Self>,
10738 ) {
10739 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10740 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10741 continue;
10742 };
10743 self.edit_breakpoint_at_anchor(
10744 anchor,
10745 breakpoint,
10746 BreakpointEditAction::InvertState,
10747 cx,
10748 );
10749 }
10750 }
10751
10752 pub fn disable_breakpoint(
10753 &mut self,
10754 _: &crate::actions::DisableBreakpoint,
10755 window: &mut Window,
10756 cx: &mut Context<Self>,
10757 ) {
10758 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10759 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10760 continue;
10761 };
10762 self.edit_breakpoint_at_anchor(
10763 anchor,
10764 breakpoint,
10765 BreakpointEditAction::InvertState,
10766 cx,
10767 );
10768 }
10769 }
10770
10771 pub fn toggle_breakpoint(
10772 &mut self,
10773 _: &crate::actions::ToggleBreakpoint,
10774 window: &mut Window,
10775 cx: &mut Context<Self>,
10776 ) {
10777 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10778 if let Some(breakpoint) = breakpoint {
10779 self.edit_breakpoint_at_anchor(
10780 anchor,
10781 breakpoint,
10782 BreakpointEditAction::Toggle,
10783 cx,
10784 );
10785 } else {
10786 self.edit_breakpoint_at_anchor(
10787 anchor,
10788 Breakpoint::new_standard(),
10789 BreakpointEditAction::Toggle,
10790 cx,
10791 );
10792 }
10793 }
10794 }
10795
10796 pub fn edit_breakpoint_at_anchor(
10797 &mut self,
10798 breakpoint_position: Anchor,
10799 breakpoint: Breakpoint,
10800 edit_action: BreakpointEditAction,
10801 cx: &mut Context<Self>,
10802 ) {
10803 let Some(breakpoint_store) = &self.breakpoint_store else {
10804 return;
10805 };
10806
10807 let Some(buffer) = self
10808 .buffer
10809 .read(cx)
10810 .buffer_for_anchor(breakpoint_position, cx)
10811 else {
10812 return;
10813 };
10814
10815 breakpoint_store.update(cx, |breakpoint_store, cx| {
10816 breakpoint_store.toggle_breakpoint(
10817 buffer,
10818 BreakpointWithPosition {
10819 position: breakpoint_position.text_anchor,
10820 bp: breakpoint,
10821 },
10822 edit_action,
10823 cx,
10824 );
10825 });
10826
10827 cx.notify();
10828 }
10829
10830 #[cfg(any(test, feature = "test-support"))]
10831 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10832 self.breakpoint_store.clone()
10833 }
10834
10835 pub fn prepare_restore_change(
10836 &self,
10837 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10838 hunk: &MultiBufferDiffHunk,
10839 cx: &mut App,
10840 ) -> Option<()> {
10841 if hunk.is_created_file() {
10842 return None;
10843 }
10844 let buffer = self.buffer.read(cx);
10845 let diff = buffer.diff_for(hunk.buffer_id)?;
10846 let buffer = buffer.buffer(hunk.buffer_id)?;
10847 let buffer = buffer.read(cx);
10848 let original_text = diff
10849 .read(cx)
10850 .base_text()
10851 .as_rope()
10852 .slice(hunk.diff_base_byte_range.clone());
10853 let buffer_snapshot = buffer.snapshot();
10854 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10855 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10856 probe
10857 .0
10858 .start
10859 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10860 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10861 }) {
10862 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10863 Some(())
10864 } else {
10865 None
10866 }
10867 }
10868
10869 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10870 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10871 }
10872
10873 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10874 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10875 }
10876
10877 fn manipulate_lines<M>(
10878 &mut self,
10879 window: &mut Window,
10880 cx: &mut Context<Self>,
10881 mut manipulate: M,
10882 ) where
10883 M: FnMut(&str) -> LineManipulationResult,
10884 {
10885 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10886
10887 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10888 let buffer = self.buffer.read(cx).snapshot(cx);
10889
10890 let mut edits = Vec::new();
10891
10892 let selections = self.selections.all::<Point>(cx);
10893 let mut selections = selections.iter().peekable();
10894 let mut contiguous_row_selections = Vec::new();
10895 let mut new_selections = Vec::new();
10896 let mut added_lines = 0;
10897 let mut removed_lines = 0;
10898
10899 while let Some(selection) = selections.next() {
10900 let (start_row, end_row) = consume_contiguous_rows(
10901 &mut contiguous_row_selections,
10902 selection,
10903 &display_map,
10904 &mut selections,
10905 );
10906
10907 let start_point = Point::new(start_row.0, 0);
10908 let end_point = Point::new(
10909 end_row.previous_row().0,
10910 buffer.line_len(end_row.previous_row()),
10911 );
10912 let text = buffer
10913 .text_for_range(start_point..end_point)
10914 .collect::<String>();
10915
10916 let LineManipulationResult {
10917 new_text,
10918 line_count_before,
10919 line_count_after,
10920 } = manipulate(&text);
10921
10922 edits.push((start_point..end_point, new_text));
10923
10924 // Selections must change based on added and removed line count
10925 let start_row =
10926 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10927 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10928 new_selections.push(Selection {
10929 id: selection.id,
10930 start: start_row,
10931 end: end_row,
10932 goal: SelectionGoal::None,
10933 reversed: selection.reversed,
10934 });
10935
10936 if line_count_after > line_count_before {
10937 added_lines += line_count_after - line_count_before;
10938 } else if line_count_before > line_count_after {
10939 removed_lines += line_count_before - line_count_after;
10940 }
10941 }
10942
10943 self.transact(window, cx, |this, window, cx| {
10944 let buffer = this.buffer.update(cx, |buffer, cx| {
10945 buffer.edit(edits, None, cx);
10946 buffer.snapshot(cx)
10947 });
10948
10949 // Recalculate offsets on newly edited buffer
10950 let new_selections = new_selections
10951 .iter()
10952 .map(|s| {
10953 let start_point = Point::new(s.start.0, 0);
10954 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10955 Selection {
10956 id: s.id,
10957 start: buffer.point_to_offset(start_point),
10958 end: buffer.point_to_offset(end_point),
10959 goal: s.goal,
10960 reversed: s.reversed,
10961 }
10962 })
10963 .collect();
10964
10965 this.change_selections(Default::default(), window, cx, |s| {
10966 s.select(new_selections);
10967 });
10968
10969 this.request_autoscroll(Autoscroll::fit(), cx);
10970 });
10971 }
10972
10973 fn manipulate_immutable_lines<Fn>(
10974 &mut self,
10975 window: &mut Window,
10976 cx: &mut Context<Self>,
10977 mut callback: Fn,
10978 ) where
10979 Fn: FnMut(&mut Vec<&str>),
10980 {
10981 self.manipulate_lines(window, cx, |text| {
10982 let mut lines: Vec<&str> = text.split('\n').collect();
10983 let line_count_before = lines.len();
10984
10985 callback(&mut lines);
10986
10987 LineManipulationResult {
10988 new_text: lines.join("\n"),
10989 line_count_before,
10990 line_count_after: lines.len(),
10991 }
10992 });
10993 }
10994
10995 fn manipulate_mutable_lines<Fn>(
10996 &mut self,
10997 window: &mut Window,
10998 cx: &mut Context<Self>,
10999 mut callback: Fn,
11000 ) where
11001 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11002 {
11003 self.manipulate_lines(window, cx, |text| {
11004 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11005 let line_count_before = lines.len();
11006
11007 callback(&mut lines);
11008
11009 LineManipulationResult {
11010 new_text: lines.join("\n"),
11011 line_count_before,
11012 line_count_after: lines.len(),
11013 }
11014 });
11015 }
11016
11017 pub fn convert_indentation_to_spaces(
11018 &mut self,
11019 _: &ConvertIndentationToSpaces,
11020 window: &mut Window,
11021 cx: &mut Context<Self>,
11022 ) {
11023 let settings = self.buffer.read(cx).language_settings(cx);
11024 let tab_size = settings.tab_size.get() as usize;
11025
11026 self.manipulate_mutable_lines(window, cx, |lines| {
11027 // Allocates a reasonably sized scratch buffer once for the whole loop
11028 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11029 // Avoids recomputing spaces that could be inserted many times
11030 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11031 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11032 .collect();
11033
11034 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11035 let mut chars = line.as_ref().chars();
11036 let mut col = 0;
11037 let mut changed = false;
11038
11039 for ch in chars.by_ref() {
11040 match ch {
11041 ' ' => {
11042 reindented_line.push(' ');
11043 col += 1;
11044 }
11045 '\t' => {
11046 // \t are converted to spaces depending on the current column
11047 let spaces_len = tab_size - (col % tab_size);
11048 reindented_line.extend(&space_cache[spaces_len - 1]);
11049 col += spaces_len;
11050 changed = true;
11051 }
11052 _ => {
11053 // If we dont append before break, the character is consumed
11054 reindented_line.push(ch);
11055 break;
11056 }
11057 }
11058 }
11059
11060 if !changed {
11061 reindented_line.clear();
11062 continue;
11063 }
11064 // Append the rest of the line and replace old reference with new one
11065 reindented_line.extend(chars);
11066 *line = Cow::Owned(reindented_line.clone());
11067 reindented_line.clear();
11068 }
11069 });
11070 }
11071
11072 pub fn convert_indentation_to_tabs(
11073 &mut self,
11074 _: &ConvertIndentationToTabs,
11075 window: &mut Window,
11076 cx: &mut Context<Self>,
11077 ) {
11078 let settings = self.buffer.read(cx).language_settings(cx);
11079 let tab_size = settings.tab_size.get() as usize;
11080
11081 self.manipulate_mutable_lines(window, cx, |lines| {
11082 // Allocates a reasonably sized buffer once for the whole loop
11083 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11084 // Avoids recomputing spaces that could be inserted many times
11085 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11086 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11087 .collect();
11088
11089 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11090 let mut chars = line.chars();
11091 let mut spaces_count = 0;
11092 let mut first_non_indent_char = None;
11093 let mut changed = false;
11094
11095 for ch in chars.by_ref() {
11096 match ch {
11097 ' ' => {
11098 // Keep track of spaces. Append \t when we reach tab_size
11099 spaces_count += 1;
11100 changed = true;
11101 if spaces_count == tab_size {
11102 reindented_line.push('\t');
11103 spaces_count = 0;
11104 }
11105 }
11106 '\t' => {
11107 reindented_line.push('\t');
11108 spaces_count = 0;
11109 }
11110 _ => {
11111 // Dont append it yet, we might have remaining spaces
11112 first_non_indent_char = Some(ch);
11113 break;
11114 }
11115 }
11116 }
11117
11118 if !changed {
11119 reindented_line.clear();
11120 continue;
11121 }
11122 // Remaining spaces that didn't make a full tab stop
11123 if spaces_count > 0 {
11124 reindented_line.extend(&space_cache[spaces_count - 1]);
11125 }
11126 // If we consume an extra character that was not indentation, add it back
11127 if let Some(extra_char) = first_non_indent_char {
11128 reindented_line.push(extra_char);
11129 }
11130 // Append the rest of the line and replace old reference with new one
11131 reindented_line.extend(chars);
11132 *line = Cow::Owned(reindented_line.clone());
11133 reindented_line.clear();
11134 }
11135 });
11136 }
11137
11138 pub fn convert_to_upper_case(
11139 &mut self,
11140 _: &ConvertToUpperCase,
11141 window: &mut Window,
11142 cx: &mut Context<Self>,
11143 ) {
11144 self.manipulate_text(window, cx, |text| text.to_uppercase())
11145 }
11146
11147 pub fn convert_to_lower_case(
11148 &mut self,
11149 _: &ConvertToLowerCase,
11150 window: &mut Window,
11151 cx: &mut Context<Self>,
11152 ) {
11153 self.manipulate_text(window, cx, |text| text.to_lowercase())
11154 }
11155
11156 pub fn convert_to_title_case(
11157 &mut self,
11158 _: &ConvertToTitleCase,
11159 window: &mut Window,
11160 cx: &mut Context<Self>,
11161 ) {
11162 self.manipulate_text(window, cx, |text| {
11163 text.split('\n')
11164 .map(|line| line.to_case(Case::Title))
11165 .join("\n")
11166 })
11167 }
11168
11169 pub fn convert_to_snake_case(
11170 &mut self,
11171 _: &ConvertToSnakeCase,
11172 window: &mut Window,
11173 cx: &mut Context<Self>,
11174 ) {
11175 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11176 }
11177
11178 pub fn convert_to_kebab_case(
11179 &mut self,
11180 _: &ConvertToKebabCase,
11181 window: &mut Window,
11182 cx: &mut Context<Self>,
11183 ) {
11184 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11185 }
11186
11187 pub fn convert_to_upper_camel_case(
11188 &mut self,
11189 _: &ConvertToUpperCamelCase,
11190 window: &mut Window,
11191 cx: &mut Context<Self>,
11192 ) {
11193 self.manipulate_text(window, cx, |text| {
11194 text.split('\n')
11195 .map(|line| line.to_case(Case::UpperCamel))
11196 .join("\n")
11197 })
11198 }
11199
11200 pub fn convert_to_lower_camel_case(
11201 &mut self,
11202 _: &ConvertToLowerCamelCase,
11203 window: &mut Window,
11204 cx: &mut Context<Self>,
11205 ) {
11206 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11207 }
11208
11209 pub fn convert_to_opposite_case(
11210 &mut self,
11211 _: &ConvertToOppositeCase,
11212 window: &mut Window,
11213 cx: &mut Context<Self>,
11214 ) {
11215 self.manipulate_text(window, cx, |text| {
11216 text.chars()
11217 .fold(String::with_capacity(text.len()), |mut t, c| {
11218 if c.is_uppercase() {
11219 t.extend(c.to_lowercase());
11220 } else {
11221 t.extend(c.to_uppercase());
11222 }
11223 t
11224 })
11225 })
11226 }
11227
11228 pub fn convert_to_sentence_case(
11229 &mut self,
11230 _: &ConvertToSentenceCase,
11231 window: &mut Window,
11232 cx: &mut Context<Self>,
11233 ) {
11234 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11235 }
11236
11237 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11238 self.manipulate_text(window, cx, |text| {
11239 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11240 if has_upper_case_characters {
11241 text.to_lowercase()
11242 } else {
11243 text.to_uppercase()
11244 }
11245 })
11246 }
11247
11248 pub fn convert_to_rot13(
11249 &mut self,
11250 _: &ConvertToRot13,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 ) {
11254 self.manipulate_text(window, cx, |text| {
11255 text.chars()
11256 .map(|c| match c {
11257 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11258 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11259 _ => c,
11260 })
11261 .collect()
11262 })
11263 }
11264
11265 pub fn convert_to_rot47(
11266 &mut self,
11267 _: &ConvertToRot47,
11268 window: &mut Window,
11269 cx: &mut Context<Self>,
11270 ) {
11271 self.manipulate_text(window, cx, |text| {
11272 text.chars()
11273 .map(|c| {
11274 let code_point = c as u32;
11275 if code_point >= 33 && code_point <= 126 {
11276 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11277 }
11278 c
11279 })
11280 .collect()
11281 })
11282 }
11283
11284 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11285 where
11286 Fn: FnMut(&str) -> String,
11287 {
11288 let buffer = self.buffer.read(cx).snapshot(cx);
11289
11290 let mut new_selections = Vec::new();
11291 let mut edits = Vec::new();
11292 let mut selection_adjustment = 0i32;
11293
11294 for selection in self.selections.all::<usize>(cx) {
11295 let selection_is_empty = selection.is_empty();
11296
11297 let (start, end) = if selection_is_empty {
11298 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11299 (word_range.start, word_range.end)
11300 } else {
11301 (selection.start, selection.end)
11302 };
11303
11304 let text = buffer.text_for_range(start..end).collect::<String>();
11305 let old_length = text.len() as i32;
11306 let text = callback(&text);
11307
11308 new_selections.push(Selection {
11309 start: (start as i32 - selection_adjustment) as usize,
11310 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11311 goal: SelectionGoal::None,
11312 ..selection
11313 });
11314
11315 selection_adjustment += old_length - text.len() as i32;
11316
11317 edits.push((start..end, text));
11318 }
11319
11320 self.transact(window, cx, |this, window, cx| {
11321 this.buffer.update(cx, |buffer, cx| {
11322 buffer.edit(edits, None, cx);
11323 });
11324
11325 this.change_selections(Default::default(), window, cx, |s| {
11326 s.select(new_selections);
11327 });
11328
11329 this.request_autoscroll(Autoscroll::fit(), cx);
11330 });
11331 }
11332
11333 pub fn move_selection_on_drop(
11334 &mut self,
11335 selection: &Selection<Anchor>,
11336 target: DisplayPoint,
11337 is_cut: bool,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) {
11341 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11342 let buffer = &display_map.buffer_snapshot;
11343 let mut edits = Vec::new();
11344 let insert_point = display_map
11345 .clip_point(target, Bias::Left)
11346 .to_point(&display_map);
11347 let text = buffer
11348 .text_for_range(selection.start..selection.end)
11349 .collect::<String>();
11350 if is_cut {
11351 edits.push(((selection.start..selection.end), String::new()));
11352 }
11353 let insert_anchor = buffer.anchor_before(insert_point);
11354 edits.push(((insert_anchor..insert_anchor), text));
11355 let last_edit_start = insert_anchor.bias_left(buffer);
11356 let last_edit_end = insert_anchor.bias_right(buffer);
11357 self.transact(window, cx, |this, window, cx| {
11358 this.buffer.update(cx, |buffer, cx| {
11359 buffer.edit(edits, None, cx);
11360 });
11361 this.change_selections(Default::default(), window, cx, |s| {
11362 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11363 });
11364 });
11365 }
11366
11367 pub fn clear_selection_drag_state(&mut self) {
11368 self.selection_drag_state = SelectionDragState::None;
11369 }
11370
11371 pub fn duplicate(
11372 &mut self,
11373 upwards: bool,
11374 whole_lines: bool,
11375 window: &mut Window,
11376 cx: &mut Context<Self>,
11377 ) {
11378 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11379
11380 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11381 let buffer = &display_map.buffer_snapshot;
11382 let selections = self.selections.all::<Point>(cx);
11383
11384 let mut edits = Vec::new();
11385 let mut selections_iter = selections.iter().peekable();
11386 while let Some(selection) = selections_iter.next() {
11387 let mut rows = selection.spanned_rows(false, &display_map);
11388 // duplicate line-wise
11389 if whole_lines || selection.start == selection.end {
11390 // Avoid duplicating the same lines twice.
11391 while let Some(next_selection) = selections_iter.peek() {
11392 let next_rows = next_selection.spanned_rows(false, &display_map);
11393 if next_rows.start < rows.end {
11394 rows.end = next_rows.end;
11395 selections_iter.next().unwrap();
11396 } else {
11397 break;
11398 }
11399 }
11400
11401 // Copy the text from the selected row region and splice it either at the start
11402 // or end of the region.
11403 let start = Point::new(rows.start.0, 0);
11404 let end = Point::new(
11405 rows.end.previous_row().0,
11406 buffer.line_len(rows.end.previous_row()),
11407 );
11408 let text = buffer
11409 .text_for_range(start..end)
11410 .chain(Some("\n"))
11411 .collect::<String>();
11412 let insert_location = if upwards {
11413 Point::new(rows.end.0, 0)
11414 } else {
11415 start
11416 };
11417 edits.push((insert_location..insert_location, text));
11418 } else {
11419 // duplicate character-wise
11420 let start = selection.start;
11421 let end = selection.end;
11422 let text = buffer.text_for_range(start..end).collect::<String>();
11423 edits.push((selection.end..selection.end, text));
11424 }
11425 }
11426
11427 self.transact(window, cx, |this, _, cx| {
11428 this.buffer.update(cx, |buffer, cx| {
11429 buffer.edit(edits, None, cx);
11430 });
11431
11432 this.request_autoscroll(Autoscroll::fit(), cx);
11433 });
11434 }
11435
11436 pub fn duplicate_line_up(
11437 &mut self,
11438 _: &DuplicateLineUp,
11439 window: &mut Window,
11440 cx: &mut Context<Self>,
11441 ) {
11442 self.duplicate(true, true, window, cx);
11443 }
11444
11445 pub fn duplicate_line_down(
11446 &mut self,
11447 _: &DuplicateLineDown,
11448 window: &mut Window,
11449 cx: &mut Context<Self>,
11450 ) {
11451 self.duplicate(false, true, window, cx);
11452 }
11453
11454 pub fn duplicate_selection(
11455 &mut self,
11456 _: &DuplicateSelection,
11457 window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 self.duplicate(false, false, window, cx);
11461 }
11462
11463 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11465 if self.mode.is_single_line() {
11466 cx.propagate();
11467 return;
11468 }
11469
11470 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11471 let buffer = self.buffer.read(cx).snapshot(cx);
11472
11473 let mut edits = Vec::new();
11474 let mut unfold_ranges = Vec::new();
11475 let mut refold_creases = Vec::new();
11476
11477 let selections = self.selections.all::<Point>(cx);
11478 let mut selections = selections.iter().peekable();
11479 let mut contiguous_row_selections = Vec::new();
11480 let mut new_selections = Vec::new();
11481
11482 while let Some(selection) = selections.next() {
11483 // Find all the selections that span a contiguous row range
11484 let (start_row, end_row) = consume_contiguous_rows(
11485 &mut contiguous_row_selections,
11486 selection,
11487 &display_map,
11488 &mut selections,
11489 );
11490
11491 // Move the text spanned by the row range to be before the line preceding the row range
11492 if start_row.0 > 0 {
11493 let range_to_move = Point::new(
11494 start_row.previous_row().0,
11495 buffer.line_len(start_row.previous_row()),
11496 )
11497 ..Point::new(
11498 end_row.previous_row().0,
11499 buffer.line_len(end_row.previous_row()),
11500 );
11501 let insertion_point = display_map
11502 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11503 .0;
11504
11505 // Don't move lines across excerpts
11506 if buffer
11507 .excerpt_containing(insertion_point..range_to_move.end)
11508 .is_some()
11509 {
11510 let text = buffer
11511 .text_for_range(range_to_move.clone())
11512 .flat_map(|s| s.chars())
11513 .skip(1)
11514 .chain(['\n'])
11515 .collect::<String>();
11516
11517 edits.push((
11518 buffer.anchor_after(range_to_move.start)
11519 ..buffer.anchor_before(range_to_move.end),
11520 String::new(),
11521 ));
11522 let insertion_anchor = buffer.anchor_after(insertion_point);
11523 edits.push((insertion_anchor..insertion_anchor, text));
11524
11525 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11526
11527 // Move selections up
11528 new_selections.extend(contiguous_row_selections.drain(..).map(
11529 |mut selection| {
11530 selection.start.row -= row_delta;
11531 selection.end.row -= row_delta;
11532 selection
11533 },
11534 ));
11535
11536 // Move folds up
11537 unfold_ranges.push(range_to_move.clone());
11538 for fold in display_map.folds_in_range(
11539 buffer.anchor_before(range_to_move.start)
11540 ..buffer.anchor_after(range_to_move.end),
11541 ) {
11542 let mut start = fold.range.start.to_point(&buffer);
11543 let mut end = fold.range.end.to_point(&buffer);
11544 start.row -= row_delta;
11545 end.row -= row_delta;
11546 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11547 }
11548 }
11549 }
11550
11551 // If we didn't move line(s), preserve the existing selections
11552 new_selections.append(&mut contiguous_row_selections);
11553 }
11554
11555 self.transact(window, cx, |this, window, cx| {
11556 this.unfold_ranges(&unfold_ranges, true, true, cx);
11557 this.buffer.update(cx, |buffer, cx| {
11558 for (range, text) in edits {
11559 buffer.edit([(range, text)], None, cx);
11560 }
11561 });
11562 this.fold_creases(refold_creases, true, window, cx);
11563 this.change_selections(Default::default(), window, cx, |s| {
11564 s.select(new_selections);
11565 })
11566 });
11567 }
11568
11569 pub fn move_line_down(
11570 &mut self,
11571 _: &MoveLineDown,
11572 window: &mut Window,
11573 cx: &mut Context<Self>,
11574 ) {
11575 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11576 if self.mode.is_single_line() {
11577 cx.propagate();
11578 return;
11579 }
11580
11581 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11582 let buffer = self.buffer.read(cx).snapshot(cx);
11583
11584 let mut edits = Vec::new();
11585 let mut unfold_ranges = Vec::new();
11586 let mut refold_creases = Vec::new();
11587
11588 let selections = self.selections.all::<Point>(cx);
11589 let mut selections = selections.iter().peekable();
11590 let mut contiguous_row_selections = Vec::new();
11591 let mut new_selections = Vec::new();
11592
11593 while let Some(selection) = selections.next() {
11594 // Find all the selections that span a contiguous row range
11595 let (start_row, end_row) = consume_contiguous_rows(
11596 &mut contiguous_row_selections,
11597 selection,
11598 &display_map,
11599 &mut selections,
11600 );
11601
11602 // Move the text spanned by the row range to be after the last line of the row range
11603 if end_row.0 <= buffer.max_point().row {
11604 let range_to_move =
11605 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11606 let insertion_point = display_map
11607 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11608 .0;
11609
11610 // Don't move lines across excerpt boundaries
11611 if buffer
11612 .excerpt_containing(range_to_move.start..insertion_point)
11613 .is_some()
11614 {
11615 let mut text = String::from("\n");
11616 text.extend(buffer.text_for_range(range_to_move.clone()));
11617 text.pop(); // Drop trailing newline
11618 edits.push((
11619 buffer.anchor_after(range_to_move.start)
11620 ..buffer.anchor_before(range_to_move.end),
11621 String::new(),
11622 ));
11623 let insertion_anchor = buffer.anchor_after(insertion_point);
11624 edits.push((insertion_anchor..insertion_anchor, text));
11625
11626 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11627
11628 // Move selections down
11629 new_selections.extend(contiguous_row_selections.drain(..).map(
11630 |mut selection| {
11631 selection.start.row += row_delta;
11632 selection.end.row += row_delta;
11633 selection
11634 },
11635 ));
11636
11637 // Move folds down
11638 unfold_ranges.push(range_to_move.clone());
11639 for fold in display_map.folds_in_range(
11640 buffer.anchor_before(range_to_move.start)
11641 ..buffer.anchor_after(range_to_move.end),
11642 ) {
11643 let mut start = fold.range.start.to_point(&buffer);
11644 let mut end = fold.range.end.to_point(&buffer);
11645 start.row += row_delta;
11646 end.row += row_delta;
11647 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11648 }
11649 }
11650 }
11651
11652 // If we didn't move line(s), preserve the existing selections
11653 new_selections.append(&mut contiguous_row_selections);
11654 }
11655
11656 self.transact(window, cx, |this, window, cx| {
11657 this.unfold_ranges(&unfold_ranges, true, true, cx);
11658 this.buffer.update(cx, |buffer, cx| {
11659 for (range, text) in edits {
11660 buffer.edit([(range, text)], None, cx);
11661 }
11662 });
11663 this.fold_creases(refold_creases, true, window, cx);
11664 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11665 });
11666 }
11667
11668 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11669 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11670 let text_layout_details = &self.text_layout_details(window);
11671 self.transact(window, cx, |this, window, cx| {
11672 let edits = this.change_selections(Default::default(), window, cx, |s| {
11673 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11674 s.move_with(|display_map, selection| {
11675 if !selection.is_empty() {
11676 return;
11677 }
11678
11679 let mut head = selection.head();
11680 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11681 if head.column() == display_map.line_len(head.row()) {
11682 transpose_offset = display_map
11683 .buffer_snapshot
11684 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11685 }
11686
11687 if transpose_offset == 0 {
11688 return;
11689 }
11690
11691 *head.column_mut() += 1;
11692 head = display_map.clip_point(head, Bias::Right);
11693 let goal = SelectionGoal::HorizontalPosition(
11694 display_map
11695 .x_for_display_point(head, text_layout_details)
11696 .into(),
11697 );
11698 selection.collapse_to(head, goal);
11699
11700 let transpose_start = display_map
11701 .buffer_snapshot
11702 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11703 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11704 let transpose_end = display_map
11705 .buffer_snapshot
11706 .clip_offset(transpose_offset + 1, Bias::Right);
11707 if let Some(ch) =
11708 display_map.buffer_snapshot.chars_at(transpose_start).next()
11709 {
11710 edits.push((transpose_start..transpose_offset, String::new()));
11711 edits.push((transpose_end..transpose_end, ch.to_string()));
11712 }
11713 }
11714 });
11715 edits
11716 });
11717 this.buffer
11718 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11719 let selections = this.selections.all::<usize>(cx);
11720 this.change_selections(Default::default(), window, cx, |s| {
11721 s.select(selections);
11722 });
11723 });
11724 }
11725
11726 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11727 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11728 if self.mode.is_single_line() {
11729 cx.propagate();
11730 return;
11731 }
11732
11733 self.rewrap_impl(RewrapOptions::default(), cx)
11734 }
11735
11736 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11737 let buffer = self.buffer.read(cx).snapshot(cx);
11738 let selections = self.selections.all::<Point>(cx);
11739
11740 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11741 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11742 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11743 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11744 .peekable();
11745
11746 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11747 row
11748 } else {
11749 return Vec::new();
11750 };
11751
11752 let language_settings = buffer.language_settings_at(selection.head(), cx);
11753 let language_scope = buffer.language_scope_at(selection.head());
11754
11755 let indent_and_prefix_for_row =
11756 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11757 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11758 let (comment_prefix, rewrap_prefix) =
11759 if let Some(language_scope) = &language_scope {
11760 let indent_end = Point::new(row, indent.len);
11761 let comment_prefix = language_scope
11762 .line_comment_prefixes()
11763 .iter()
11764 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11765 .map(|prefix| prefix.to_string());
11766 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11767 let line_text_after_indent = buffer
11768 .text_for_range(indent_end..line_end)
11769 .collect::<String>();
11770 let rewrap_prefix = language_scope
11771 .rewrap_prefixes()
11772 .iter()
11773 .find_map(|prefix_regex| {
11774 prefix_regex.find(&line_text_after_indent).map(|mat| {
11775 if mat.start() == 0 {
11776 Some(mat.as_str().to_string())
11777 } else {
11778 None
11779 }
11780 })
11781 })
11782 .flatten();
11783 (comment_prefix, rewrap_prefix)
11784 } else {
11785 (None, None)
11786 };
11787 (indent, comment_prefix, rewrap_prefix)
11788 };
11789
11790 let mut ranges = Vec::new();
11791 let from_empty_selection = selection.is_empty();
11792
11793 let mut current_range_start = first_row;
11794 let mut prev_row = first_row;
11795 let (
11796 mut current_range_indent,
11797 mut current_range_comment_prefix,
11798 mut current_range_rewrap_prefix,
11799 ) = indent_and_prefix_for_row(first_row);
11800
11801 for row in non_blank_rows_iter.skip(1) {
11802 let has_paragraph_break = row > prev_row + 1;
11803
11804 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11805 indent_and_prefix_for_row(row);
11806
11807 let has_indent_change = row_indent != current_range_indent;
11808 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11809
11810 let has_boundary_change = has_comment_change
11811 || row_rewrap_prefix.is_some()
11812 || (has_indent_change && current_range_comment_prefix.is_some());
11813
11814 if has_paragraph_break || has_boundary_change {
11815 ranges.push((
11816 language_settings.clone(),
11817 Point::new(current_range_start, 0)
11818 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11819 current_range_indent,
11820 current_range_comment_prefix.clone(),
11821 current_range_rewrap_prefix.clone(),
11822 from_empty_selection,
11823 ));
11824 current_range_start = row;
11825 current_range_indent = row_indent;
11826 current_range_comment_prefix = row_comment_prefix;
11827 current_range_rewrap_prefix = row_rewrap_prefix;
11828 }
11829 prev_row = row;
11830 }
11831
11832 ranges.push((
11833 language_settings.clone(),
11834 Point::new(current_range_start, 0)
11835 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11836 current_range_indent,
11837 current_range_comment_prefix,
11838 current_range_rewrap_prefix,
11839 from_empty_selection,
11840 ));
11841
11842 ranges
11843 });
11844
11845 let mut edits = Vec::new();
11846 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11847
11848 for (
11849 language_settings,
11850 wrap_range,
11851 indent_size,
11852 comment_prefix,
11853 rewrap_prefix,
11854 from_empty_selection,
11855 ) in wrap_ranges
11856 {
11857 let mut start_row = wrap_range.start.row;
11858 let mut end_row = wrap_range.end.row;
11859
11860 // Skip selections that overlap with a range that has already been rewrapped.
11861 let selection_range = start_row..end_row;
11862 if rewrapped_row_ranges
11863 .iter()
11864 .any(|range| range.overlaps(&selection_range))
11865 {
11866 continue;
11867 }
11868
11869 let tab_size = language_settings.tab_size;
11870
11871 let indent_prefix = indent_size.chars().collect::<String>();
11872 let mut line_prefix = indent_prefix.clone();
11873 let mut inside_comment = false;
11874 if let Some(prefix) = &comment_prefix {
11875 line_prefix.push_str(prefix);
11876 inside_comment = true;
11877 }
11878 if let Some(prefix) = &rewrap_prefix {
11879 line_prefix.push_str(prefix);
11880 }
11881
11882 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11883 RewrapBehavior::InComments => inside_comment,
11884 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11885 RewrapBehavior::Anywhere => true,
11886 };
11887
11888 let should_rewrap = options.override_language_settings
11889 || allow_rewrap_based_on_language
11890 || self.hard_wrap.is_some();
11891 if !should_rewrap {
11892 continue;
11893 }
11894
11895 if from_empty_selection {
11896 'expand_upwards: while start_row > 0 {
11897 let prev_row = start_row - 1;
11898 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11899 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11900 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11901 {
11902 start_row = prev_row;
11903 } else {
11904 break 'expand_upwards;
11905 }
11906 }
11907
11908 'expand_downwards: while end_row < buffer.max_point().row {
11909 let next_row = end_row + 1;
11910 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11911 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11912 && !buffer.is_line_blank(MultiBufferRow(next_row))
11913 {
11914 end_row = next_row;
11915 } else {
11916 break 'expand_downwards;
11917 }
11918 }
11919 }
11920
11921 let start = Point::new(start_row, 0);
11922 let start_offset = start.to_offset(&buffer);
11923 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11924 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11925 let Some(lines_without_prefixes) = selection_text
11926 .lines()
11927 .enumerate()
11928 .map(|(ix, line)| {
11929 let line_trimmed = line.trim_start();
11930 if rewrap_prefix.is_some() && ix > 0 {
11931 Ok(line_trimmed)
11932 } else {
11933 line_trimmed
11934 .strip_prefix(&line_prefix.trim_start())
11935 .with_context(|| {
11936 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11937 })
11938 }
11939 })
11940 .collect::<Result<Vec<_>, _>>()
11941 .log_err()
11942 else {
11943 continue;
11944 };
11945
11946 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11947 buffer
11948 .language_settings_at(Point::new(start_row, 0), cx)
11949 .preferred_line_length as usize
11950 });
11951
11952 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11953 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11954 } else {
11955 line_prefix.clone()
11956 };
11957
11958 let wrapped_text = wrap_with_prefix(
11959 line_prefix,
11960 subsequent_lines_prefix,
11961 lines_without_prefixes.join("\n"),
11962 wrap_column,
11963 tab_size,
11964 options.preserve_existing_whitespace,
11965 );
11966
11967 // TODO: should always use char-based diff while still supporting cursor behavior that
11968 // matches vim.
11969 let mut diff_options = DiffOptions::default();
11970 if options.override_language_settings {
11971 diff_options.max_word_diff_len = 0;
11972 diff_options.max_word_diff_line_count = 0;
11973 } else {
11974 diff_options.max_word_diff_len = usize::MAX;
11975 diff_options.max_word_diff_line_count = usize::MAX;
11976 }
11977
11978 for (old_range, new_text) in
11979 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11980 {
11981 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11982 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11983 edits.push((edit_start..edit_end, new_text));
11984 }
11985
11986 rewrapped_row_ranges.push(start_row..=end_row);
11987 }
11988
11989 self.buffer
11990 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11991 }
11992
11993 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11994 let mut text = String::new();
11995 let buffer = self.buffer.read(cx).snapshot(cx);
11996 let mut selections = self.selections.all::<Point>(cx);
11997 let mut clipboard_selections = Vec::with_capacity(selections.len());
11998 {
11999 let max_point = buffer.max_point();
12000 let mut is_first = true;
12001 for selection in &mut selections {
12002 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12003 if is_entire_line {
12004 selection.start = Point::new(selection.start.row, 0);
12005 if !selection.is_empty() && selection.end.column == 0 {
12006 selection.end = cmp::min(max_point, selection.end);
12007 } else {
12008 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12009 }
12010 selection.goal = SelectionGoal::None;
12011 }
12012 if is_first {
12013 is_first = false;
12014 } else {
12015 text += "\n";
12016 }
12017 let mut len = 0;
12018 for chunk in buffer.text_for_range(selection.start..selection.end) {
12019 text.push_str(chunk);
12020 len += chunk.len();
12021 }
12022 clipboard_selections.push(ClipboardSelection {
12023 len,
12024 is_entire_line,
12025 first_line_indent: buffer
12026 .indent_size_for_line(MultiBufferRow(selection.start.row))
12027 .len,
12028 });
12029 }
12030 }
12031
12032 self.transact(window, cx, |this, window, cx| {
12033 this.change_selections(Default::default(), window, cx, |s| {
12034 s.select(selections);
12035 });
12036 this.insert("", window, cx);
12037 });
12038 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12039 }
12040
12041 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12043 let item = self.cut_common(window, cx);
12044 cx.write_to_clipboard(item);
12045 }
12046
12047 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12048 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12049 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12050 s.move_with(|snapshot, sel| {
12051 if sel.is_empty() {
12052 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12053 }
12054 });
12055 });
12056 let item = self.cut_common(window, cx);
12057 cx.set_global(KillRing(item))
12058 }
12059
12060 pub fn kill_ring_yank(
12061 &mut self,
12062 _: &KillRingYank,
12063 window: &mut Window,
12064 cx: &mut Context<Self>,
12065 ) {
12066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12067 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12068 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12069 (kill_ring.text().to_string(), kill_ring.metadata_json())
12070 } else {
12071 return;
12072 }
12073 } else {
12074 return;
12075 };
12076 self.do_paste(&text, metadata, false, window, cx);
12077 }
12078
12079 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12080 self.do_copy(true, cx);
12081 }
12082
12083 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12084 self.do_copy(false, cx);
12085 }
12086
12087 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12088 let selections = self.selections.all::<Point>(cx);
12089 let buffer = self.buffer.read(cx).read(cx);
12090 let mut text = String::new();
12091
12092 let mut clipboard_selections = Vec::with_capacity(selections.len());
12093 {
12094 let max_point = buffer.max_point();
12095 let mut is_first = true;
12096 for selection in &selections {
12097 let mut start = selection.start;
12098 let mut end = selection.end;
12099 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12100 if is_entire_line {
12101 start = Point::new(start.row, 0);
12102 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12103 }
12104
12105 let mut trimmed_selections = Vec::new();
12106 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12107 let row = MultiBufferRow(start.row);
12108 let first_indent = buffer.indent_size_for_line(row);
12109 if first_indent.len == 0 || start.column > first_indent.len {
12110 trimmed_selections.push(start..end);
12111 } else {
12112 trimmed_selections.push(
12113 Point::new(row.0, first_indent.len)
12114 ..Point::new(row.0, buffer.line_len(row)),
12115 );
12116 for row in start.row + 1..=end.row {
12117 let mut line_len = buffer.line_len(MultiBufferRow(row));
12118 if row == end.row {
12119 line_len = end.column;
12120 }
12121 if line_len == 0 {
12122 trimmed_selections
12123 .push(Point::new(row, 0)..Point::new(row, line_len));
12124 continue;
12125 }
12126 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12127 if row_indent_size.len >= first_indent.len {
12128 trimmed_selections.push(
12129 Point::new(row, first_indent.len)..Point::new(row, line_len),
12130 );
12131 } else {
12132 trimmed_selections.clear();
12133 trimmed_selections.push(start..end);
12134 break;
12135 }
12136 }
12137 }
12138 } else {
12139 trimmed_selections.push(start..end);
12140 }
12141
12142 for trimmed_range in trimmed_selections {
12143 if is_first {
12144 is_first = false;
12145 } else {
12146 text += "\n";
12147 }
12148 let mut len = 0;
12149 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12150 text.push_str(chunk);
12151 len += chunk.len();
12152 }
12153 clipboard_selections.push(ClipboardSelection {
12154 len,
12155 is_entire_line,
12156 first_line_indent: buffer
12157 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12158 .len,
12159 });
12160 }
12161 }
12162 }
12163
12164 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12165 text,
12166 clipboard_selections,
12167 ));
12168 }
12169
12170 pub fn do_paste(
12171 &mut self,
12172 text: &String,
12173 clipboard_selections: Option<Vec<ClipboardSelection>>,
12174 handle_entire_lines: bool,
12175 window: &mut Window,
12176 cx: &mut Context<Self>,
12177 ) {
12178 if self.read_only(cx) {
12179 return;
12180 }
12181
12182 let clipboard_text = Cow::Borrowed(text);
12183
12184 self.transact(window, cx, |this, window, cx| {
12185 let had_active_edit_prediction = this.has_active_edit_prediction();
12186
12187 if let Some(mut clipboard_selections) = clipboard_selections {
12188 let old_selections = this.selections.all::<usize>(cx);
12189 let all_selections_were_entire_line =
12190 clipboard_selections.iter().all(|s| s.is_entire_line);
12191 let first_selection_indent_column =
12192 clipboard_selections.first().map(|s| s.first_line_indent);
12193 if clipboard_selections.len() != old_selections.len() {
12194 clipboard_selections.drain(..);
12195 }
12196 let cursor_offset = this.selections.last::<usize>(cx).head();
12197 let mut auto_indent_on_paste = true;
12198
12199 this.buffer.update(cx, |buffer, cx| {
12200 let snapshot = buffer.read(cx);
12201 auto_indent_on_paste = snapshot
12202 .language_settings_at(cursor_offset, cx)
12203 .auto_indent_on_paste;
12204
12205 let mut start_offset = 0;
12206 let mut edits = Vec::new();
12207 let mut original_indent_columns = Vec::new();
12208 for (ix, selection) in old_selections.iter().enumerate() {
12209 let to_insert;
12210 let entire_line;
12211 let original_indent_column;
12212 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12213 let end_offset = start_offset + clipboard_selection.len;
12214 to_insert = &clipboard_text[start_offset..end_offset];
12215 entire_line = clipboard_selection.is_entire_line;
12216 start_offset = end_offset + 1;
12217 original_indent_column = Some(clipboard_selection.first_line_indent);
12218 } else {
12219 to_insert = clipboard_text.as_str();
12220 entire_line = all_selections_were_entire_line;
12221 original_indent_column = first_selection_indent_column
12222 }
12223
12224 // If the corresponding selection was empty when this slice of the
12225 // clipboard text was written, then the entire line containing the
12226 // selection was copied. If this selection is also currently empty,
12227 // then paste the line before the current line of the buffer.
12228 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12229 let column = selection.start.to_point(&snapshot).column as usize;
12230 let line_start = selection.start - column;
12231 line_start..line_start
12232 } else {
12233 selection.range()
12234 };
12235
12236 edits.push((range, to_insert));
12237 original_indent_columns.push(original_indent_column);
12238 }
12239 drop(snapshot);
12240
12241 buffer.edit(
12242 edits,
12243 if auto_indent_on_paste {
12244 Some(AutoindentMode::Block {
12245 original_indent_columns,
12246 })
12247 } else {
12248 None
12249 },
12250 cx,
12251 );
12252 });
12253
12254 let selections = this.selections.all::<usize>(cx);
12255 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12256 } else {
12257 this.insert(&clipboard_text, window, cx);
12258 }
12259
12260 let trigger_in_words =
12261 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12262
12263 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12264 });
12265 }
12266
12267 pub fn diff_clipboard_with_selection(
12268 &mut self,
12269 _: &DiffClipboardWithSelection,
12270 window: &mut Window,
12271 cx: &mut Context<Self>,
12272 ) {
12273 let selections = self.selections.all::<usize>(cx);
12274
12275 if selections.is_empty() {
12276 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12277 return;
12278 };
12279
12280 let clipboard_text = match cx.read_from_clipboard() {
12281 Some(item) => match item.entries().first() {
12282 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12283 _ => None,
12284 },
12285 None => None,
12286 };
12287
12288 let Some(clipboard_text) = clipboard_text else {
12289 log::warn!("Clipboard doesn't contain text.");
12290 return;
12291 };
12292
12293 window.dispatch_action(
12294 Box::new(DiffClipboardWithSelectionData {
12295 clipboard_text,
12296 editor: cx.entity(),
12297 }),
12298 cx,
12299 );
12300 }
12301
12302 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12303 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12304 if let Some(item) = cx.read_from_clipboard() {
12305 let entries = item.entries();
12306
12307 match entries.first() {
12308 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12309 // of all the pasted entries.
12310 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12311 .do_paste(
12312 clipboard_string.text(),
12313 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12314 true,
12315 window,
12316 cx,
12317 ),
12318 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12319 }
12320 }
12321 }
12322
12323 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12324 if self.read_only(cx) {
12325 return;
12326 }
12327
12328 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12329
12330 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12331 if let Some((selections, _)) =
12332 self.selection_history.transaction(transaction_id).cloned()
12333 {
12334 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12335 s.select_anchors(selections.to_vec());
12336 });
12337 } else {
12338 log::error!(
12339 "No entry in selection_history found for undo. \
12340 This may correspond to a bug where undo does not update the selection. \
12341 If this is occurring, please add details to \
12342 https://github.com/zed-industries/zed/issues/22692"
12343 );
12344 }
12345 self.request_autoscroll(Autoscroll::fit(), cx);
12346 self.unmark_text(window, cx);
12347 self.refresh_edit_prediction(true, false, window, cx);
12348 cx.emit(EditorEvent::Edited { transaction_id });
12349 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12350 }
12351 }
12352
12353 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12354 if self.read_only(cx) {
12355 return;
12356 }
12357
12358 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12359
12360 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12361 if let Some((_, Some(selections))) =
12362 self.selection_history.transaction(transaction_id).cloned()
12363 {
12364 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12365 s.select_anchors(selections.to_vec());
12366 });
12367 } else {
12368 log::error!(
12369 "No entry in selection_history found for redo. \
12370 This may correspond to a bug where undo does not update the selection. \
12371 If this is occurring, please add details to \
12372 https://github.com/zed-industries/zed/issues/22692"
12373 );
12374 }
12375 self.request_autoscroll(Autoscroll::fit(), cx);
12376 self.unmark_text(window, cx);
12377 self.refresh_edit_prediction(true, false, window, cx);
12378 cx.emit(EditorEvent::Edited { transaction_id });
12379 }
12380 }
12381
12382 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12383 self.buffer
12384 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12385 }
12386
12387 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12388 self.buffer
12389 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12390 }
12391
12392 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12394 self.change_selections(Default::default(), window, cx, |s| {
12395 s.move_with(|map, selection| {
12396 let cursor = if selection.is_empty() {
12397 movement::left(map, selection.start)
12398 } else {
12399 selection.start
12400 };
12401 selection.collapse_to(cursor, SelectionGoal::None);
12402 });
12403 })
12404 }
12405
12406 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12408 self.change_selections(Default::default(), window, cx, |s| {
12409 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12410 })
12411 }
12412
12413 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12414 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12415 self.change_selections(Default::default(), window, cx, |s| {
12416 s.move_with(|map, selection| {
12417 let cursor = if selection.is_empty() {
12418 movement::right(map, selection.end)
12419 } else {
12420 selection.end
12421 };
12422 selection.collapse_to(cursor, SelectionGoal::None)
12423 });
12424 })
12425 }
12426
12427 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12429 self.change_selections(Default::default(), window, cx, |s| {
12430 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12431 })
12432 }
12433
12434 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12435 if self.take_rename(true, window, cx).is_some() {
12436 return;
12437 }
12438
12439 if self.mode.is_single_line() {
12440 cx.propagate();
12441 return;
12442 }
12443
12444 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12445
12446 let text_layout_details = &self.text_layout_details(window);
12447 let selection_count = self.selections.count();
12448 let first_selection = self.selections.first_anchor();
12449
12450 self.change_selections(Default::default(), window, cx, |s| {
12451 s.move_with(|map, selection| {
12452 if !selection.is_empty() {
12453 selection.goal = SelectionGoal::None;
12454 }
12455 let (cursor, goal) = movement::up(
12456 map,
12457 selection.start,
12458 selection.goal,
12459 false,
12460 text_layout_details,
12461 );
12462 selection.collapse_to(cursor, goal);
12463 });
12464 });
12465
12466 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12467 {
12468 cx.propagate();
12469 }
12470 }
12471
12472 pub fn move_up_by_lines(
12473 &mut self,
12474 action: &MoveUpByLines,
12475 window: &mut Window,
12476 cx: &mut Context<Self>,
12477 ) {
12478 if self.take_rename(true, window, cx).is_some() {
12479 return;
12480 }
12481
12482 if self.mode.is_single_line() {
12483 cx.propagate();
12484 return;
12485 }
12486
12487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12488
12489 let text_layout_details = &self.text_layout_details(window);
12490
12491 self.change_selections(Default::default(), window, cx, |s| {
12492 s.move_with(|map, selection| {
12493 if !selection.is_empty() {
12494 selection.goal = SelectionGoal::None;
12495 }
12496 let (cursor, goal) = movement::up_by_rows(
12497 map,
12498 selection.start,
12499 action.lines,
12500 selection.goal,
12501 false,
12502 text_layout_details,
12503 );
12504 selection.collapse_to(cursor, goal);
12505 });
12506 })
12507 }
12508
12509 pub fn move_down_by_lines(
12510 &mut self,
12511 action: &MoveDownByLines,
12512 window: &mut Window,
12513 cx: &mut Context<Self>,
12514 ) {
12515 if self.take_rename(true, window, cx).is_some() {
12516 return;
12517 }
12518
12519 if self.mode.is_single_line() {
12520 cx.propagate();
12521 return;
12522 }
12523
12524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12525
12526 let text_layout_details = &self.text_layout_details(window);
12527
12528 self.change_selections(Default::default(), window, cx, |s| {
12529 s.move_with(|map, selection| {
12530 if !selection.is_empty() {
12531 selection.goal = SelectionGoal::None;
12532 }
12533 let (cursor, goal) = movement::down_by_rows(
12534 map,
12535 selection.start,
12536 action.lines,
12537 selection.goal,
12538 false,
12539 text_layout_details,
12540 );
12541 selection.collapse_to(cursor, goal);
12542 });
12543 })
12544 }
12545
12546 pub fn select_down_by_lines(
12547 &mut self,
12548 action: &SelectDownByLines,
12549 window: &mut Window,
12550 cx: &mut Context<Self>,
12551 ) {
12552 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12553 let text_layout_details = &self.text_layout_details(window);
12554 self.change_selections(Default::default(), window, cx, |s| {
12555 s.move_heads_with(|map, head, goal| {
12556 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12557 })
12558 })
12559 }
12560
12561 pub fn select_up_by_lines(
12562 &mut self,
12563 action: &SelectUpByLines,
12564 window: &mut Window,
12565 cx: &mut Context<Self>,
12566 ) {
12567 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12568 let text_layout_details = &self.text_layout_details(window);
12569 self.change_selections(Default::default(), window, cx, |s| {
12570 s.move_heads_with(|map, head, goal| {
12571 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12572 })
12573 })
12574 }
12575
12576 pub fn select_page_up(
12577 &mut self,
12578 _: &SelectPageUp,
12579 window: &mut Window,
12580 cx: &mut Context<Self>,
12581 ) {
12582 let Some(row_count) = self.visible_row_count() else {
12583 return;
12584 };
12585
12586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12587
12588 let text_layout_details = &self.text_layout_details(window);
12589
12590 self.change_selections(Default::default(), window, cx, |s| {
12591 s.move_heads_with(|map, head, goal| {
12592 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12593 })
12594 })
12595 }
12596
12597 pub fn move_page_up(
12598 &mut self,
12599 action: &MovePageUp,
12600 window: &mut Window,
12601 cx: &mut Context<Self>,
12602 ) {
12603 if self.take_rename(true, window, cx).is_some() {
12604 return;
12605 }
12606
12607 if self
12608 .context_menu
12609 .borrow_mut()
12610 .as_mut()
12611 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12612 .unwrap_or(false)
12613 {
12614 return;
12615 }
12616
12617 if matches!(self.mode, EditorMode::SingleLine) {
12618 cx.propagate();
12619 return;
12620 }
12621
12622 let Some(row_count) = self.visible_row_count() else {
12623 return;
12624 };
12625
12626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12627
12628 let effects = if action.center_cursor {
12629 SelectionEffects::scroll(Autoscroll::center())
12630 } else {
12631 SelectionEffects::default()
12632 };
12633
12634 let text_layout_details = &self.text_layout_details(window);
12635
12636 self.change_selections(effects, window, cx, |s| {
12637 s.move_with(|map, selection| {
12638 if !selection.is_empty() {
12639 selection.goal = SelectionGoal::None;
12640 }
12641 let (cursor, goal) = movement::up_by_rows(
12642 map,
12643 selection.end,
12644 row_count,
12645 selection.goal,
12646 false,
12647 text_layout_details,
12648 );
12649 selection.collapse_to(cursor, goal);
12650 });
12651 });
12652 }
12653
12654 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12655 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12656 let text_layout_details = &self.text_layout_details(window);
12657 self.change_selections(Default::default(), window, cx, |s| {
12658 s.move_heads_with(|map, head, goal| {
12659 movement::up(map, head, goal, false, text_layout_details)
12660 })
12661 })
12662 }
12663
12664 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12665 self.take_rename(true, window, cx);
12666
12667 if self.mode.is_single_line() {
12668 cx.propagate();
12669 return;
12670 }
12671
12672 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12673
12674 let text_layout_details = &self.text_layout_details(window);
12675 let selection_count = self.selections.count();
12676 let first_selection = self.selections.first_anchor();
12677
12678 self.change_selections(Default::default(), window, cx, |s| {
12679 s.move_with(|map, selection| {
12680 if !selection.is_empty() {
12681 selection.goal = SelectionGoal::None;
12682 }
12683 let (cursor, goal) = movement::down(
12684 map,
12685 selection.end,
12686 selection.goal,
12687 false,
12688 text_layout_details,
12689 );
12690 selection.collapse_to(cursor, goal);
12691 });
12692 });
12693
12694 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12695 {
12696 cx.propagate();
12697 }
12698 }
12699
12700 pub fn select_page_down(
12701 &mut self,
12702 _: &SelectPageDown,
12703 window: &mut Window,
12704 cx: &mut Context<Self>,
12705 ) {
12706 let Some(row_count) = self.visible_row_count() else {
12707 return;
12708 };
12709
12710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12711
12712 let text_layout_details = &self.text_layout_details(window);
12713
12714 self.change_selections(Default::default(), window, cx, |s| {
12715 s.move_heads_with(|map, head, goal| {
12716 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12717 })
12718 })
12719 }
12720
12721 pub fn move_page_down(
12722 &mut self,
12723 action: &MovePageDown,
12724 window: &mut Window,
12725 cx: &mut Context<Self>,
12726 ) {
12727 if self.take_rename(true, window, cx).is_some() {
12728 return;
12729 }
12730
12731 if self
12732 .context_menu
12733 .borrow_mut()
12734 .as_mut()
12735 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12736 .unwrap_or(false)
12737 {
12738 return;
12739 }
12740
12741 if matches!(self.mode, EditorMode::SingleLine) {
12742 cx.propagate();
12743 return;
12744 }
12745
12746 let Some(row_count) = self.visible_row_count() else {
12747 return;
12748 };
12749
12750 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12751
12752 let effects = if action.center_cursor {
12753 SelectionEffects::scroll(Autoscroll::center())
12754 } else {
12755 SelectionEffects::default()
12756 };
12757
12758 let text_layout_details = &self.text_layout_details(window);
12759 self.change_selections(effects, window, cx, |s| {
12760 s.move_with(|map, selection| {
12761 if !selection.is_empty() {
12762 selection.goal = SelectionGoal::None;
12763 }
12764 let (cursor, goal) = movement::down_by_rows(
12765 map,
12766 selection.end,
12767 row_count,
12768 selection.goal,
12769 false,
12770 text_layout_details,
12771 );
12772 selection.collapse_to(cursor, goal);
12773 });
12774 });
12775 }
12776
12777 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12778 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12779 let text_layout_details = &self.text_layout_details(window);
12780 self.change_selections(Default::default(), window, cx, |s| {
12781 s.move_heads_with(|map, head, goal| {
12782 movement::down(map, head, goal, false, text_layout_details)
12783 })
12784 });
12785 }
12786
12787 pub fn context_menu_first(
12788 &mut self,
12789 _: &ContextMenuFirst,
12790 window: &mut Window,
12791 cx: &mut Context<Self>,
12792 ) {
12793 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12794 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12795 }
12796 }
12797
12798 pub fn context_menu_prev(
12799 &mut self,
12800 _: &ContextMenuPrevious,
12801 window: &mut Window,
12802 cx: &mut Context<Self>,
12803 ) {
12804 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12805 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12806 }
12807 }
12808
12809 pub fn context_menu_next(
12810 &mut self,
12811 _: &ContextMenuNext,
12812 window: &mut Window,
12813 cx: &mut Context<Self>,
12814 ) {
12815 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12816 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12817 }
12818 }
12819
12820 pub fn context_menu_last(
12821 &mut self,
12822 _: &ContextMenuLast,
12823 window: &mut Window,
12824 cx: &mut Context<Self>,
12825 ) {
12826 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12827 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12828 }
12829 }
12830
12831 pub fn signature_help_prev(
12832 &mut self,
12833 _: &SignatureHelpPrevious,
12834 _: &mut Window,
12835 cx: &mut Context<Self>,
12836 ) {
12837 if let Some(popover) = self.signature_help_state.popover_mut() {
12838 if popover.current_signature == 0 {
12839 popover.current_signature = popover.signatures.len() - 1;
12840 } else {
12841 popover.current_signature -= 1;
12842 }
12843 cx.notify();
12844 }
12845 }
12846
12847 pub fn signature_help_next(
12848 &mut self,
12849 _: &SignatureHelpNext,
12850 _: &mut Window,
12851 cx: &mut Context<Self>,
12852 ) {
12853 if let Some(popover) = self.signature_help_state.popover_mut() {
12854 if popover.current_signature + 1 == popover.signatures.len() {
12855 popover.current_signature = 0;
12856 } else {
12857 popover.current_signature += 1;
12858 }
12859 cx.notify();
12860 }
12861 }
12862
12863 pub fn move_to_previous_word_start(
12864 &mut self,
12865 _: &MoveToPreviousWordStart,
12866 window: &mut Window,
12867 cx: &mut Context<Self>,
12868 ) {
12869 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12870 self.change_selections(Default::default(), window, cx, |s| {
12871 s.move_cursors_with(|map, head, _| {
12872 (
12873 movement::previous_word_start(map, head),
12874 SelectionGoal::None,
12875 )
12876 });
12877 })
12878 }
12879
12880 pub fn move_to_previous_subword_start(
12881 &mut self,
12882 _: &MoveToPreviousSubwordStart,
12883 window: &mut Window,
12884 cx: &mut Context<Self>,
12885 ) {
12886 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12887 self.change_selections(Default::default(), window, cx, |s| {
12888 s.move_cursors_with(|map, head, _| {
12889 (
12890 movement::previous_subword_start(map, head),
12891 SelectionGoal::None,
12892 )
12893 });
12894 })
12895 }
12896
12897 pub fn select_to_previous_word_start(
12898 &mut self,
12899 _: &SelectToPreviousWordStart,
12900 window: &mut Window,
12901 cx: &mut Context<Self>,
12902 ) {
12903 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12904 self.change_selections(Default::default(), window, cx, |s| {
12905 s.move_heads_with(|map, head, _| {
12906 (
12907 movement::previous_word_start(map, head),
12908 SelectionGoal::None,
12909 )
12910 });
12911 })
12912 }
12913
12914 pub fn select_to_previous_subword_start(
12915 &mut self,
12916 _: &SelectToPreviousSubwordStart,
12917 window: &mut Window,
12918 cx: &mut Context<Self>,
12919 ) {
12920 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12921 self.change_selections(Default::default(), window, cx, |s| {
12922 s.move_heads_with(|map, head, _| {
12923 (
12924 movement::previous_subword_start(map, head),
12925 SelectionGoal::None,
12926 )
12927 });
12928 })
12929 }
12930
12931 pub fn delete_to_previous_word_start(
12932 &mut self,
12933 action: &DeleteToPreviousWordStart,
12934 window: &mut Window,
12935 cx: &mut Context<Self>,
12936 ) {
12937 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12938 self.transact(window, cx, |this, window, cx| {
12939 this.select_autoclose_pair(window, cx);
12940 this.change_selections(Default::default(), window, cx, |s| {
12941 s.move_with(|map, selection| {
12942 if selection.is_empty() {
12943 let cursor = if action.ignore_newlines {
12944 movement::previous_word_start(map, selection.head())
12945 } else {
12946 movement::previous_word_start_or_newline(map, selection.head())
12947 };
12948 selection.set_head(cursor, SelectionGoal::None);
12949 }
12950 });
12951 });
12952 this.insert("", window, cx);
12953 });
12954 }
12955
12956 pub fn delete_to_previous_subword_start(
12957 &mut self,
12958 _: &DeleteToPreviousSubwordStart,
12959 window: &mut Window,
12960 cx: &mut Context<Self>,
12961 ) {
12962 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12963 self.transact(window, cx, |this, window, cx| {
12964 this.select_autoclose_pair(window, cx);
12965 this.change_selections(Default::default(), window, cx, |s| {
12966 s.move_with(|map, selection| {
12967 if selection.is_empty() {
12968 let cursor = movement::previous_subword_start(map, selection.head());
12969 selection.set_head(cursor, SelectionGoal::None);
12970 }
12971 });
12972 });
12973 this.insert("", window, cx);
12974 });
12975 }
12976
12977 pub fn move_to_next_word_end(
12978 &mut self,
12979 _: &MoveToNextWordEnd,
12980 window: &mut Window,
12981 cx: &mut Context<Self>,
12982 ) {
12983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12984 self.change_selections(Default::default(), window, cx, |s| {
12985 s.move_cursors_with(|map, head, _| {
12986 (movement::next_word_end(map, head), SelectionGoal::None)
12987 });
12988 })
12989 }
12990
12991 pub fn move_to_next_subword_end(
12992 &mut self,
12993 _: &MoveToNextSubwordEnd,
12994 window: &mut Window,
12995 cx: &mut Context<Self>,
12996 ) {
12997 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12998 self.change_selections(Default::default(), window, cx, |s| {
12999 s.move_cursors_with(|map, head, _| {
13000 (movement::next_subword_end(map, head), SelectionGoal::None)
13001 });
13002 })
13003 }
13004
13005 pub fn select_to_next_word_end(
13006 &mut self,
13007 _: &SelectToNextWordEnd,
13008 window: &mut Window,
13009 cx: &mut Context<Self>,
13010 ) {
13011 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13012 self.change_selections(Default::default(), window, cx, |s| {
13013 s.move_heads_with(|map, head, _| {
13014 (movement::next_word_end(map, head), SelectionGoal::None)
13015 });
13016 })
13017 }
13018
13019 pub fn select_to_next_subword_end(
13020 &mut self,
13021 _: &SelectToNextSubwordEnd,
13022 window: &mut Window,
13023 cx: &mut Context<Self>,
13024 ) {
13025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13026 self.change_selections(Default::default(), window, cx, |s| {
13027 s.move_heads_with(|map, head, _| {
13028 (movement::next_subword_end(map, head), SelectionGoal::None)
13029 });
13030 })
13031 }
13032
13033 pub fn delete_to_next_word_end(
13034 &mut self,
13035 action: &DeleteToNextWordEnd,
13036 window: &mut Window,
13037 cx: &mut Context<Self>,
13038 ) {
13039 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13040 self.transact(window, cx, |this, window, cx| {
13041 this.change_selections(Default::default(), window, cx, |s| {
13042 s.move_with(|map, selection| {
13043 if selection.is_empty() {
13044 let cursor = if action.ignore_newlines {
13045 movement::next_word_end(map, selection.head())
13046 } else {
13047 movement::next_word_end_or_newline(map, selection.head())
13048 };
13049 selection.set_head(cursor, SelectionGoal::None);
13050 }
13051 });
13052 });
13053 this.insert("", window, cx);
13054 });
13055 }
13056
13057 pub fn delete_to_next_subword_end(
13058 &mut self,
13059 _: &DeleteToNextSubwordEnd,
13060 window: &mut Window,
13061 cx: &mut Context<Self>,
13062 ) {
13063 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13064 self.transact(window, cx, |this, window, cx| {
13065 this.change_selections(Default::default(), window, cx, |s| {
13066 s.move_with(|map, selection| {
13067 if selection.is_empty() {
13068 let cursor = movement::next_subword_end(map, selection.head());
13069 selection.set_head(cursor, SelectionGoal::None);
13070 }
13071 });
13072 });
13073 this.insert("", window, cx);
13074 });
13075 }
13076
13077 pub fn move_to_beginning_of_line(
13078 &mut self,
13079 action: &MoveToBeginningOfLine,
13080 window: &mut Window,
13081 cx: &mut Context<Self>,
13082 ) {
13083 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13084 self.change_selections(Default::default(), window, cx, |s| {
13085 s.move_cursors_with(|map, head, _| {
13086 (
13087 movement::indented_line_beginning(
13088 map,
13089 head,
13090 action.stop_at_soft_wraps,
13091 action.stop_at_indent,
13092 ),
13093 SelectionGoal::None,
13094 )
13095 });
13096 })
13097 }
13098
13099 pub fn select_to_beginning_of_line(
13100 &mut self,
13101 action: &SelectToBeginningOfLine,
13102 window: &mut Window,
13103 cx: &mut Context<Self>,
13104 ) {
13105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13106 self.change_selections(Default::default(), window, cx, |s| {
13107 s.move_heads_with(|map, head, _| {
13108 (
13109 movement::indented_line_beginning(
13110 map,
13111 head,
13112 action.stop_at_soft_wraps,
13113 action.stop_at_indent,
13114 ),
13115 SelectionGoal::None,
13116 )
13117 });
13118 });
13119 }
13120
13121 pub fn delete_to_beginning_of_line(
13122 &mut self,
13123 action: &DeleteToBeginningOfLine,
13124 window: &mut Window,
13125 cx: &mut Context<Self>,
13126 ) {
13127 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13128 self.transact(window, cx, |this, window, cx| {
13129 this.change_selections(Default::default(), window, cx, |s| {
13130 s.move_with(|_, selection| {
13131 selection.reversed = true;
13132 });
13133 });
13134
13135 this.select_to_beginning_of_line(
13136 &SelectToBeginningOfLine {
13137 stop_at_soft_wraps: false,
13138 stop_at_indent: action.stop_at_indent,
13139 },
13140 window,
13141 cx,
13142 );
13143 this.backspace(&Backspace, window, cx);
13144 });
13145 }
13146
13147 pub fn move_to_end_of_line(
13148 &mut self,
13149 action: &MoveToEndOfLine,
13150 window: &mut Window,
13151 cx: &mut Context<Self>,
13152 ) {
13153 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13154 self.change_selections(Default::default(), window, cx, |s| {
13155 s.move_cursors_with(|map, head, _| {
13156 (
13157 movement::line_end(map, head, action.stop_at_soft_wraps),
13158 SelectionGoal::None,
13159 )
13160 });
13161 })
13162 }
13163
13164 pub fn select_to_end_of_line(
13165 &mut self,
13166 action: &SelectToEndOfLine,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13171 self.change_selections(Default::default(), window, cx, |s| {
13172 s.move_heads_with(|map, head, _| {
13173 (
13174 movement::line_end(map, head, action.stop_at_soft_wraps),
13175 SelectionGoal::None,
13176 )
13177 });
13178 })
13179 }
13180
13181 pub fn delete_to_end_of_line(
13182 &mut self,
13183 _: &DeleteToEndOfLine,
13184 window: &mut Window,
13185 cx: &mut Context<Self>,
13186 ) {
13187 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13188 self.transact(window, cx, |this, window, cx| {
13189 this.select_to_end_of_line(
13190 &SelectToEndOfLine {
13191 stop_at_soft_wraps: false,
13192 },
13193 window,
13194 cx,
13195 );
13196 this.delete(&Delete, window, cx);
13197 });
13198 }
13199
13200 pub fn cut_to_end_of_line(
13201 &mut self,
13202 _: &CutToEndOfLine,
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.select_to_end_of_line(
13209 &SelectToEndOfLine {
13210 stop_at_soft_wraps: false,
13211 },
13212 window,
13213 cx,
13214 );
13215 this.cut(&Cut, window, cx);
13216 });
13217 }
13218
13219 pub fn move_to_start_of_paragraph(
13220 &mut self,
13221 _: &MoveToStartOfParagraph,
13222 window: &mut Window,
13223 cx: &mut Context<Self>,
13224 ) {
13225 if matches!(self.mode, EditorMode::SingleLine) {
13226 cx.propagate();
13227 return;
13228 }
13229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13230 self.change_selections(Default::default(), window, cx, |s| {
13231 s.move_with(|map, selection| {
13232 selection.collapse_to(
13233 movement::start_of_paragraph(map, selection.head(), 1),
13234 SelectionGoal::None,
13235 )
13236 });
13237 })
13238 }
13239
13240 pub fn move_to_end_of_paragraph(
13241 &mut self,
13242 _: &MoveToEndOfParagraph,
13243 window: &mut Window,
13244 cx: &mut Context<Self>,
13245 ) {
13246 if matches!(self.mode, EditorMode::SingleLine) {
13247 cx.propagate();
13248 return;
13249 }
13250 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13251 self.change_selections(Default::default(), window, cx, |s| {
13252 s.move_with(|map, selection| {
13253 selection.collapse_to(
13254 movement::end_of_paragraph(map, selection.head(), 1),
13255 SelectionGoal::None,
13256 )
13257 });
13258 })
13259 }
13260
13261 pub fn select_to_start_of_paragraph(
13262 &mut self,
13263 _: &SelectToStartOfParagraph,
13264 window: &mut Window,
13265 cx: &mut Context<Self>,
13266 ) {
13267 if matches!(self.mode, EditorMode::SingleLine) {
13268 cx.propagate();
13269 return;
13270 }
13271 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13272 self.change_selections(Default::default(), window, cx, |s| {
13273 s.move_heads_with(|map, head, _| {
13274 (
13275 movement::start_of_paragraph(map, head, 1),
13276 SelectionGoal::None,
13277 )
13278 });
13279 })
13280 }
13281
13282 pub fn select_to_end_of_paragraph(
13283 &mut self,
13284 _: &SelectToEndOfParagraph,
13285 window: &mut Window,
13286 cx: &mut Context<Self>,
13287 ) {
13288 if matches!(self.mode, EditorMode::SingleLine) {
13289 cx.propagate();
13290 return;
13291 }
13292 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13293 self.change_selections(Default::default(), window, cx, |s| {
13294 s.move_heads_with(|map, head, _| {
13295 (
13296 movement::end_of_paragraph(map, head, 1),
13297 SelectionGoal::None,
13298 )
13299 });
13300 })
13301 }
13302
13303 pub fn move_to_start_of_excerpt(
13304 &mut self,
13305 _: &MoveToStartOfExcerpt,
13306 window: &mut Window,
13307 cx: &mut Context<Self>,
13308 ) {
13309 if matches!(self.mode, EditorMode::SingleLine) {
13310 cx.propagate();
13311 return;
13312 }
13313 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13314 self.change_selections(Default::default(), window, cx, |s| {
13315 s.move_with(|map, selection| {
13316 selection.collapse_to(
13317 movement::start_of_excerpt(
13318 map,
13319 selection.head(),
13320 workspace::searchable::Direction::Prev,
13321 ),
13322 SelectionGoal::None,
13323 )
13324 });
13325 })
13326 }
13327
13328 pub fn move_to_start_of_next_excerpt(
13329 &mut self,
13330 _: &MoveToStartOfNextExcerpt,
13331 window: &mut Window,
13332 cx: &mut Context<Self>,
13333 ) {
13334 if matches!(self.mode, EditorMode::SingleLine) {
13335 cx.propagate();
13336 return;
13337 }
13338
13339 self.change_selections(Default::default(), window, cx, |s| {
13340 s.move_with(|map, selection| {
13341 selection.collapse_to(
13342 movement::start_of_excerpt(
13343 map,
13344 selection.head(),
13345 workspace::searchable::Direction::Next,
13346 ),
13347 SelectionGoal::None,
13348 )
13349 });
13350 })
13351 }
13352
13353 pub fn move_to_end_of_excerpt(
13354 &mut self,
13355 _: &MoveToEndOfExcerpt,
13356 window: &mut Window,
13357 cx: &mut Context<Self>,
13358 ) {
13359 if matches!(self.mode, EditorMode::SingleLine) {
13360 cx.propagate();
13361 return;
13362 }
13363 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13364 self.change_selections(Default::default(), window, cx, |s| {
13365 s.move_with(|map, selection| {
13366 selection.collapse_to(
13367 movement::end_of_excerpt(
13368 map,
13369 selection.head(),
13370 workspace::searchable::Direction::Next,
13371 ),
13372 SelectionGoal::None,
13373 )
13374 });
13375 })
13376 }
13377
13378 pub fn move_to_end_of_previous_excerpt(
13379 &mut self,
13380 _: &MoveToEndOfPreviousExcerpt,
13381 window: &mut Window,
13382 cx: &mut Context<Self>,
13383 ) {
13384 if matches!(self.mode, EditorMode::SingleLine) {
13385 cx.propagate();
13386 return;
13387 }
13388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13389 self.change_selections(Default::default(), window, cx, |s| {
13390 s.move_with(|map, selection| {
13391 selection.collapse_to(
13392 movement::end_of_excerpt(
13393 map,
13394 selection.head(),
13395 workspace::searchable::Direction::Prev,
13396 ),
13397 SelectionGoal::None,
13398 )
13399 });
13400 })
13401 }
13402
13403 pub fn select_to_start_of_excerpt(
13404 &mut self,
13405 _: &SelectToStartOfExcerpt,
13406 window: &mut Window,
13407 cx: &mut Context<Self>,
13408 ) {
13409 if matches!(self.mode, EditorMode::SingleLine) {
13410 cx.propagate();
13411 return;
13412 }
13413 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13414 self.change_selections(Default::default(), window, cx, |s| {
13415 s.move_heads_with(|map, head, _| {
13416 (
13417 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13418 SelectionGoal::None,
13419 )
13420 });
13421 })
13422 }
13423
13424 pub fn select_to_start_of_next_excerpt(
13425 &mut self,
13426 _: &SelectToStartOfNextExcerpt,
13427 window: &mut Window,
13428 cx: &mut Context<Self>,
13429 ) {
13430 if matches!(self.mode, EditorMode::SingleLine) {
13431 cx.propagate();
13432 return;
13433 }
13434 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13435 self.change_selections(Default::default(), window, cx, |s| {
13436 s.move_heads_with(|map, head, _| {
13437 (
13438 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13439 SelectionGoal::None,
13440 )
13441 });
13442 })
13443 }
13444
13445 pub fn select_to_end_of_excerpt(
13446 &mut self,
13447 _: &SelectToEndOfExcerpt,
13448 window: &mut Window,
13449 cx: &mut Context<Self>,
13450 ) {
13451 if matches!(self.mode, EditorMode::SingleLine) {
13452 cx.propagate();
13453 return;
13454 }
13455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13456 self.change_selections(Default::default(), window, cx, |s| {
13457 s.move_heads_with(|map, head, _| {
13458 (
13459 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13460 SelectionGoal::None,
13461 )
13462 });
13463 })
13464 }
13465
13466 pub fn select_to_end_of_previous_excerpt(
13467 &mut self,
13468 _: &SelectToEndOfPreviousExcerpt,
13469 window: &mut Window,
13470 cx: &mut Context<Self>,
13471 ) {
13472 if matches!(self.mode, EditorMode::SingleLine) {
13473 cx.propagate();
13474 return;
13475 }
13476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13477 self.change_selections(Default::default(), window, cx, |s| {
13478 s.move_heads_with(|map, head, _| {
13479 (
13480 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13481 SelectionGoal::None,
13482 )
13483 });
13484 })
13485 }
13486
13487 pub fn move_to_beginning(
13488 &mut self,
13489 _: &MoveToBeginning,
13490 window: &mut Window,
13491 cx: &mut Context<Self>,
13492 ) {
13493 if matches!(self.mode, EditorMode::SingleLine) {
13494 cx.propagate();
13495 return;
13496 }
13497 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13498 self.change_selections(Default::default(), window, cx, |s| {
13499 s.select_ranges(vec![0..0]);
13500 });
13501 }
13502
13503 pub fn select_to_beginning(
13504 &mut self,
13505 _: &SelectToBeginning,
13506 window: &mut Window,
13507 cx: &mut Context<Self>,
13508 ) {
13509 let mut selection = self.selections.last::<Point>(cx);
13510 selection.set_head(Point::zero(), SelectionGoal::None);
13511 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13512 self.change_selections(Default::default(), window, cx, |s| {
13513 s.select(vec![selection]);
13514 });
13515 }
13516
13517 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13518 if matches!(self.mode, EditorMode::SingleLine) {
13519 cx.propagate();
13520 return;
13521 }
13522 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13523 let cursor = self.buffer.read(cx).read(cx).len();
13524 self.change_selections(Default::default(), window, cx, |s| {
13525 s.select_ranges(vec![cursor..cursor])
13526 });
13527 }
13528
13529 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13530 self.nav_history = nav_history;
13531 }
13532
13533 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13534 self.nav_history.as_ref()
13535 }
13536
13537 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13538 self.push_to_nav_history(
13539 self.selections.newest_anchor().head(),
13540 None,
13541 false,
13542 true,
13543 cx,
13544 );
13545 }
13546
13547 fn push_to_nav_history(
13548 &mut self,
13549 cursor_anchor: Anchor,
13550 new_position: Option<Point>,
13551 is_deactivate: bool,
13552 always: bool,
13553 cx: &mut Context<Self>,
13554 ) {
13555 if let Some(nav_history) = self.nav_history.as_mut() {
13556 let buffer = self.buffer.read(cx).read(cx);
13557 let cursor_position = cursor_anchor.to_point(&buffer);
13558 let scroll_state = self.scroll_manager.anchor();
13559 let scroll_top_row = scroll_state.top_row(&buffer);
13560 drop(buffer);
13561
13562 if let Some(new_position) = new_position {
13563 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13564 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13565 return;
13566 }
13567 }
13568
13569 nav_history.push(
13570 Some(NavigationData {
13571 cursor_anchor,
13572 cursor_position,
13573 scroll_anchor: scroll_state,
13574 scroll_top_row,
13575 }),
13576 cx,
13577 );
13578 cx.emit(EditorEvent::PushedToNavHistory {
13579 anchor: cursor_anchor,
13580 is_deactivate,
13581 })
13582 }
13583 }
13584
13585 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13587 let buffer = self.buffer.read(cx).snapshot(cx);
13588 let mut selection = self.selections.first::<usize>(cx);
13589 selection.set_head(buffer.len(), SelectionGoal::None);
13590 self.change_selections(Default::default(), window, cx, |s| {
13591 s.select(vec![selection]);
13592 });
13593 }
13594
13595 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13596 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13597 let end = self.buffer.read(cx).read(cx).len();
13598 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13599 s.select_ranges(vec![0..end]);
13600 });
13601 }
13602
13603 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13606 let mut selections = self.selections.all::<Point>(cx);
13607 let max_point = display_map.buffer_snapshot.max_point();
13608 for selection in &mut selections {
13609 let rows = selection.spanned_rows(true, &display_map);
13610 selection.start = Point::new(rows.start.0, 0);
13611 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13612 selection.reversed = false;
13613 }
13614 self.change_selections(Default::default(), window, cx, |s| {
13615 s.select(selections);
13616 });
13617 }
13618
13619 pub fn split_selection_into_lines(
13620 &mut self,
13621 action: &SplitSelectionIntoLines,
13622 window: &mut Window,
13623 cx: &mut Context<Self>,
13624 ) {
13625 let selections = self
13626 .selections
13627 .all::<Point>(cx)
13628 .into_iter()
13629 .map(|selection| selection.start..selection.end)
13630 .collect::<Vec<_>>();
13631 self.unfold_ranges(&selections, true, true, cx);
13632
13633 let mut new_selection_ranges = Vec::new();
13634 {
13635 let buffer = self.buffer.read(cx).read(cx);
13636 for selection in selections {
13637 for row in selection.start.row..selection.end.row {
13638 let line_start = Point::new(row, 0);
13639 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13640
13641 if action.keep_selections {
13642 // Keep the selection range for each line
13643 let selection_start = if row == selection.start.row {
13644 selection.start
13645 } else {
13646 line_start
13647 };
13648 new_selection_ranges.push(selection_start..line_end);
13649 } else {
13650 // Collapse to cursor at end of line
13651 new_selection_ranges.push(line_end..line_end);
13652 }
13653 }
13654
13655 let is_multiline_selection = selection.start.row != selection.end.row;
13656 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13657 // so this action feels more ergonomic when paired with other selection operations
13658 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13659 if !should_skip_last {
13660 if action.keep_selections {
13661 if is_multiline_selection {
13662 let line_start = Point::new(selection.end.row, 0);
13663 new_selection_ranges.push(line_start..selection.end);
13664 } else {
13665 new_selection_ranges.push(selection.start..selection.end);
13666 }
13667 } else {
13668 new_selection_ranges.push(selection.end..selection.end);
13669 }
13670 }
13671 }
13672 }
13673 self.change_selections(Default::default(), window, cx, |s| {
13674 s.select_ranges(new_selection_ranges);
13675 });
13676 }
13677
13678 pub fn add_selection_above(
13679 &mut self,
13680 _: &AddSelectionAbove,
13681 window: &mut Window,
13682 cx: &mut Context<Self>,
13683 ) {
13684 self.add_selection(true, window, cx);
13685 }
13686
13687 pub fn add_selection_below(
13688 &mut self,
13689 _: &AddSelectionBelow,
13690 window: &mut Window,
13691 cx: &mut Context<Self>,
13692 ) {
13693 self.add_selection(false, window, cx);
13694 }
13695
13696 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13697 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13698
13699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13700 let all_selections = self.selections.all::<Point>(cx);
13701 let text_layout_details = self.text_layout_details(window);
13702
13703 let (mut columnar_selections, new_selections_to_columnarize) = {
13704 if let Some(state) = self.add_selections_state.as_ref() {
13705 let columnar_selection_ids: HashSet<_> = state
13706 .groups
13707 .iter()
13708 .flat_map(|group| group.stack.iter())
13709 .copied()
13710 .collect();
13711
13712 all_selections
13713 .into_iter()
13714 .partition(|s| columnar_selection_ids.contains(&s.id))
13715 } else {
13716 (Vec::new(), all_selections)
13717 }
13718 };
13719
13720 let mut state = self
13721 .add_selections_state
13722 .take()
13723 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13724
13725 for selection in new_selections_to_columnarize {
13726 let range = selection.display_range(&display_map).sorted();
13727 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13728 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13729 let positions = start_x.min(end_x)..start_x.max(end_x);
13730 let mut stack = Vec::new();
13731 for row in range.start.row().0..=range.end.row().0 {
13732 if let Some(selection) = self.selections.build_columnar_selection(
13733 &display_map,
13734 DisplayRow(row),
13735 &positions,
13736 selection.reversed,
13737 &text_layout_details,
13738 ) {
13739 stack.push(selection.id);
13740 columnar_selections.push(selection);
13741 }
13742 }
13743 if !stack.is_empty() {
13744 if above {
13745 stack.reverse();
13746 }
13747 state.groups.push(AddSelectionsGroup { above, stack });
13748 }
13749 }
13750
13751 let mut final_selections = Vec::new();
13752 let end_row = if above {
13753 DisplayRow(0)
13754 } else {
13755 display_map.max_point().row()
13756 };
13757
13758 let mut last_added_item_per_group = HashMap::default();
13759 for group in state.groups.iter_mut() {
13760 if let Some(last_id) = group.stack.last() {
13761 last_added_item_per_group.insert(*last_id, group);
13762 }
13763 }
13764
13765 for selection in columnar_selections {
13766 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13767 if above == group.above {
13768 let range = selection.display_range(&display_map).sorted();
13769 debug_assert_eq!(range.start.row(), range.end.row());
13770 let mut row = range.start.row();
13771 let positions =
13772 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13773 px(start)..px(end)
13774 } else {
13775 let start_x =
13776 display_map.x_for_display_point(range.start, &text_layout_details);
13777 let end_x =
13778 display_map.x_for_display_point(range.end, &text_layout_details);
13779 start_x.min(end_x)..start_x.max(end_x)
13780 };
13781
13782 let mut maybe_new_selection = None;
13783 while row != end_row {
13784 if above {
13785 row.0 -= 1;
13786 } else {
13787 row.0 += 1;
13788 }
13789 if let Some(new_selection) = self.selections.build_columnar_selection(
13790 &display_map,
13791 row,
13792 &positions,
13793 selection.reversed,
13794 &text_layout_details,
13795 ) {
13796 maybe_new_selection = Some(new_selection);
13797 break;
13798 }
13799 }
13800
13801 if let Some(new_selection) = maybe_new_selection {
13802 group.stack.push(new_selection.id);
13803 if above {
13804 final_selections.push(new_selection);
13805 final_selections.push(selection);
13806 } else {
13807 final_selections.push(selection);
13808 final_selections.push(new_selection);
13809 }
13810 } else {
13811 final_selections.push(selection);
13812 }
13813 } else {
13814 group.stack.pop();
13815 }
13816 } else {
13817 final_selections.push(selection);
13818 }
13819 }
13820
13821 self.change_selections(Default::default(), window, cx, |s| {
13822 s.select(final_selections);
13823 });
13824
13825 let final_selection_ids: HashSet<_> = self
13826 .selections
13827 .all::<Point>(cx)
13828 .iter()
13829 .map(|s| s.id)
13830 .collect();
13831 state.groups.retain_mut(|group| {
13832 // selections might get merged above so we remove invalid items from stacks
13833 group.stack.retain(|id| final_selection_ids.contains(id));
13834
13835 // single selection in stack can be treated as initial state
13836 group.stack.len() > 1
13837 });
13838
13839 if !state.groups.is_empty() {
13840 self.add_selections_state = Some(state);
13841 }
13842 }
13843
13844 fn select_match_ranges(
13845 &mut self,
13846 range: Range<usize>,
13847 reversed: bool,
13848 replace_newest: bool,
13849 auto_scroll: Option<Autoscroll>,
13850 window: &mut Window,
13851 cx: &mut Context<Editor>,
13852 ) {
13853 self.unfold_ranges(
13854 std::slice::from_ref(&range),
13855 false,
13856 auto_scroll.is_some(),
13857 cx,
13858 );
13859 let effects = if let Some(scroll) = auto_scroll {
13860 SelectionEffects::scroll(scroll)
13861 } else {
13862 SelectionEffects::no_scroll()
13863 };
13864 self.change_selections(effects, window, cx, |s| {
13865 if replace_newest {
13866 s.delete(s.newest_anchor().id);
13867 }
13868 if reversed {
13869 s.insert_range(range.end..range.start);
13870 } else {
13871 s.insert_range(range);
13872 }
13873 });
13874 }
13875
13876 pub fn select_next_match_internal(
13877 &mut self,
13878 display_map: &DisplaySnapshot,
13879 replace_newest: bool,
13880 autoscroll: Option<Autoscroll>,
13881 window: &mut Window,
13882 cx: &mut Context<Self>,
13883 ) -> Result<()> {
13884 let buffer = &display_map.buffer_snapshot;
13885 let mut selections = self.selections.all::<usize>(cx);
13886 if let Some(mut select_next_state) = self.select_next_state.take() {
13887 let query = &select_next_state.query;
13888 if !select_next_state.done {
13889 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13890 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13891 let mut next_selected_range = None;
13892
13893 let bytes_after_last_selection =
13894 buffer.bytes_in_range(last_selection.end..buffer.len());
13895 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13896 let query_matches = query
13897 .stream_find_iter(bytes_after_last_selection)
13898 .map(|result| (last_selection.end, result))
13899 .chain(
13900 query
13901 .stream_find_iter(bytes_before_first_selection)
13902 .map(|result| (0, result)),
13903 );
13904
13905 for (start_offset, query_match) in query_matches {
13906 let query_match = query_match.unwrap(); // can only fail due to I/O
13907 let offset_range =
13908 start_offset + query_match.start()..start_offset + query_match.end();
13909
13910 if !select_next_state.wordwise
13911 || (!buffer.is_inside_word(offset_range.start, false)
13912 && !buffer.is_inside_word(offset_range.end, false))
13913 {
13914 // TODO: This is n^2, because we might check all the selections
13915 if !selections
13916 .iter()
13917 .any(|selection| selection.range().overlaps(&offset_range))
13918 {
13919 next_selected_range = Some(offset_range);
13920 break;
13921 }
13922 }
13923 }
13924
13925 if let Some(next_selected_range) = next_selected_range {
13926 self.select_match_ranges(
13927 next_selected_range,
13928 last_selection.reversed,
13929 replace_newest,
13930 autoscroll,
13931 window,
13932 cx,
13933 );
13934 } else {
13935 select_next_state.done = true;
13936 }
13937 }
13938
13939 self.select_next_state = Some(select_next_state);
13940 } else {
13941 let mut only_carets = true;
13942 let mut same_text_selected = true;
13943 let mut selected_text = None;
13944
13945 let mut selections_iter = selections.iter().peekable();
13946 while let Some(selection) = selections_iter.next() {
13947 if selection.start != selection.end {
13948 only_carets = false;
13949 }
13950
13951 if same_text_selected {
13952 if selected_text.is_none() {
13953 selected_text =
13954 Some(buffer.text_for_range(selection.range()).collect::<String>());
13955 }
13956
13957 if let Some(next_selection) = selections_iter.peek() {
13958 if next_selection.range().len() == selection.range().len() {
13959 let next_selected_text = buffer
13960 .text_for_range(next_selection.range())
13961 .collect::<String>();
13962 if Some(next_selected_text) != selected_text {
13963 same_text_selected = false;
13964 selected_text = None;
13965 }
13966 } else {
13967 same_text_selected = false;
13968 selected_text = None;
13969 }
13970 }
13971 }
13972 }
13973
13974 if only_carets {
13975 for selection in &mut selections {
13976 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13977 selection.start = word_range.start;
13978 selection.end = word_range.end;
13979 selection.goal = SelectionGoal::None;
13980 selection.reversed = false;
13981 self.select_match_ranges(
13982 selection.start..selection.end,
13983 selection.reversed,
13984 replace_newest,
13985 autoscroll,
13986 window,
13987 cx,
13988 );
13989 }
13990
13991 if selections.len() == 1 {
13992 let selection = selections
13993 .last()
13994 .expect("ensured that there's only one selection");
13995 let query = buffer
13996 .text_for_range(selection.start..selection.end)
13997 .collect::<String>();
13998 let is_empty = query.is_empty();
13999 let select_state = SelectNextState {
14000 query: AhoCorasick::new(&[query])?,
14001 wordwise: true,
14002 done: is_empty,
14003 };
14004 self.select_next_state = Some(select_state);
14005 } else {
14006 self.select_next_state = None;
14007 }
14008 } else if let Some(selected_text) = selected_text {
14009 self.select_next_state = Some(SelectNextState {
14010 query: AhoCorasick::new(&[selected_text])?,
14011 wordwise: false,
14012 done: false,
14013 });
14014 self.select_next_match_internal(
14015 display_map,
14016 replace_newest,
14017 autoscroll,
14018 window,
14019 cx,
14020 )?;
14021 }
14022 }
14023 Ok(())
14024 }
14025
14026 pub fn select_all_matches(
14027 &mut self,
14028 _action: &SelectAllMatches,
14029 window: &mut Window,
14030 cx: &mut Context<Self>,
14031 ) -> Result<()> {
14032 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14033
14034 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14035
14036 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14037 let Some(select_next_state) = self.select_next_state.as_mut() else {
14038 return Ok(());
14039 };
14040 if select_next_state.done {
14041 return Ok(());
14042 }
14043
14044 let mut new_selections = Vec::new();
14045
14046 let reversed = self.selections.oldest::<usize>(cx).reversed;
14047 let buffer = &display_map.buffer_snapshot;
14048 let query_matches = select_next_state
14049 .query
14050 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14051
14052 for query_match in query_matches.into_iter() {
14053 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14054 let offset_range = if reversed {
14055 query_match.end()..query_match.start()
14056 } else {
14057 query_match.start()..query_match.end()
14058 };
14059
14060 if !select_next_state.wordwise
14061 || (!buffer.is_inside_word(offset_range.start, false)
14062 && !buffer.is_inside_word(offset_range.end, false))
14063 {
14064 new_selections.push(offset_range.start..offset_range.end);
14065 }
14066 }
14067
14068 select_next_state.done = true;
14069
14070 if new_selections.is_empty() {
14071 log::error!("bug: new_selections is empty in select_all_matches");
14072 return Ok(());
14073 }
14074
14075 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14076 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14077 selections.select_ranges(new_selections)
14078 });
14079
14080 Ok(())
14081 }
14082
14083 pub fn select_next(
14084 &mut self,
14085 action: &SelectNext,
14086 window: &mut Window,
14087 cx: &mut Context<Self>,
14088 ) -> Result<()> {
14089 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14090 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14091 self.select_next_match_internal(
14092 &display_map,
14093 action.replace_newest,
14094 Some(Autoscroll::newest()),
14095 window,
14096 cx,
14097 )?;
14098 Ok(())
14099 }
14100
14101 pub fn select_previous(
14102 &mut self,
14103 action: &SelectPrevious,
14104 window: &mut Window,
14105 cx: &mut Context<Self>,
14106 ) -> Result<()> {
14107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14108 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14109 let buffer = &display_map.buffer_snapshot;
14110 let mut selections = self.selections.all::<usize>(cx);
14111 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14112 let query = &select_prev_state.query;
14113 if !select_prev_state.done {
14114 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14115 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14116 let mut next_selected_range = None;
14117 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14118 let bytes_before_last_selection =
14119 buffer.reversed_bytes_in_range(0..last_selection.start);
14120 let bytes_after_first_selection =
14121 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14122 let query_matches = query
14123 .stream_find_iter(bytes_before_last_selection)
14124 .map(|result| (last_selection.start, result))
14125 .chain(
14126 query
14127 .stream_find_iter(bytes_after_first_selection)
14128 .map(|result| (buffer.len(), result)),
14129 );
14130 for (end_offset, query_match) in query_matches {
14131 let query_match = query_match.unwrap(); // can only fail due to I/O
14132 let offset_range =
14133 end_offset - query_match.end()..end_offset - query_match.start();
14134
14135 if !select_prev_state.wordwise
14136 || (!buffer.is_inside_word(offset_range.start, false)
14137 && !buffer.is_inside_word(offset_range.end, false))
14138 {
14139 next_selected_range = Some(offset_range);
14140 break;
14141 }
14142 }
14143
14144 if let Some(next_selected_range) = next_selected_range {
14145 self.select_match_ranges(
14146 next_selected_range,
14147 last_selection.reversed,
14148 action.replace_newest,
14149 Some(Autoscroll::newest()),
14150 window,
14151 cx,
14152 );
14153 } else {
14154 select_prev_state.done = true;
14155 }
14156 }
14157
14158 self.select_prev_state = Some(select_prev_state);
14159 } else {
14160 let mut only_carets = true;
14161 let mut same_text_selected = true;
14162 let mut selected_text = None;
14163
14164 let mut selections_iter = selections.iter().peekable();
14165 while let Some(selection) = selections_iter.next() {
14166 if selection.start != selection.end {
14167 only_carets = false;
14168 }
14169
14170 if same_text_selected {
14171 if selected_text.is_none() {
14172 selected_text =
14173 Some(buffer.text_for_range(selection.range()).collect::<String>());
14174 }
14175
14176 if let Some(next_selection) = selections_iter.peek() {
14177 if next_selection.range().len() == selection.range().len() {
14178 let next_selected_text = buffer
14179 .text_for_range(next_selection.range())
14180 .collect::<String>();
14181 if Some(next_selected_text) != selected_text {
14182 same_text_selected = false;
14183 selected_text = None;
14184 }
14185 } else {
14186 same_text_selected = false;
14187 selected_text = None;
14188 }
14189 }
14190 }
14191 }
14192
14193 if only_carets {
14194 for selection in &mut selections {
14195 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14196 selection.start = word_range.start;
14197 selection.end = word_range.end;
14198 selection.goal = SelectionGoal::None;
14199 selection.reversed = false;
14200 self.select_match_ranges(
14201 selection.start..selection.end,
14202 selection.reversed,
14203 action.replace_newest,
14204 Some(Autoscroll::newest()),
14205 window,
14206 cx,
14207 );
14208 }
14209 if selections.len() == 1 {
14210 let selection = selections
14211 .last()
14212 .expect("ensured that there's only one selection");
14213 let query = buffer
14214 .text_for_range(selection.start..selection.end)
14215 .collect::<String>();
14216 let is_empty = query.is_empty();
14217 let select_state = SelectNextState {
14218 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14219 wordwise: true,
14220 done: is_empty,
14221 };
14222 self.select_prev_state = Some(select_state);
14223 } else {
14224 self.select_prev_state = None;
14225 }
14226 } else if let Some(selected_text) = selected_text {
14227 self.select_prev_state = Some(SelectNextState {
14228 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14229 wordwise: false,
14230 done: false,
14231 });
14232 self.select_previous(action, window, cx)?;
14233 }
14234 }
14235 Ok(())
14236 }
14237
14238 pub fn find_next_match(
14239 &mut self,
14240 _: &FindNextMatch,
14241 window: &mut Window,
14242 cx: &mut Context<Self>,
14243 ) -> Result<()> {
14244 let selections = self.selections.disjoint_anchors();
14245 match selections.first() {
14246 Some(first) if selections.len() >= 2 => {
14247 self.change_selections(Default::default(), window, cx, |s| {
14248 s.select_ranges([first.range()]);
14249 });
14250 }
14251 _ => self.select_next(
14252 &SelectNext {
14253 replace_newest: true,
14254 },
14255 window,
14256 cx,
14257 )?,
14258 }
14259 Ok(())
14260 }
14261
14262 pub fn find_previous_match(
14263 &mut self,
14264 _: &FindPreviousMatch,
14265 window: &mut Window,
14266 cx: &mut Context<Self>,
14267 ) -> Result<()> {
14268 let selections = self.selections.disjoint_anchors();
14269 match selections.last() {
14270 Some(last) if selections.len() >= 2 => {
14271 self.change_selections(Default::default(), window, cx, |s| {
14272 s.select_ranges([last.range()]);
14273 });
14274 }
14275 _ => self.select_previous(
14276 &SelectPrevious {
14277 replace_newest: true,
14278 },
14279 window,
14280 cx,
14281 )?,
14282 }
14283 Ok(())
14284 }
14285
14286 pub fn toggle_comments(
14287 &mut self,
14288 action: &ToggleComments,
14289 window: &mut Window,
14290 cx: &mut Context<Self>,
14291 ) {
14292 if self.read_only(cx) {
14293 return;
14294 }
14295 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14296 let text_layout_details = &self.text_layout_details(window);
14297 self.transact(window, cx, |this, window, cx| {
14298 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14299 let mut edits = Vec::new();
14300 let mut selection_edit_ranges = Vec::new();
14301 let mut last_toggled_row = None;
14302 let snapshot = this.buffer.read(cx).read(cx);
14303 let empty_str: Arc<str> = Arc::default();
14304 let mut suffixes_inserted = Vec::new();
14305 let ignore_indent = action.ignore_indent;
14306
14307 fn comment_prefix_range(
14308 snapshot: &MultiBufferSnapshot,
14309 row: MultiBufferRow,
14310 comment_prefix: &str,
14311 comment_prefix_whitespace: &str,
14312 ignore_indent: bool,
14313 ) -> Range<Point> {
14314 let indent_size = if ignore_indent {
14315 0
14316 } else {
14317 snapshot.indent_size_for_line(row).len
14318 };
14319
14320 let start = Point::new(row.0, indent_size);
14321
14322 let mut line_bytes = snapshot
14323 .bytes_in_range(start..snapshot.max_point())
14324 .flatten()
14325 .copied();
14326
14327 // If this line currently begins with the line comment prefix, then record
14328 // the range containing the prefix.
14329 if line_bytes
14330 .by_ref()
14331 .take(comment_prefix.len())
14332 .eq(comment_prefix.bytes())
14333 {
14334 // Include any whitespace that matches the comment prefix.
14335 let matching_whitespace_len = line_bytes
14336 .zip(comment_prefix_whitespace.bytes())
14337 .take_while(|(a, b)| a == b)
14338 .count() as u32;
14339 let end = Point::new(
14340 start.row,
14341 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14342 );
14343 start..end
14344 } else {
14345 start..start
14346 }
14347 }
14348
14349 fn comment_suffix_range(
14350 snapshot: &MultiBufferSnapshot,
14351 row: MultiBufferRow,
14352 comment_suffix: &str,
14353 comment_suffix_has_leading_space: bool,
14354 ) -> Range<Point> {
14355 let end = Point::new(row.0, snapshot.line_len(row));
14356 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14357
14358 let mut line_end_bytes = snapshot
14359 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14360 .flatten()
14361 .copied();
14362
14363 let leading_space_len = if suffix_start_column > 0
14364 && line_end_bytes.next() == Some(b' ')
14365 && comment_suffix_has_leading_space
14366 {
14367 1
14368 } else {
14369 0
14370 };
14371
14372 // If this line currently begins with the line comment prefix, then record
14373 // the range containing the prefix.
14374 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14375 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14376 start..end
14377 } else {
14378 end..end
14379 }
14380 }
14381
14382 // TODO: Handle selections that cross excerpts
14383 for selection in &mut selections {
14384 let start_column = snapshot
14385 .indent_size_for_line(MultiBufferRow(selection.start.row))
14386 .len;
14387 let language = if let Some(language) =
14388 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14389 {
14390 language
14391 } else {
14392 continue;
14393 };
14394
14395 selection_edit_ranges.clear();
14396
14397 // If multiple selections contain a given row, avoid processing that
14398 // row more than once.
14399 let mut start_row = MultiBufferRow(selection.start.row);
14400 if last_toggled_row == Some(start_row) {
14401 start_row = start_row.next_row();
14402 }
14403 let end_row =
14404 if selection.end.row > selection.start.row && selection.end.column == 0 {
14405 MultiBufferRow(selection.end.row - 1)
14406 } else {
14407 MultiBufferRow(selection.end.row)
14408 };
14409 last_toggled_row = Some(end_row);
14410
14411 if start_row > end_row {
14412 continue;
14413 }
14414
14415 // If the language has line comments, toggle those.
14416 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14417
14418 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14419 if ignore_indent {
14420 full_comment_prefixes = full_comment_prefixes
14421 .into_iter()
14422 .map(|s| Arc::from(s.trim_end()))
14423 .collect();
14424 }
14425
14426 if !full_comment_prefixes.is_empty() {
14427 let first_prefix = full_comment_prefixes
14428 .first()
14429 .expect("prefixes is non-empty");
14430 let prefix_trimmed_lengths = full_comment_prefixes
14431 .iter()
14432 .map(|p| p.trim_end_matches(' ').len())
14433 .collect::<SmallVec<[usize; 4]>>();
14434
14435 let mut all_selection_lines_are_comments = true;
14436
14437 for row in start_row.0..=end_row.0 {
14438 let row = MultiBufferRow(row);
14439 if start_row < end_row && snapshot.is_line_blank(row) {
14440 continue;
14441 }
14442
14443 let prefix_range = full_comment_prefixes
14444 .iter()
14445 .zip(prefix_trimmed_lengths.iter().copied())
14446 .map(|(prefix, trimmed_prefix_len)| {
14447 comment_prefix_range(
14448 snapshot.deref(),
14449 row,
14450 &prefix[..trimmed_prefix_len],
14451 &prefix[trimmed_prefix_len..],
14452 ignore_indent,
14453 )
14454 })
14455 .max_by_key(|range| range.end.column - range.start.column)
14456 .expect("prefixes is non-empty");
14457
14458 if prefix_range.is_empty() {
14459 all_selection_lines_are_comments = false;
14460 }
14461
14462 selection_edit_ranges.push(prefix_range);
14463 }
14464
14465 if all_selection_lines_are_comments {
14466 edits.extend(
14467 selection_edit_ranges
14468 .iter()
14469 .cloned()
14470 .map(|range| (range, empty_str.clone())),
14471 );
14472 } else {
14473 let min_column = selection_edit_ranges
14474 .iter()
14475 .map(|range| range.start.column)
14476 .min()
14477 .unwrap_or(0);
14478 edits.extend(selection_edit_ranges.iter().map(|range| {
14479 let position = Point::new(range.start.row, min_column);
14480 (position..position, first_prefix.clone())
14481 }));
14482 }
14483 } else if let Some(BlockCommentConfig {
14484 start: full_comment_prefix,
14485 end: comment_suffix,
14486 ..
14487 }) = language.block_comment()
14488 {
14489 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14490 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14491 let prefix_range = comment_prefix_range(
14492 snapshot.deref(),
14493 start_row,
14494 comment_prefix,
14495 comment_prefix_whitespace,
14496 ignore_indent,
14497 );
14498 let suffix_range = comment_suffix_range(
14499 snapshot.deref(),
14500 end_row,
14501 comment_suffix.trim_start_matches(' '),
14502 comment_suffix.starts_with(' '),
14503 );
14504
14505 if prefix_range.is_empty() || suffix_range.is_empty() {
14506 edits.push((
14507 prefix_range.start..prefix_range.start,
14508 full_comment_prefix.clone(),
14509 ));
14510 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14511 suffixes_inserted.push((end_row, comment_suffix.len()));
14512 } else {
14513 edits.push((prefix_range, empty_str.clone()));
14514 edits.push((suffix_range, empty_str.clone()));
14515 }
14516 } else {
14517 continue;
14518 }
14519 }
14520
14521 drop(snapshot);
14522 this.buffer.update(cx, |buffer, cx| {
14523 buffer.edit(edits, None, cx);
14524 });
14525
14526 // Adjust selections so that they end before any comment suffixes that
14527 // were inserted.
14528 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14529 let mut selections = this.selections.all::<Point>(cx);
14530 let snapshot = this.buffer.read(cx).read(cx);
14531 for selection in &mut selections {
14532 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14533 match row.cmp(&MultiBufferRow(selection.end.row)) {
14534 Ordering::Less => {
14535 suffixes_inserted.next();
14536 continue;
14537 }
14538 Ordering::Greater => break,
14539 Ordering::Equal => {
14540 if selection.end.column == snapshot.line_len(row) {
14541 if selection.is_empty() {
14542 selection.start.column -= suffix_len as u32;
14543 }
14544 selection.end.column -= suffix_len as u32;
14545 }
14546 break;
14547 }
14548 }
14549 }
14550 }
14551
14552 drop(snapshot);
14553 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14554
14555 let selections = this.selections.all::<Point>(cx);
14556 let selections_on_single_row = selections.windows(2).all(|selections| {
14557 selections[0].start.row == selections[1].start.row
14558 && selections[0].end.row == selections[1].end.row
14559 && selections[0].start.row == selections[0].end.row
14560 });
14561 let selections_selecting = selections
14562 .iter()
14563 .any(|selection| selection.start != selection.end);
14564 let advance_downwards = action.advance_downwards
14565 && selections_on_single_row
14566 && !selections_selecting
14567 && !matches!(this.mode, EditorMode::SingleLine);
14568
14569 if advance_downwards {
14570 let snapshot = this.buffer.read(cx).snapshot(cx);
14571
14572 this.change_selections(Default::default(), window, cx, |s| {
14573 s.move_cursors_with(|display_snapshot, display_point, _| {
14574 let mut point = display_point.to_point(display_snapshot);
14575 point.row += 1;
14576 point = snapshot.clip_point(point, Bias::Left);
14577 let display_point = point.to_display_point(display_snapshot);
14578 let goal = SelectionGoal::HorizontalPosition(
14579 display_snapshot
14580 .x_for_display_point(display_point, text_layout_details)
14581 .into(),
14582 );
14583 (display_point, goal)
14584 })
14585 });
14586 }
14587 });
14588 }
14589
14590 pub fn select_enclosing_symbol(
14591 &mut self,
14592 _: &SelectEnclosingSymbol,
14593 window: &mut Window,
14594 cx: &mut Context<Self>,
14595 ) {
14596 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14597
14598 let buffer = self.buffer.read(cx).snapshot(cx);
14599 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14600
14601 fn update_selection(
14602 selection: &Selection<usize>,
14603 buffer_snap: &MultiBufferSnapshot,
14604 ) -> Option<Selection<usize>> {
14605 let cursor = selection.head();
14606 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14607 for symbol in symbols.iter().rev() {
14608 let start = symbol.range.start.to_offset(buffer_snap);
14609 let end = symbol.range.end.to_offset(buffer_snap);
14610 let new_range = start..end;
14611 if start < selection.start || end > selection.end {
14612 return Some(Selection {
14613 id: selection.id,
14614 start: new_range.start,
14615 end: new_range.end,
14616 goal: SelectionGoal::None,
14617 reversed: selection.reversed,
14618 });
14619 }
14620 }
14621 None
14622 }
14623
14624 let mut selected_larger_symbol = false;
14625 let new_selections = old_selections
14626 .iter()
14627 .map(|selection| match update_selection(selection, &buffer) {
14628 Some(new_selection) => {
14629 if new_selection.range() != selection.range() {
14630 selected_larger_symbol = true;
14631 }
14632 new_selection
14633 }
14634 None => selection.clone(),
14635 })
14636 .collect::<Vec<_>>();
14637
14638 if selected_larger_symbol {
14639 self.change_selections(Default::default(), window, cx, |s| {
14640 s.select(new_selections);
14641 });
14642 }
14643 }
14644
14645 pub fn select_larger_syntax_node(
14646 &mut self,
14647 _: &SelectLargerSyntaxNode,
14648 window: &mut Window,
14649 cx: &mut Context<Self>,
14650 ) {
14651 let Some(visible_row_count) = self.visible_row_count() else {
14652 return;
14653 };
14654 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14655 if old_selections.is_empty() {
14656 return;
14657 }
14658
14659 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14660
14661 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14662 let buffer = self.buffer.read(cx).snapshot(cx);
14663
14664 let mut selected_larger_node = false;
14665 let mut new_selections = old_selections
14666 .iter()
14667 .map(|selection| {
14668 let old_range = selection.start..selection.end;
14669
14670 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14671 // manually select word at selection
14672 if ["string_content", "inline"].contains(&node.kind()) {
14673 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14674 // ignore if word is already selected
14675 if !word_range.is_empty() && old_range != word_range {
14676 let (last_word_range, _) =
14677 buffer.surrounding_word(old_range.end, false);
14678 // only select word if start and end point belongs to same word
14679 if word_range == last_word_range {
14680 selected_larger_node = true;
14681 return Selection {
14682 id: selection.id,
14683 start: word_range.start,
14684 end: word_range.end,
14685 goal: SelectionGoal::None,
14686 reversed: selection.reversed,
14687 };
14688 }
14689 }
14690 }
14691 }
14692
14693 let mut new_range = old_range.clone();
14694 while let Some((_node, containing_range)) =
14695 buffer.syntax_ancestor(new_range.clone())
14696 {
14697 new_range = match containing_range {
14698 MultiOrSingleBufferOffsetRange::Single(_) => break,
14699 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14700 };
14701 if !display_map.intersects_fold(new_range.start)
14702 && !display_map.intersects_fold(new_range.end)
14703 {
14704 break;
14705 }
14706 }
14707
14708 selected_larger_node |= new_range != old_range;
14709 Selection {
14710 id: selection.id,
14711 start: new_range.start,
14712 end: new_range.end,
14713 goal: SelectionGoal::None,
14714 reversed: selection.reversed,
14715 }
14716 })
14717 .collect::<Vec<_>>();
14718
14719 if !selected_larger_node {
14720 return; // don't put this call in the history
14721 }
14722
14723 // scroll based on transformation done to the last selection created by the user
14724 let (last_old, last_new) = old_selections
14725 .last()
14726 .zip(new_selections.last().cloned())
14727 .expect("old_selections isn't empty");
14728
14729 // revert selection
14730 let is_selection_reversed = {
14731 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14732 new_selections.last_mut().expect("checked above").reversed =
14733 should_newest_selection_be_reversed;
14734 should_newest_selection_be_reversed
14735 };
14736
14737 if selected_larger_node {
14738 self.select_syntax_node_history.disable_clearing = true;
14739 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14740 s.select(new_selections.clone());
14741 });
14742 self.select_syntax_node_history.disable_clearing = false;
14743 }
14744
14745 let start_row = last_new.start.to_display_point(&display_map).row().0;
14746 let end_row = last_new.end.to_display_point(&display_map).row().0;
14747 let selection_height = end_row - start_row + 1;
14748 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14749
14750 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14751 let scroll_behavior = if fits_on_the_screen {
14752 self.request_autoscroll(Autoscroll::fit(), cx);
14753 SelectSyntaxNodeScrollBehavior::FitSelection
14754 } else if is_selection_reversed {
14755 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14756 SelectSyntaxNodeScrollBehavior::CursorTop
14757 } else {
14758 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14759 SelectSyntaxNodeScrollBehavior::CursorBottom
14760 };
14761
14762 self.select_syntax_node_history.push((
14763 old_selections,
14764 scroll_behavior,
14765 is_selection_reversed,
14766 ));
14767 }
14768
14769 pub fn select_smaller_syntax_node(
14770 &mut self,
14771 _: &SelectSmallerSyntaxNode,
14772 window: &mut Window,
14773 cx: &mut Context<Self>,
14774 ) {
14775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14776
14777 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14778 self.select_syntax_node_history.pop()
14779 {
14780 if let Some(selection) = selections.last_mut() {
14781 selection.reversed = is_selection_reversed;
14782 }
14783
14784 self.select_syntax_node_history.disable_clearing = true;
14785 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14786 s.select(selections.to_vec());
14787 });
14788 self.select_syntax_node_history.disable_clearing = false;
14789
14790 match scroll_behavior {
14791 SelectSyntaxNodeScrollBehavior::CursorTop => {
14792 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14793 }
14794 SelectSyntaxNodeScrollBehavior::FitSelection => {
14795 self.request_autoscroll(Autoscroll::fit(), cx);
14796 }
14797 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14798 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14799 }
14800 }
14801 }
14802 }
14803
14804 pub fn unwrap_syntax_node(
14805 &mut self,
14806 _: &UnwrapSyntaxNode,
14807 window: &mut Window,
14808 cx: &mut Context<Self>,
14809 ) {
14810 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14811
14812 let buffer = self.buffer.read(cx).snapshot(cx);
14813 let selections = self
14814 .selections
14815 .all::<usize>(cx)
14816 .into_iter()
14817 // subtracting the offset requires sorting
14818 .sorted_by_key(|i| i.start);
14819
14820 let full_edits = selections
14821 .into_iter()
14822 .filter_map(|selection| {
14823 // Only requires two branches once if-let-chains stabilize (#53667)
14824 let child = if !selection.is_empty() {
14825 selection.range()
14826 } else if let Some((_, ancestor_range)) =
14827 buffer.syntax_ancestor(selection.start..selection.end)
14828 {
14829 match ancestor_range {
14830 MultiOrSingleBufferOffsetRange::Single(range) => range,
14831 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14832 }
14833 } else {
14834 selection.range()
14835 };
14836
14837 let mut parent = child.clone();
14838 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14839 parent = match ancestor_range {
14840 MultiOrSingleBufferOffsetRange::Single(range) => range,
14841 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14842 };
14843 if parent.start < child.start || parent.end > child.end {
14844 break;
14845 }
14846 }
14847
14848 if parent == child {
14849 return None;
14850 }
14851 let text = buffer.text_for_range(child).collect::<String>();
14852 Some((selection.id, parent, text))
14853 })
14854 .collect::<Vec<_>>();
14855
14856 self.transact(window, cx, |this, window, cx| {
14857 this.buffer.update(cx, |buffer, cx| {
14858 buffer.edit(
14859 full_edits
14860 .iter()
14861 .map(|(_, p, t)| (p.clone(), t.clone()))
14862 .collect::<Vec<_>>(),
14863 None,
14864 cx,
14865 );
14866 });
14867 this.change_selections(Default::default(), window, cx, |s| {
14868 let mut offset = 0;
14869 let mut selections = vec![];
14870 for (id, parent, text) in full_edits {
14871 let start = parent.start - offset;
14872 offset += parent.len() - text.len();
14873 selections.push(Selection {
14874 id,
14875 start,
14876 end: start + text.len(),
14877 reversed: false,
14878 goal: Default::default(),
14879 });
14880 }
14881 s.select(selections);
14882 });
14883 });
14884 }
14885
14886 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14887 if !EditorSettings::get_global(cx).gutter.runnables {
14888 self.clear_tasks();
14889 return Task::ready(());
14890 }
14891 let project = self.project().map(Entity::downgrade);
14892 let task_sources = self.lsp_task_sources(cx);
14893 let multi_buffer = self.buffer.downgrade();
14894 cx.spawn_in(window, async move |editor, cx| {
14895 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14896 let Some(project) = project.and_then(|p| p.upgrade()) else {
14897 return;
14898 };
14899 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14900 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14901 }) else {
14902 return;
14903 };
14904
14905 let hide_runnables = project
14906 .update(cx, |project, _| project.is_via_collab())
14907 .unwrap_or(true);
14908 if hide_runnables {
14909 return;
14910 }
14911 let new_rows =
14912 cx.background_spawn({
14913 let snapshot = display_snapshot.clone();
14914 async move {
14915 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14916 }
14917 })
14918 .await;
14919 let Ok(lsp_tasks) =
14920 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14921 else {
14922 return;
14923 };
14924 let lsp_tasks = lsp_tasks.await;
14925
14926 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14927 lsp_tasks
14928 .into_iter()
14929 .flat_map(|(kind, tasks)| {
14930 tasks.into_iter().filter_map(move |(location, task)| {
14931 Some((kind.clone(), location?, task))
14932 })
14933 })
14934 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14935 let buffer = location.target.buffer;
14936 let buffer_snapshot = buffer.read(cx).snapshot();
14937 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14938 |(excerpt_id, snapshot, _)| {
14939 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14940 display_snapshot
14941 .buffer_snapshot
14942 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14943 } else {
14944 None
14945 }
14946 },
14947 );
14948 if let Some(offset) = offset {
14949 let task_buffer_range =
14950 location.target.range.to_point(&buffer_snapshot);
14951 let context_buffer_range =
14952 task_buffer_range.to_offset(&buffer_snapshot);
14953 let context_range = BufferOffset(context_buffer_range.start)
14954 ..BufferOffset(context_buffer_range.end);
14955
14956 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14957 .or_insert_with(|| RunnableTasks {
14958 templates: Vec::new(),
14959 offset,
14960 column: task_buffer_range.start.column,
14961 extra_variables: HashMap::default(),
14962 context_range,
14963 })
14964 .templates
14965 .push((kind, task.original_task().clone()));
14966 }
14967
14968 acc
14969 })
14970 }) else {
14971 return;
14972 };
14973
14974 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14975 buffer.language_settings(cx).tasks.prefer_lsp
14976 }) else {
14977 return;
14978 };
14979
14980 let rows = Self::runnable_rows(
14981 project,
14982 display_snapshot,
14983 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14984 new_rows,
14985 cx.clone(),
14986 )
14987 .await;
14988 editor
14989 .update(cx, |editor, _| {
14990 editor.clear_tasks();
14991 for (key, mut value) in rows {
14992 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14993 value.templates.extend(lsp_tasks.templates);
14994 }
14995
14996 editor.insert_tasks(key, value);
14997 }
14998 for (key, value) in lsp_tasks_by_rows {
14999 editor.insert_tasks(key, value);
15000 }
15001 })
15002 .ok();
15003 })
15004 }
15005 fn fetch_runnable_ranges(
15006 snapshot: &DisplaySnapshot,
15007 range: Range<Anchor>,
15008 ) -> Vec<language::RunnableRange> {
15009 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15010 }
15011
15012 fn runnable_rows(
15013 project: Entity<Project>,
15014 snapshot: DisplaySnapshot,
15015 prefer_lsp: bool,
15016 runnable_ranges: Vec<RunnableRange>,
15017 cx: AsyncWindowContext,
15018 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15019 cx.spawn(async move |cx| {
15020 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15021 for mut runnable in runnable_ranges {
15022 let Some(tasks) = cx
15023 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15024 .ok()
15025 else {
15026 continue;
15027 };
15028 let mut tasks = tasks.await;
15029
15030 if prefer_lsp {
15031 tasks.retain(|(task_kind, _)| {
15032 !matches!(task_kind, TaskSourceKind::Language { .. })
15033 });
15034 }
15035 if tasks.is_empty() {
15036 continue;
15037 }
15038
15039 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15040 let Some(row) = snapshot
15041 .buffer_snapshot
15042 .buffer_line_for_row(MultiBufferRow(point.row))
15043 .map(|(_, range)| range.start.row)
15044 else {
15045 continue;
15046 };
15047
15048 let context_range =
15049 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15050 runnable_rows.push((
15051 (runnable.buffer_id, row),
15052 RunnableTasks {
15053 templates: tasks,
15054 offset: snapshot
15055 .buffer_snapshot
15056 .anchor_before(runnable.run_range.start),
15057 context_range,
15058 column: point.column,
15059 extra_variables: runnable.extra_captures,
15060 },
15061 ));
15062 }
15063 runnable_rows
15064 })
15065 }
15066
15067 fn templates_with_tags(
15068 project: &Entity<Project>,
15069 runnable: &mut Runnable,
15070 cx: &mut App,
15071 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15072 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15073 let (worktree_id, file) = project
15074 .buffer_for_id(runnable.buffer, cx)
15075 .and_then(|buffer| buffer.read(cx).file())
15076 .map(|file| (file.worktree_id(cx), file.clone()))
15077 .unzip();
15078
15079 (
15080 project.task_store().read(cx).task_inventory().cloned(),
15081 worktree_id,
15082 file,
15083 )
15084 });
15085
15086 let tags = mem::take(&mut runnable.tags);
15087 let language = runnable.language.clone();
15088 cx.spawn(async move |cx| {
15089 let mut templates_with_tags = Vec::new();
15090 if let Some(inventory) = inventory {
15091 for RunnableTag(tag) in tags {
15092 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15093 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15094 }) else {
15095 return templates_with_tags;
15096 };
15097 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15098 move |(_, template)| {
15099 template.tags.iter().any(|source_tag| source_tag == &tag)
15100 },
15101 ));
15102 }
15103 }
15104 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15105
15106 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15107 // Strongest source wins; if we have worktree tag binding, prefer that to
15108 // global and language bindings;
15109 // if we have a global binding, prefer that to language binding.
15110 let first_mismatch = templates_with_tags
15111 .iter()
15112 .position(|(tag_source, _)| tag_source != leading_tag_source);
15113 if let Some(index) = first_mismatch {
15114 templates_with_tags.truncate(index);
15115 }
15116 }
15117
15118 templates_with_tags
15119 })
15120 }
15121
15122 pub fn move_to_enclosing_bracket(
15123 &mut self,
15124 _: &MoveToEnclosingBracket,
15125 window: &mut Window,
15126 cx: &mut Context<Self>,
15127 ) {
15128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15129 self.change_selections(Default::default(), window, cx, |s| {
15130 s.move_offsets_with(|snapshot, selection| {
15131 let Some(enclosing_bracket_ranges) =
15132 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15133 else {
15134 return;
15135 };
15136
15137 let mut best_length = usize::MAX;
15138 let mut best_inside = false;
15139 let mut best_in_bracket_range = false;
15140 let mut best_destination = None;
15141 for (open, close) in enclosing_bracket_ranges {
15142 let close = close.to_inclusive();
15143 let length = close.end() - open.start;
15144 let inside = selection.start >= open.end && selection.end <= *close.start();
15145 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15146 || close.contains(&selection.head());
15147
15148 // If best is next to a bracket and current isn't, skip
15149 if !in_bracket_range && best_in_bracket_range {
15150 continue;
15151 }
15152
15153 // Prefer smaller lengths unless best is inside and current isn't
15154 if length > best_length && (best_inside || !inside) {
15155 continue;
15156 }
15157
15158 best_length = length;
15159 best_inside = inside;
15160 best_in_bracket_range = in_bracket_range;
15161 best_destination = Some(
15162 if close.contains(&selection.start) && close.contains(&selection.end) {
15163 if inside { open.end } else { open.start }
15164 } else if inside {
15165 *close.start()
15166 } else {
15167 *close.end()
15168 },
15169 );
15170 }
15171
15172 if let Some(destination) = best_destination {
15173 selection.collapse_to(destination, SelectionGoal::None);
15174 }
15175 })
15176 });
15177 }
15178
15179 pub fn undo_selection(
15180 &mut self,
15181 _: &UndoSelection,
15182 window: &mut Window,
15183 cx: &mut Context<Self>,
15184 ) {
15185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15186 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15187 self.selection_history.mode = SelectionHistoryMode::Undoing;
15188 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15189 this.end_selection(window, cx);
15190 this.change_selections(
15191 SelectionEffects::scroll(Autoscroll::newest()),
15192 window,
15193 cx,
15194 |s| s.select_anchors(entry.selections.to_vec()),
15195 );
15196 });
15197 self.selection_history.mode = SelectionHistoryMode::Normal;
15198
15199 self.select_next_state = entry.select_next_state;
15200 self.select_prev_state = entry.select_prev_state;
15201 self.add_selections_state = entry.add_selections_state;
15202 }
15203 }
15204
15205 pub fn redo_selection(
15206 &mut self,
15207 _: &RedoSelection,
15208 window: &mut Window,
15209 cx: &mut Context<Self>,
15210 ) {
15211 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15212 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15213 self.selection_history.mode = SelectionHistoryMode::Redoing;
15214 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15215 this.end_selection(window, cx);
15216 this.change_selections(
15217 SelectionEffects::scroll(Autoscroll::newest()),
15218 window,
15219 cx,
15220 |s| s.select_anchors(entry.selections.to_vec()),
15221 );
15222 });
15223 self.selection_history.mode = SelectionHistoryMode::Normal;
15224
15225 self.select_next_state = entry.select_next_state;
15226 self.select_prev_state = entry.select_prev_state;
15227 self.add_selections_state = entry.add_selections_state;
15228 }
15229 }
15230
15231 pub fn expand_excerpts(
15232 &mut self,
15233 action: &ExpandExcerpts,
15234 _: &mut Window,
15235 cx: &mut Context<Self>,
15236 ) {
15237 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15238 }
15239
15240 pub fn expand_excerpts_down(
15241 &mut self,
15242 action: &ExpandExcerptsDown,
15243 _: &mut Window,
15244 cx: &mut Context<Self>,
15245 ) {
15246 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15247 }
15248
15249 pub fn expand_excerpts_up(
15250 &mut self,
15251 action: &ExpandExcerptsUp,
15252 _: &mut Window,
15253 cx: &mut Context<Self>,
15254 ) {
15255 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15256 }
15257
15258 pub fn expand_excerpts_for_direction(
15259 &mut self,
15260 lines: u32,
15261 direction: ExpandExcerptDirection,
15262
15263 cx: &mut Context<Self>,
15264 ) {
15265 let selections = self.selections.disjoint_anchors();
15266
15267 let lines = if lines == 0 {
15268 EditorSettings::get_global(cx).expand_excerpt_lines
15269 } else {
15270 lines
15271 };
15272
15273 self.buffer.update(cx, |buffer, cx| {
15274 let snapshot = buffer.snapshot(cx);
15275 let mut excerpt_ids = selections
15276 .iter()
15277 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15278 .collect::<Vec<_>>();
15279 excerpt_ids.sort();
15280 excerpt_ids.dedup();
15281 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15282 })
15283 }
15284
15285 pub fn expand_excerpt(
15286 &mut self,
15287 excerpt: ExcerptId,
15288 direction: ExpandExcerptDirection,
15289 window: &mut Window,
15290 cx: &mut Context<Self>,
15291 ) {
15292 let current_scroll_position = self.scroll_position(cx);
15293 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15294 let mut should_scroll_up = false;
15295
15296 if direction == ExpandExcerptDirection::Down {
15297 let multi_buffer = self.buffer.read(cx);
15298 let snapshot = multi_buffer.snapshot(cx);
15299 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15300 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15301 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15302 {
15303 let buffer_snapshot = buffer.read(cx).snapshot();
15304 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15305 let last_row = buffer_snapshot.max_point().row;
15306 let lines_below = last_row.saturating_sub(excerpt_end_row);
15307 should_scroll_up = lines_below >= lines_to_expand;
15308 }
15309 }
15310
15311 self.buffer.update(cx, |buffer, cx| {
15312 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15313 });
15314
15315 if should_scroll_up {
15316 let new_scroll_position =
15317 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15318 self.set_scroll_position(new_scroll_position, window, cx);
15319 }
15320 }
15321
15322 pub fn go_to_singleton_buffer_point(
15323 &mut self,
15324 point: Point,
15325 window: &mut Window,
15326 cx: &mut Context<Self>,
15327 ) {
15328 self.go_to_singleton_buffer_range(point..point, window, cx);
15329 }
15330
15331 pub fn go_to_singleton_buffer_range(
15332 &mut self,
15333 range: Range<Point>,
15334 window: &mut Window,
15335 cx: &mut Context<Self>,
15336 ) {
15337 let multibuffer = self.buffer().read(cx);
15338 let Some(buffer) = multibuffer.as_singleton() else {
15339 return;
15340 };
15341 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15342 return;
15343 };
15344 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15345 return;
15346 };
15347 self.change_selections(
15348 SelectionEffects::default().nav_history(true),
15349 window,
15350 cx,
15351 |s| s.select_anchor_ranges([start..end]),
15352 );
15353 }
15354
15355 pub fn go_to_diagnostic(
15356 &mut self,
15357 action: &GoToDiagnostic,
15358 window: &mut Window,
15359 cx: &mut Context<Self>,
15360 ) {
15361 if !self.diagnostics_enabled() {
15362 return;
15363 }
15364 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15365 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15366 }
15367
15368 pub fn go_to_prev_diagnostic(
15369 &mut self,
15370 action: &GoToPreviousDiagnostic,
15371 window: &mut Window,
15372 cx: &mut Context<Self>,
15373 ) {
15374 if !self.diagnostics_enabled() {
15375 return;
15376 }
15377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15378 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15379 }
15380
15381 pub fn go_to_diagnostic_impl(
15382 &mut self,
15383 direction: Direction,
15384 severity: GoToDiagnosticSeverityFilter,
15385 window: &mut Window,
15386 cx: &mut Context<Self>,
15387 ) {
15388 let buffer = self.buffer.read(cx).snapshot(cx);
15389 let selection = self.selections.newest::<usize>(cx);
15390
15391 let mut active_group_id = None;
15392 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15393 && active_group.active_range.start.to_offset(&buffer) == selection.start
15394 {
15395 active_group_id = Some(active_group.group_id);
15396 }
15397
15398 fn filtered(
15399 snapshot: EditorSnapshot,
15400 severity: GoToDiagnosticSeverityFilter,
15401 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15402 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15403 diagnostics
15404 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15405 .filter(|entry| entry.range.start != entry.range.end)
15406 .filter(|entry| !entry.diagnostic.is_unnecessary)
15407 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15408 }
15409
15410 let snapshot = self.snapshot(window, cx);
15411 let before = filtered(
15412 snapshot.clone(),
15413 severity,
15414 buffer
15415 .diagnostics_in_range(0..selection.start)
15416 .filter(|entry| entry.range.start <= selection.start),
15417 );
15418 let after = filtered(
15419 snapshot,
15420 severity,
15421 buffer
15422 .diagnostics_in_range(selection.start..buffer.len())
15423 .filter(|entry| entry.range.start >= selection.start),
15424 );
15425
15426 let mut found: Option<DiagnosticEntry<usize>> = None;
15427 if direction == Direction::Prev {
15428 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15429 {
15430 for diagnostic in prev_diagnostics.into_iter().rev() {
15431 if diagnostic.range.start != selection.start
15432 || active_group_id
15433 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15434 {
15435 found = Some(diagnostic);
15436 break 'outer;
15437 }
15438 }
15439 }
15440 } else {
15441 for diagnostic in after.chain(before) {
15442 if diagnostic.range.start != selection.start
15443 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15444 {
15445 found = Some(diagnostic);
15446 break;
15447 }
15448 }
15449 }
15450 let Some(next_diagnostic) = found else {
15451 return;
15452 };
15453
15454 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15455 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15456 return;
15457 };
15458 self.change_selections(Default::default(), window, cx, |s| {
15459 s.select_ranges(vec![
15460 next_diagnostic.range.start..next_diagnostic.range.start,
15461 ])
15462 });
15463 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15464 self.refresh_edit_prediction(false, true, window, cx);
15465 }
15466
15467 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15469 let snapshot = self.snapshot(window, cx);
15470 let selection = self.selections.newest::<Point>(cx);
15471 self.go_to_hunk_before_or_after_position(
15472 &snapshot,
15473 selection.head(),
15474 Direction::Next,
15475 window,
15476 cx,
15477 );
15478 }
15479
15480 pub fn go_to_hunk_before_or_after_position(
15481 &mut self,
15482 snapshot: &EditorSnapshot,
15483 position: Point,
15484 direction: Direction,
15485 window: &mut Window,
15486 cx: &mut Context<Editor>,
15487 ) {
15488 let row = if direction == Direction::Next {
15489 self.hunk_after_position(snapshot, position)
15490 .map(|hunk| hunk.row_range.start)
15491 } else {
15492 self.hunk_before_position(snapshot, position)
15493 };
15494
15495 if let Some(row) = row {
15496 let destination = Point::new(row.0, 0);
15497 let autoscroll = Autoscroll::center();
15498
15499 self.unfold_ranges(&[destination..destination], false, false, cx);
15500 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15501 s.select_ranges([destination..destination]);
15502 });
15503 }
15504 }
15505
15506 fn hunk_after_position(
15507 &mut self,
15508 snapshot: &EditorSnapshot,
15509 position: Point,
15510 ) -> Option<MultiBufferDiffHunk> {
15511 snapshot
15512 .buffer_snapshot
15513 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15514 .find(|hunk| hunk.row_range.start.0 > position.row)
15515 .or_else(|| {
15516 snapshot
15517 .buffer_snapshot
15518 .diff_hunks_in_range(Point::zero()..position)
15519 .find(|hunk| hunk.row_range.end.0 < position.row)
15520 })
15521 }
15522
15523 fn go_to_prev_hunk(
15524 &mut self,
15525 _: &GoToPreviousHunk,
15526 window: &mut Window,
15527 cx: &mut Context<Self>,
15528 ) {
15529 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15530 let snapshot = self.snapshot(window, cx);
15531 let selection = self.selections.newest::<Point>(cx);
15532 self.go_to_hunk_before_or_after_position(
15533 &snapshot,
15534 selection.head(),
15535 Direction::Prev,
15536 window,
15537 cx,
15538 );
15539 }
15540
15541 fn hunk_before_position(
15542 &mut self,
15543 snapshot: &EditorSnapshot,
15544 position: Point,
15545 ) -> Option<MultiBufferRow> {
15546 snapshot
15547 .buffer_snapshot
15548 .diff_hunk_before(position)
15549 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15550 }
15551
15552 fn go_to_next_change(
15553 &mut self,
15554 _: &GoToNextChange,
15555 window: &mut Window,
15556 cx: &mut Context<Self>,
15557 ) {
15558 if let Some(selections) = self
15559 .change_list
15560 .next_change(1, Direction::Next)
15561 .map(|s| s.to_vec())
15562 {
15563 self.change_selections(Default::default(), window, cx, |s| {
15564 let map = s.display_map();
15565 s.select_display_ranges(selections.iter().map(|a| {
15566 let point = a.to_display_point(&map);
15567 point..point
15568 }))
15569 })
15570 }
15571 }
15572
15573 fn go_to_previous_change(
15574 &mut self,
15575 _: &GoToPreviousChange,
15576 window: &mut Window,
15577 cx: &mut Context<Self>,
15578 ) {
15579 if let Some(selections) = self
15580 .change_list
15581 .next_change(1, Direction::Prev)
15582 .map(|s| s.to_vec())
15583 {
15584 self.change_selections(Default::default(), window, cx, |s| {
15585 let map = s.display_map();
15586 s.select_display_ranges(selections.iter().map(|a| {
15587 let point = a.to_display_point(&map);
15588 point..point
15589 }))
15590 })
15591 }
15592 }
15593
15594 fn go_to_line<T: 'static>(
15595 &mut self,
15596 position: Anchor,
15597 highlight_color: Option<Hsla>,
15598 window: &mut Window,
15599 cx: &mut Context<Self>,
15600 ) {
15601 let snapshot = self.snapshot(window, cx).display_snapshot;
15602 let position = position.to_point(&snapshot.buffer_snapshot);
15603 let start = snapshot
15604 .buffer_snapshot
15605 .clip_point(Point::new(position.row, 0), Bias::Left);
15606 let end = start + Point::new(1, 0);
15607 let start = snapshot.buffer_snapshot.anchor_before(start);
15608 let end = snapshot.buffer_snapshot.anchor_before(end);
15609
15610 self.highlight_rows::<T>(
15611 start..end,
15612 highlight_color
15613 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15614 Default::default(),
15615 cx,
15616 );
15617
15618 if self.buffer.read(cx).is_singleton() {
15619 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15620 }
15621 }
15622
15623 pub fn go_to_definition(
15624 &mut self,
15625 _: &GoToDefinition,
15626 window: &mut Window,
15627 cx: &mut Context<Self>,
15628 ) -> Task<Result<Navigated>> {
15629 let definition =
15630 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15631 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15632 cx.spawn_in(window, async move |editor, cx| {
15633 if definition.await? == Navigated::Yes {
15634 return Ok(Navigated::Yes);
15635 }
15636 match fallback_strategy {
15637 GoToDefinitionFallback::None => Ok(Navigated::No),
15638 GoToDefinitionFallback::FindAllReferences => {
15639 match editor.update_in(cx, |editor, window, cx| {
15640 editor.find_all_references(&FindAllReferences, window, cx)
15641 })? {
15642 Some(references) => references.await,
15643 None => Ok(Navigated::No),
15644 }
15645 }
15646 }
15647 })
15648 }
15649
15650 pub fn go_to_declaration(
15651 &mut self,
15652 _: &GoToDeclaration,
15653 window: &mut Window,
15654 cx: &mut Context<Self>,
15655 ) -> Task<Result<Navigated>> {
15656 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15657 }
15658
15659 pub fn go_to_declaration_split(
15660 &mut self,
15661 _: &GoToDeclaration,
15662 window: &mut Window,
15663 cx: &mut Context<Self>,
15664 ) -> Task<Result<Navigated>> {
15665 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15666 }
15667
15668 pub fn go_to_implementation(
15669 &mut self,
15670 _: &GoToImplementation,
15671 window: &mut Window,
15672 cx: &mut Context<Self>,
15673 ) -> Task<Result<Navigated>> {
15674 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15675 }
15676
15677 pub fn go_to_implementation_split(
15678 &mut self,
15679 _: &GoToImplementationSplit,
15680 window: &mut Window,
15681 cx: &mut Context<Self>,
15682 ) -> Task<Result<Navigated>> {
15683 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15684 }
15685
15686 pub fn go_to_type_definition(
15687 &mut self,
15688 _: &GoToTypeDefinition,
15689 window: &mut Window,
15690 cx: &mut Context<Self>,
15691 ) -> Task<Result<Navigated>> {
15692 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15693 }
15694
15695 pub fn go_to_definition_split(
15696 &mut self,
15697 _: &GoToDefinitionSplit,
15698 window: &mut Window,
15699 cx: &mut Context<Self>,
15700 ) -> Task<Result<Navigated>> {
15701 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15702 }
15703
15704 pub fn go_to_type_definition_split(
15705 &mut self,
15706 _: &GoToTypeDefinitionSplit,
15707 window: &mut Window,
15708 cx: &mut Context<Self>,
15709 ) -> Task<Result<Navigated>> {
15710 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15711 }
15712
15713 fn go_to_definition_of_kind(
15714 &mut self,
15715 kind: GotoDefinitionKind,
15716 split: bool,
15717 window: &mut Window,
15718 cx: &mut Context<Self>,
15719 ) -> Task<Result<Navigated>> {
15720 let Some(provider) = self.semantics_provider.clone() else {
15721 return Task::ready(Ok(Navigated::No));
15722 };
15723 let head = self.selections.newest::<usize>(cx).head();
15724 let buffer = self.buffer.read(cx);
15725 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15726 return Task::ready(Ok(Navigated::No));
15727 };
15728 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15729 return Task::ready(Ok(Navigated::No));
15730 };
15731
15732 cx.spawn_in(window, async move |editor, cx| {
15733 let Some(definitions) = definitions.await? else {
15734 return Ok(Navigated::No);
15735 };
15736 let navigated = editor
15737 .update_in(cx, |editor, window, cx| {
15738 editor.navigate_to_hover_links(
15739 Some(kind),
15740 definitions
15741 .into_iter()
15742 .filter(|location| {
15743 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15744 })
15745 .map(HoverLink::Text)
15746 .collect::<Vec<_>>(),
15747 split,
15748 window,
15749 cx,
15750 )
15751 })?
15752 .await?;
15753 anyhow::Ok(navigated)
15754 })
15755 }
15756
15757 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15758 let selection = self.selections.newest_anchor();
15759 let head = selection.head();
15760 let tail = selection.tail();
15761
15762 let Some((buffer, start_position)) =
15763 self.buffer.read(cx).text_anchor_for_position(head, cx)
15764 else {
15765 return;
15766 };
15767
15768 let end_position = if head != tail {
15769 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15770 return;
15771 };
15772 Some(pos)
15773 } else {
15774 None
15775 };
15776
15777 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15778 let url = if let Some(end_pos) = end_position {
15779 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15780 } else {
15781 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15782 };
15783
15784 if let Some(url) = url {
15785 editor.update(cx, |_, cx| {
15786 cx.open_url(&url);
15787 })
15788 } else {
15789 Ok(())
15790 }
15791 });
15792
15793 url_finder.detach();
15794 }
15795
15796 pub fn open_selected_filename(
15797 &mut self,
15798 _: &OpenSelectedFilename,
15799 window: &mut Window,
15800 cx: &mut Context<Self>,
15801 ) {
15802 let Some(workspace) = self.workspace() else {
15803 return;
15804 };
15805
15806 let position = self.selections.newest_anchor().head();
15807
15808 let Some((buffer, buffer_position)) =
15809 self.buffer.read(cx).text_anchor_for_position(position, cx)
15810 else {
15811 return;
15812 };
15813
15814 let project = self.project.clone();
15815
15816 cx.spawn_in(window, async move |_, cx| {
15817 let result = find_file(&buffer, project, buffer_position, cx).await;
15818
15819 if let Some((_, path)) = result {
15820 workspace
15821 .update_in(cx, |workspace, window, cx| {
15822 workspace.open_resolved_path(path, window, cx)
15823 })?
15824 .await?;
15825 }
15826 anyhow::Ok(())
15827 })
15828 .detach();
15829 }
15830
15831 pub(crate) fn navigate_to_hover_links(
15832 &mut self,
15833 kind: Option<GotoDefinitionKind>,
15834 definitions: Vec<HoverLink>,
15835 split: bool,
15836 window: &mut Window,
15837 cx: &mut Context<Editor>,
15838 ) -> Task<Result<Navigated>> {
15839 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15840 let mut first_url_or_file = None;
15841 let definitions: Vec<_> = definitions
15842 .into_iter()
15843 .filter_map(|def| match def {
15844 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15845 HoverLink::InlayHint(lsp_location, server_id) => {
15846 let computation =
15847 self.compute_target_location(lsp_location, server_id, window, cx);
15848 Some(cx.background_spawn(computation))
15849 }
15850 HoverLink::Url(url) => {
15851 first_url_or_file = Some(Either::Left(url));
15852 None
15853 }
15854 HoverLink::File(path) => {
15855 first_url_or_file = Some(Either::Right(path));
15856 None
15857 }
15858 })
15859 .collect();
15860
15861 let workspace = self.workspace();
15862
15863 cx.spawn_in(window, async move |editor, acx| {
15864 let mut locations: Vec<Location> = future::join_all(definitions)
15865 .await
15866 .into_iter()
15867 .filter_map(|location| location.transpose())
15868 .collect::<Result<_>>()
15869 .context("location tasks")?;
15870
15871 if locations.len() > 1 {
15872 let Some(workspace) = workspace else {
15873 return Ok(Navigated::No);
15874 };
15875
15876 let tab_kind = match kind {
15877 Some(GotoDefinitionKind::Implementation) => "Implementations",
15878 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15879 Some(GotoDefinitionKind::Declaration) => "Declarations",
15880 Some(GotoDefinitionKind::Type) => "Types",
15881 };
15882 let title = editor
15883 .update_in(acx, |_, _, cx| {
15884 let target = locations
15885 .iter()
15886 .map(|location| {
15887 location
15888 .buffer
15889 .read(cx)
15890 .text_for_range(location.range.clone())
15891 .collect::<String>()
15892 })
15893 .filter(|text| !text.contains('\n'))
15894 .unique()
15895 .take(3)
15896 .join(", ");
15897 if target.is_empty() {
15898 tab_kind.to_owned()
15899 } else {
15900 format!("{tab_kind} for {target}")
15901 }
15902 })
15903 .context("buffer title")?;
15904
15905 let opened = workspace
15906 .update_in(acx, |workspace, window, cx| {
15907 Self::open_locations_in_multibuffer(
15908 workspace,
15909 locations,
15910 title,
15911 split,
15912 MultibufferSelectionMode::First,
15913 window,
15914 cx,
15915 )
15916 })
15917 .is_ok();
15918
15919 anyhow::Ok(Navigated::from_bool(opened))
15920 } else if locations.is_empty() {
15921 // If there is one definition, just open it directly
15922 match first_url_or_file {
15923 Some(Either::Left(url)) => {
15924 acx.update(|_, cx| cx.open_url(&url))?;
15925 Ok(Navigated::Yes)
15926 }
15927 Some(Either::Right(path)) => {
15928 let Some(workspace) = workspace else {
15929 return Ok(Navigated::No);
15930 };
15931
15932 workspace
15933 .update_in(acx, |workspace, window, cx| {
15934 workspace.open_resolved_path(path, window, cx)
15935 })?
15936 .await?;
15937 Ok(Navigated::Yes)
15938 }
15939 None => Ok(Navigated::No),
15940 }
15941 } else {
15942 let Some(workspace) = workspace else {
15943 return Ok(Navigated::No);
15944 };
15945
15946 let target = locations.pop().unwrap();
15947 editor.update_in(acx, |editor, window, cx| {
15948 let pane = workspace.read(cx).active_pane().clone();
15949
15950 let range = target.range.to_point(target.buffer.read(cx));
15951 let range = editor.range_for_match(&range);
15952 let range = collapse_multiline_range(range);
15953
15954 if !split
15955 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15956 {
15957 editor.go_to_singleton_buffer_range(range, window, cx);
15958 } else {
15959 window.defer(cx, move |window, cx| {
15960 let target_editor: Entity<Self> =
15961 workspace.update(cx, |workspace, cx| {
15962 let pane = if split {
15963 workspace.adjacent_pane(window, cx)
15964 } else {
15965 workspace.active_pane().clone()
15966 };
15967
15968 workspace.open_project_item(
15969 pane,
15970 target.buffer.clone(),
15971 true,
15972 true,
15973 window,
15974 cx,
15975 )
15976 });
15977 target_editor.update(cx, |target_editor, cx| {
15978 // When selecting a definition in a different buffer, disable the nav history
15979 // to avoid creating a history entry at the previous cursor location.
15980 pane.update(cx, |pane, _| pane.disable_history());
15981 target_editor.go_to_singleton_buffer_range(range, window, cx);
15982 pane.update(cx, |pane, _| pane.enable_history());
15983 });
15984 });
15985 }
15986 Navigated::Yes
15987 })
15988 }
15989 })
15990 }
15991
15992 fn compute_target_location(
15993 &self,
15994 lsp_location: lsp::Location,
15995 server_id: LanguageServerId,
15996 window: &mut Window,
15997 cx: &mut Context<Self>,
15998 ) -> Task<anyhow::Result<Option<Location>>> {
15999 let Some(project) = self.project.clone() else {
16000 return Task::ready(Ok(None));
16001 };
16002
16003 cx.spawn_in(window, async move |editor, cx| {
16004 let location_task = editor.update(cx, |_, cx| {
16005 project.update(cx, |project, cx| {
16006 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16007 })
16008 })?;
16009 let location = Some({
16010 let target_buffer_handle = location_task.await.context("open local buffer")?;
16011 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16012 let target_start = target_buffer
16013 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16014 let target_end = target_buffer
16015 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16016 target_buffer.anchor_after(target_start)
16017 ..target_buffer.anchor_before(target_end)
16018 })?;
16019 Location {
16020 buffer: target_buffer_handle,
16021 range,
16022 }
16023 });
16024 Ok(location)
16025 })
16026 }
16027
16028 pub fn find_all_references(
16029 &mut self,
16030 _: &FindAllReferences,
16031 window: &mut Window,
16032 cx: &mut Context<Self>,
16033 ) -> Option<Task<Result<Navigated>>> {
16034 let selection = self.selections.newest::<usize>(cx);
16035 let multi_buffer = self.buffer.read(cx);
16036 let head = selection.head();
16037
16038 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16039 let head_anchor = multi_buffer_snapshot.anchor_at(
16040 head,
16041 if head < selection.tail() {
16042 Bias::Right
16043 } else {
16044 Bias::Left
16045 },
16046 );
16047
16048 match self
16049 .find_all_references_task_sources
16050 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16051 {
16052 Ok(_) => {
16053 log::info!(
16054 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16055 );
16056 return None;
16057 }
16058 Err(i) => {
16059 self.find_all_references_task_sources.insert(i, head_anchor);
16060 }
16061 }
16062
16063 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16064 let workspace = self.workspace()?;
16065 let project = workspace.read(cx).project().clone();
16066 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16067 Some(cx.spawn_in(window, async move |editor, cx| {
16068 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16069 if let Ok(i) = editor
16070 .find_all_references_task_sources
16071 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16072 {
16073 editor.find_all_references_task_sources.remove(i);
16074 }
16075 });
16076
16077 let Some(locations) = references.await? else {
16078 return anyhow::Ok(Navigated::No);
16079 };
16080 if locations.is_empty() {
16081 return anyhow::Ok(Navigated::No);
16082 }
16083
16084 workspace.update_in(cx, |workspace, window, cx| {
16085 let target = locations
16086 .iter()
16087 .map(|location| {
16088 location
16089 .buffer
16090 .read(cx)
16091 .text_for_range(location.range.clone())
16092 .collect::<String>()
16093 })
16094 .filter(|text| !text.contains('\n'))
16095 .unique()
16096 .take(3)
16097 .join(", ");
16098 let title = if target.is_empty() {
16099 "References".to_owned()
16100 } else {
16101 format!("References to {target}")
16102 };
16103 Self::open_locations_in_multibuffer(
16104 workspace,
16105 locations,
16106 title,
16107 false,
16108 MultibufferSelectionMode::First,
16109 window,
16110 cx,
16111 );
16112 Navigated::Yes
16113 })
16114 }))
16115 }
16116
16117 /// Opens a multibuffer with the given project locations in it
16118 pub fn open_locations_in_multibuffer(
16119 workspace: &mut Workspace,
16120 mut locations: Vec<Location>,
16121 title: String,
16122 split: bool,
16123 multibuffer_selection_mode: MultibufferSelectionMode,
16124 window: &mut Window,
16125 cx: &mut Context<Workspace>,
16126 ) {
16127 if locations.is_empty() {
16128 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16129 return;
16130 }
16131
16132 // If there are multiple definitions, open them in a multibuffer
16133 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16134 let mut locations = locations.into_iter().peekable();
16135 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16136 let capability = workspace.project().read(cx).capability();
16137
16138 let excerpt_buffer = cx.new(|cx| {
16139 let mut multibuffer = MultiBuffer::new(capability);
16140 while let Some(location) = locations.next() {
16141 let buffer = location.buffer.read(cx);
16142 let mut ranges_for_buffer = Vec::new();
16143 let range = location.range.to_point(buffer);
16144 ranges_for_buffer.push(range.clone());
16145
16146 while let Some(next_location) = locations.peek() {
16147 if next_location.buffer == location.buffer {
16148 ranges_for_buffer.push(next_location.range.to_point(buffer));
16149 locations.next();
16150 } else {
16151 break;
16152 }
16153 }
16154
16155 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16156 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16157 PathKey::for_buffer(&location.buffer, cx),
16158 location.buffer.clone(),
16159 ranges_for_buffer,
16160 DEFAULT_MULTIBUFFER_CONTEXT,
16161 cx,
16162 );
16163 ranges.extend(new_ranges)
16164 }
16165
16166 multibuffer.with_title(title)
16167 });
16168
16169 let editor = cx.new(|cx| {
16170 Editor::for_multibuffer(
16171 excerpt_buffer,
16172 Some(workspace.project().clone()),
16173 window,
16174 cx,
16175 )
16176 });
16177 editor.update(cx, |editor, cx| {
16178 match multibuffer_selection_mode {
16179 MultibufferSelectionMode::First => {
16180 if let Some(first_range) = ranges.first() {
16181 editor.change_selections(
16182 SelectionEffects::no_scroll(),
16183 window,
16184 cx,
16185 |selections| {
16186 selections.clear_disjoint();
16187 selections
16188 .select_anchor_ranges(std::iter::once(first_range.clone()));
16189 },
16190 );
16191 }
16192 editor.highlight_background::<Self>(
16193 &ranges,
16194 |theme| theme.colors().editor_highlighted_line_background,
16195 cx,
16196 );
16197 }
16198 MultibufferSelectionMode::All => {
16199 editor.change_selections(
16200 SelectionEffects::no_scroll(),
16201 window,
16202 cx,
16203 |selections| {
16204 selections.clear_disjoint();
16205 selections.select_anchor_ranges(ranges);
16206 },
16207 );
16208 }
16209 }
16210 editor.register_buffers_with_language_servers(cx);
16211 });
16212
16213 let item = Box::new(editor);
16214 let item_id = item.item_id();
16215
16216 if split {
16217 workspace.split_item(SplitDirection::Right, item, window, cx);
16218 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16219 let (preview_item_id, preview_item_idx) =
16220 workspace.active_pane().read_with(cx, |pane, _| {
16221 (pane.preview_item_id(), pane.preview_item_idx())
16222 });
16223
16224 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16225
16226 if let Some(preview_item_id) = preview_item_id {
16227 workspace.active_pane().update(cx, |pane, cx| {
16228 pane.remove_item(preview_item_id, false, false, window, cx);
16229 });
16230 }
16231 } else {
16232 workspace.add_item_to_active_pane(item, None, true, window, cx);
16233 }
16234 workspace.active_pane().update(cx, |pane, cx| {
16235 pane.set_preview_item_id(Some(item_id), cx);
16236 });
16237 }
16238
16239 pub fn rename(
16240 &mut self,
16241 _: &Rename,
16242 window: &mut Window,
16243 cx: &mut Context<Self>,
16244 ) -> Option<Task<Result<()>>> {
16245 use language::ToOffset as _;
16246
16247 let provider = self.semantics_provider.clone()?;
16248 let selection = self.selections.newest_anchor().clone();
16249 let (cursor_buffer, cursor_buffer_position) = self
16250 .buffer
16251 .read(cx)
16252 .text_anchor_for_position(selection.head(), cx)?;
16253 let (tail_buffer, cursor_buffer_position_end) = self
16254 .buffer
16255 .read(cx)
16256 .text_anchor_for_position(selection.tail(), cx)?;
16257 if tail_buffer != cursor_buffer {
16258 return None;
16259 }
16260
16261 let snapshot = cursor_buffer.read(cx).snapshot();
16262 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16263 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16264 let prepare_rename = provider
16265 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16266 .unwrap_or_else(|| Task::ready(Ok(None)));
16267 drop(snapshot);
16268
16269 Some(cx.spawn_in(window, async move |this, cx| {
16270 let rename_range = if let Some(range) = prepare_rename.await? {
16271 Some(range)
16272 } else {
16273 this.update(cx, |this, cx| {
16274 let buffer = this.buffer.read(cx).snapshot(cx);
16275 let mut buffer_highlights = this
16276 .document_highlights_for_position(selection.head(), &buffer)
16277 .filter(|highlight| {
16278 highlight.start.excerpt_id == selection.head().excerpt_id
16279 && highlight.end.excerpt_id == selection.head().excerpt_id
16280 });
16281 buffer_highlights
16282 .next()
16283 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16284 })?
16285 };
16286 if let Some(rename_range) = rename_range {
16287 this.update_in(cx, |this, window, cx| {
16288 let snapshot = cursor_buffer.read(cx).snapshot();
16289 let rename_buffer_range = rename_range.to_offset(&snapshot);
16290 let cursor_offset_in_rename_range =
16291 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16292 let cursor_offset_in_rename_range_end =
16293 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16294
16295 this.take_rename(false, window, cx);
16296 let buffer = this.buffer.read(cx).read(cx);
16297 let cursor_offset = selection.head().to_offset(&buffer);
16298 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16299 let rename_end = rename_start + rename_buffer_range.len();
16300 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16301 let mut old_highlight_id = None;
16302 let old_name: Arc<str> = buffer
16303 .chunks(rename_start..rename_end, true)
16304 .map(|chunk| {
16305 if old_highlight_id.is_none() {
16306 old_highlight_id = chunk.syntax_highlight_id;
16307 }
16308 chunk.text
16309 })
16310 .collect::<String>()
16311 .into();
16312
16313 drop(buffer);
16314
16315 // Position the selection in the rename editor so that it matches the current selection.
16316 this.show_local_selections = false;
16317 let rename_editor = cx.new(|cx| {
16318 let mut editor = Editor::single_line(window, cx);
16319 editor.buffer.update(cx, |buffer, cx| {
16320 buffer.edit([(0..0, old_name.clone())], None, cx)
16321 });
16322 let rename_selection_range = match cursor_offset_in_rename_range
16323 .cmp(&cursor_offset_in_rename_range_end)
16324 {
16325 Ordering::Equal => {
16326 editor.select_all(&SelectAll, window, cx);
16327 return editor;
16328 }
16329 Ordering::Less => {
16330 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16331 }
16332 Ordering::Greater => {
16333 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16334 }
16335 };
16336 if rename_selection_range.end > old_name.len() {
16337 editor.select_all(&SelectAll, window, cx);
16338 } else {
16339 editor.change_selections(Default::default(), window, cx, |s| {
16340 s.select_ranges([rename_selection_range]);
16341 });
16342 }
16343 editor
16344 });
16345 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16346 if e == &EditorEvent::Focused {
16347 cx.emit(EditorEvent::FocusedIn)
16348 }
16349 })
16350 .detach();
16351
16352 let write_highlights =
16353 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16354 let read_highlights =
16355 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16356 let ranges = write_highlights
16357 .iter()
16358 .flat_map(|(_, ranges)| ranges.iter())
16359 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16360 .cloned()
16361 .collect();
16362
16363 this.highlight_text::<Rename>(
16364 ranges,
16365 HighlightStyle {
16366 fade_out: Some(0.6),
16367 ..Default::default()
16368 },
16369 cx,
16370 );
16371 let rename_focus_handle = rename_editor.focus_handle(cx);
16372 window.focus(&rename_focus_handle);
16373 let block_id = this.insert_blocks(
16374 [BlockProperties {
16375 style: BlockStyle::Flex,
16376 placement: BlockPlacement::Below(range.start),
16377 height: Some(1),
16378 render: Arc::new({
16379 let rename_editor = rename_editor.clone();
16380 move |cx: &mut BlockContext| {
16381 let mut text_style = cx.editor_style.text.clone();
16382 if let Some(highlight_style) = old_highlight_id
16383 .and_then(|h| h.style(&cx.editor_style.syntax))
16384 {
16385 text_style = text_style.highlight(highlight_style);
16386 }
16387 div()
16388 .block_mouse_except_scroll()
16389 .pl(cx.anchor_x)
16390 .child(EditorElement::new(
16391 &rename_editor,
16392 EditorStyle {
16393 background: cx.theme().system().transparent,
16394 local_player: cx.editor_style.local_player,
16395 text: text_style,
16396 scrollbar_width: cx.editor_style.scrollbar_width,
16397 syntax: cx.editor_style.syntax.clone(),
16398 status: cx.editor_style.status.clone(),
16399 inlay_hints_style: HighlightStyle {
16400 font_weight: Some(FontWeight::BOLD),
16401 ..make_inlay_hints_style(cx.app)
16402 },
16403 edit_prediction_styles: make_suggestion_styles(
16404 cx.app,
16405 ),
16406 ..EditorStyle::default()
16407 },
16408 ))
16409 .into_any_element()
16410 }
16411 }),
16412 priority: 0,
16413 }],
16414 Some(Autoscroll::fit()),
16415 cx,
16416 )[0];
16417 this.pending_rename = Some(RenameState {
16418 range,
16419 old_name,
16420 editor: rename_editor,
16421 block_id,
16422 });
16423 })?;
16424 }
16425
16426 Ok(())
16427 }))
16428 }
16429
16430 pub fn confirm_rename(
16431 &mut self,
16432 _: &ConfirmRename,
16433 window: &mut Window,
16434 cx: &mut Context<Self>,
16435 ) -> Option<Task<Result<()>>> {
16436 let rename = self.take_rename(false, window, cx)?;
16437 let workspace = self.workspace()?.downgrade();
16438 let (buffer, start) = self
16439 .buffer
16440 .read(cx)
16441 .text_anchor_for_position(rename.range.start, cx)?;
16442 let (end_buffer, _) = self
16443 .buffer
16444 .read(cx)
16445 .text_anchor_for_position(rename.range.end, cx)?;
16446 if buffer != end_buffer {
16447 return None;
16448 }
16449
16450 let old_name = rename.old_name;
16451 let new_name = rename.editor.read(cx).text(cx);
16452
16453 let rename = self.semantics_provider.as_ref()?.perform_rename(
16454 &buffer,
16455 start,
16456 new_name.clone(),
16457 cx,
16458 )?;
16459
16460 Some(cx.spawn_in(window, async move |editor, cx| {
16461 let project_transaction = rename.await?;
16462 Self::open_project_transaction(
16463 &editor,
16464 workspace,
16465 project_transaction,
16466 format!("Rename: {} → {}", old_name, new_name),
16467 cx,
16468 )
16469 .await?;
16470
16471 editor.update(cx, |editor, cx| {
16472 editor.refresh_document_highlights(cx);
16473 })?;
16474 Ok(())
16475 }))
16476 }
16477
16478 fn take_rename(
16479 &mut self,
16480 moving_cursor: bool,
16481 window: &mut Window,
16482 cx: &mut Context<Self>,
16483 ) -> Option<RenameState> {
16484 let rename = self.pending_rename.take()?;
16485 if rename.editor.focus_handle(cx).is_focused(window) {
16486 window.focus(&self.focus_handle);
16487 }
16488
16489 self.remove_blocks(
16490 [rename.block_id].into_iter().collect(),
16491 Some(Autoscroll::fit()),
16492 cx,
16493 );
16494 self.clear_highlights::<Rename>(cx);
16495 self.show_local_selections = true;
16496
16497 if moving_cursor {
16498 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16499 editor.selections.newest::<usize>(cx).head()
16500 });
16501
16502 // Update the selection to match the position of the selection inside
16503 // the rename editor.
16504 let snapshot = self.buffer.read(cx).read(cx);
16505 let rename_range = rename.range.to_offset(&snapshot);
16506 let cursor_in_editor = snapshot
16507 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16508 .min(rename_range.end);
16509 drop(snapshot);
16510
16511 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16512 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16513 });
16514 } else {
16515 self.refresh_document_highlights(cx);
16516 }
16517
16518 Some(rename)
16519 }
16520
16521 pub fn pending_rename(&self) -> Option<&RenameState> {
16522 self.pending_rename.as_ref()
16523 }
16524
16525 fn format(
16526 &mut self,
16527 _: &Format,
16528 window: &mut Window,
16529 cx: &mut Context<Self>,
16530 ) -> Option<Task<Result<()>>> {
16531 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16532
16533 let project = match &self.project {
16534 Some(project) => project.clone(),
16535 None => return None,
16536 };
16537
16538 Some(self.perform_format(
16539 project,
16540 FormatTrigger::Manual,
16541 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16542 window,
16543 cx,
16544 ))
16545 }
16546
16547 fn format_selections(
16548 &mut self,
16549 _: &FormatSelections,
16550 window: &mut Window,
16551 cx: &mut Context<Self>,
16552 ) -> Option<Task<Result<()>>> {
16553 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16554
16555 let project = match &self.project {
16556 Some(project) => project.clone(),
16557 None => return None,
16558 };
16559
16560 let ranges = self
16561 .selections
16562 .all_adjusted(cx)
16563 .into_iter()
16564 .map(|selection| selection.range())
16565 .collect_vec();
16566
16567 Some(self.perform_format(
16568 project,
16569 FormatTrigger::Manual,
16570 FormatTarget::Ranges(ranges),
16571 window,
16572 cx,
16573 ))
16574 }
16575
16576 fn perform_format(
16577 &mut self,
16578 project: Entity<Project>,
16579 trigger: FormatTrigger,
16580 target: FormatTarget,
16581 window: &mut Window,
16582 cx: &mut Context<Self>,
16583 ) -> Task<Result<()>> {
16584 let buffer = self.buffer.clone();
16585 let (buffers, target) = match target {
16586 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16587 FormatTarget::Ranges(selection_ranges) => {
16588 let multi_buffer = buffer.read(cx);
16589 let snapshot = multi_buffer.read(cx);
16590 let mut buffers = HashSet::default();
16591 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16592 BTreeMap::new();
16593 for selection_range in selection_ranges {
16594 for (buffer, buffer_range, _) in
16595 snapshot.range_to_buffer_ranges(selection_range)
16596 {
16597 let buffer_id = buffer.remote_id();
16598 let start = buffer.anchor_before(buffer_range.start);
16599 let end = buffer.anchor_after(buffer_range.end);
16600 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16601 buffer_id_to_ranges
16602 .entry(buffer_id)
16603 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16604 .or_insert_with(|| vec![start..end]);
16605 }
16606 }
16607 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16608 }
16609 };
16610
16611 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16612 let selections_prev = transaction_id_prev
16613 .and_then(|transaction_id_prev| {
16614 // default to selections as they were after the last edit, if we have them,
16615 // instead of how they are now.
16616 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16617 // will take you back to where you made the last edit, instead of staying where you scrolled
16618 self.selection_history
16619 .transaction(transaction_id_prev)
16620 .map(|t| t.0.clone())
16621 })
16622 .unwrap_or_else(|| self.selections.disjoint_anchors());
16623
16624 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16625 let format = project.update(cx, |project, cx| {
16626 project.format(buffers, target, true, trigger, cx)
16627 });
16628
16629 cx.spawn_in(window, async move |editor, cx| {
16630 let transaction = futures::select_biased! {
16631 transaction = format.log_err().fuse() => transaction,
16632 () = timeout => {
16633 log::warn!("timed out waiting for formatting");
16634 None
16635 }
16636 };
16637
16638 buffer
16639 .update(cx, |buffer, cx| {
16640 if let Some(transaction) = transaction
16641 && !buffer.is_singleton()
16642 {
16643 buffer.push_transaction(&transaction.0, cx);
16644 }
16645 cx.notify();
16646 })
16647 .ok();
16648
16649 if let Some(transaction_id_now) =
16650 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16651 {
16652 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16653 if has_new_transaction {
16654 _ = editor.update(cx, |editor, _| {
16655 editor
16656 .selection_history
16657 .insert_transaction(transaction_id_now, selections_prev);
16658 });
16659 }
16660 }
16661
16662 Ok(())
16663 })
16664 }
16665
16666 fn organize_imports(
16667 &mut self,
16668 _: &OrganizeImports,
16669 window: &mut Window,
16670 cx: &mut Context<Self>,
16671 ) -> Option<Task<Result<()>>> {
16672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16673 let project = match &self.project {
16674 Some(project) => project.clone(),
16675 None => return None,
16676 };
16677 Some(self.perform_code_action_kind(
16678 project,
16679 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16680 window,
16681 cx,
16682 ))
16683 }
16684
16685 fn perform_code_action_kind(
16686 &mut self,
16687 project: Entity<Project>,
16688 kind: CodeActionKind,
16689 window: &mut Window,
16690 cx: &mut Context<Self>,
16691 ) -> Task<Result<()>> {
16692 let buffer = self.buffer.clone();
16693 let buffers = buffer.read(cx).all_buffers();
16694 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16695 let apply_action = project.update(cx, |project, cx| {
16696 project.apply_code_action_kind(buffers, kind, true, cx)
16697 });
16698 cx.spawn_in(window, async move |_, cx| {
16699 let transaction = futures::select_biased! {
16700 () = timeout => {
16701 log::warn!("timed out waiting for executing code action");
16702 None
16703 }
16704 transaction = apply_action.log_err().fuse() => transaction,
16705 };
16706 buffer
16707 .update(cx, |buffer, cx| {
16708 // check if we need this
16709 if let Some(transaction) = transaction
16710 && !buffer.is_singleton()
16711 {
16712 buffer.push_transaction(&transaction.0, cx);
16713 }
16714 cx.notify();
16715 })
16716 .ok();
16717 Ok(())
16718 })
16719 }
16720
16721 pub fn restart_language_server(
16722 &mut self,
16723 _: &RestartLanguageServer,
16724 _: &mut Window,
16725 cx: &mut Context<Self>,
16726 ) {
16727 if let Some(project) = self.project.clone() {
16728 self.buffer.update(cx, |multi_buffer, cx| {
16729 project.update(cx, |project, cx| {
16730 project.restart_language_servers_for_buffers(
16731 multi_buffer.all_buffers().into_iter().collect(),
16732 HashSet::default(),
16733 cx,
16734 );
16735 });
16736 })
16737 }
16738 }
16739
16740 pub fn stop_language_server(
16741 &mut self,
16742 _: &StopLanguageServer,
16743 _: &mut Window,
16744 cx: &mut Context<Self>,
16745 ) {
16746 if let Some(project) = self.project.clone() {
16747 self.buffer.update(cx, |multi_buffer, cx| {
16748 project.update(cx, |project, cx| {
16749 project.stop_language_servers_for_buffers(
16750 multi_buffer.all_buffers().into_iter().collect(),
16751 HashSet::default(),
16752 cx,
16753 );
16754 cx.emit(project::Event::RefreshInlayHints);
16755 });
16756 });
16757 }
16758 }
16759
16760 fn cancel_language_server_work(
16761 workspace: &mut Workspace,
16762 _: &actions::CancelLanguageServerWork,
16763 _: &mut Window,
16764 cx: &mut Context<Workspace>,
16765 ) {
16766 let project = workspace.project();
16767 let buffers = workspace
16768 .active_item(cx)
16769 .and_then(|item| item.act_as::<Editor>(cx))
16770 .map_or(HashSet::default(), |editor| {
16771 editor.read(cx).buffer.read(cx).all_buffers()
16772 });
16773 project.update(cx, |project, cx| {
16774 project.cancel_language_server_work_for_buffers(buffers, cx);
16775 });
16776 }
16777
16778 fn show_character_palette(
16779 &mut self,
16780 _: &ShowCharacterPalette,
16781 window: &mut Window,
16782 _: &mut Context<Self>,
16783 ) {
16784 window.show_character_palette();
16785 }
16786
16787 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16788 if !self.diagnostics_enabled() {
16789 return;
16790 }
16791
16792 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16793 let buffer = self.buffer.read(cx).snapshot(cx);
16794 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16795 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16796 let is_valid = buffer
16797 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16798 .any(|entry| {
16799 entry.diagnostic.is_primary
16800 && !entry.range.is_empty()
16801 && entry.range.start == primary_range_start
16802 && entry.diagnostic.message == active_diagnostics.active_message
16803 });
16804
16805 if !is_valid {
16806 self.dismiss_diagnostics(cx);
16807 }
16808 }
16809 }
16810
16811 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16812 match &self.active_diagnostics {
16813 ActiveDiagnostic::Group(group) => Some(group),
16814 _ => None,
16815 }
16816 }
16817
16818 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16819 if !self.diagnostics_enabled() {
16820 return;
16821 }
16822 self.dismiss_diagnostics(cx);
16823 self.active_diagnostics = ActiveDiagnostic::All;
16824 }
16825
16826 fn activate_diagnostics(
16827 &mut self,
16828 buffer_id: BufferId,
16829 diagnostic: DiagnosticEntry<usize>,
16830 window: &mut Window,
16831 cx: &mut Context<Self>,
16832 ) {
16833 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16834 return;
16835 }
16836 self.dismiss_diagnostics(cx);
16837 let snapshot = self.snapshot(window, cx);
16838 let buffer = self.buffer.read(cx).snapshot(cx);
16839 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16840 return;
16841 };
16842
16843 let diagnostic_group = buffer
16844 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16845 .collect::<Vec<_>>();
16846
16847 let blocks =
16848 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16849
16850 let blocks = self.display_map.update(cx, |display_map, cx| {
16851 display_map.insert_blocks(blocks, cx).into_iter().collect()
16852 });
16853 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16854 active_range: buffer.anchor_before(diagnostic.range.start)
16855 ..buffer.anchor_after(diagnostic.range.end),
16856 active_message: diagnostic.diagnostic.message.clone(),
16857 group_id: diagnostic.diagnostic.group_id,
16858 blocks,
16859 });
16860 cx.notify();
16861 }
16862
16863 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16864 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16865 return;
16866 };
16867
16868 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16869 if let ActiveDiagnostic::Group(group) = prev {
16870 self.display_map.update(cx, |display_map, cx| {
16871 display_map.remove_blocks(group.blocks, cx);
16872 });
16873 cx.notify();
16874 }
16875 }
16876
16877 /// Disable inline diagnostics rendering for this editor.
16878 pub fn disable_inline_diagnostics(&mut self) {
16879 self.inline_diagnostics_enabled = false;
16880 self.inline_diagnostics_update = Task::ready(());
16881 self.inline_diagnostics.clear();
16882 }
16883
16884 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16885 self.diagnostics_enabled = false;
16886 self.dismiss_diagnostics(cx);
16887 self.inline_diagnostics_update = Task::ready(());
16888 self.inline_diagnostics.clear();
16889 }
16890
16891 pub fn diagnostics_enabled(&self) -> bool {
16892 self.diagnostics_enabled && self.mode.is_full()
16893 }
16894
16895 pub fn inline_diagnostics_enabled(&self) -> bool {
16896 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16897 }
16898
16899 pub fn show_inline_diagnostics(&self) -> bool {
16900 self.show_inline_diagnostics
16901 }
16902
16903 pub fn toggle_inline_diagnostics(
16904 &mut self,
16905 _: &ToggleInlineDiagnostics,
16906 window: &mut Window,
16907 cx: &mut Context<Editor>,
16908 ) {
16909 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16910 self.refresh_inline_diagnostics(false, window, cx);
16911 }
16912
16913 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16914 self.diagnostics_max_severity = severity;
16915 self.display_map.update(cx, |display_map, _| {
16916 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16917 });
16918 }
16919
16920 pub fn toggle_diagnostics(
16921 &mut self,
16922 _: &ToggleDiagnostics,
16923 window: &mut Window,
16924 cx: &mut Context<Editor>,
16925 ) {
16926 if !self.diagnostics_enabled() {
16927 return;
16928 }
16929
16930 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16931 EditorSettings::get_global(cx)
16932 .diagnostics_max_severity
16933 .filter(|severity| severity != &DiagnosticSeverity::Off)
16934 .unwrap_or(DiagnosticSeverity::Hint)
16935 } else {
16936 DiagnosticSeverity::Off
16937 };
16938 self.set_max_diagnostics_severity(new_severity, cx);
16939 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16940 self.active_diagnostics = ActiveDiagnostic::None;
16941 self.inline_diagnostics_update = Task::ready(());
16942 self.inline_diagnostics.clear();
16943 } else {
16944 self.refresh_inline_diagnostics(false, window, cx);
16945 }
16946
16947 cx.notify();
16948 }
16949
16950 pub fn toggle_minimap(
16951 &mut self,
16952 _: &ToggleMinimap,
16953 window: &mut Window,
16954 cx: &mut Context<Editor>,
16955 ) {
16956 if self.supports_minimap(cx) {
16957 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16958 }
16959 }
16960
16961 fn refresh_inline_diagnostics(
16962 &mut self,
16963 debounce: bool,
16964 window: &mut Window,
16965 cx: &mut Context<Self>,
16966 ) {
16967 let max_severity = ProjectSettings::get_global(cx)
16968 .diagnostics
16969 .inline
16970 .max_severity
16971 .unwrap_or(self.diagnostics_max_severity);
16972
16973 if !self.inline_diagnostics_enabled()
16974 || !self.show_inline_diagnostics
16975 || max_severity == DiagnosticSeverity::Off
16976 {
16977 self.inline_diagnostics_update = Task::ready(());
16978 self.inline_diagnostics.clear();
16979 return;
16980 }
16981
16982 let debounce_ms = ProjectSettings::get_global(cx)
16983 .diagnostics
16984 .inline
16985 .update_debounce_ms;
16986 let debounce = if debounce && debounce_ms > 0 {
16987 Some(Duration::from_millis(debounce_ms))
16988 } else {
16989 None
16990 };
16991 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16992 if let Some(debounce) = debounce {
16993 cx.background_executor().timer(debounce).await;
16994 }
16995 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16996 editor
16997 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16998 .ok()
16999 }) else {
17000 return;
17001 };
17002
17003 let new_inline_diagnostics = cx
17004 .background_spawn(async move {
17005 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17006 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17007 let message = diagnostic_entry
17008 .diagnostic
17009 .message
17010 .split_once('\n')
17011 .map(|(line, _)| line)
17012 .map(SharedString::new)
17013 .unwrap_or_else(|| {
17014 SharedString::from(diagnostic_entry.diagnostic.message)
17015 });
17016 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17017 let (Ok(i) | Err(i)) = inline_diagnostics
17018 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17019 inline_diagnostics.insert(
17020 i,
17021 (
17022 start_anchor,
17023 InlineDiagnostic {
17024 message,
17025 group_id: diagnostic_entry.diagnostic.group_id,
17026 start: diagnostic_entry.range.start.to_point(&snapshot),
17027 is_primary: diagnostic_entry.diagnostic.is_primary,
17028 severity: diagnostic_entry.diagnostic.severity,
17029 },
17030 ),
17031 );
17032 }
17033 inline_diagnostics
17034 })
17035 .await;
17036
17037 editor
17038 .update(cx, |editor, cx| {
17039 editor.inline_diagnostics = new_inline_diagnostics;
17040 cx.notify();
17041 })
17042 .ok();
17043 });
17044 }
17045
17046 fn pull_diagnostics(
17047 &mut self,
17048 buffer_id: Option<BufferId>,
17049 window: &Window,
17050 cx: &mut Context<Self>,
17051 ) -> Option<()> {
17052 if !self.mode().is_full() {
17053 return None;
17054 }
17055 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17056 .diagnostics
17057 .lsp_pull_diagnostics;
17058 if !pull_diagnostics_settings.enabled {
17059 return None;
17060 }
17061 let project = self.project()?.downgrade();
17062 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17063 let mut buffers = self.buffer.read(cx).all_buffers();
17064 if let Some(buffer_id) = buffer_id {
17065 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17066 }
17067
17068 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17069 cx.background_executor().timer(debounce).await;
17070
17071 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17072 buffers
17073 .into_iter()
17074 .filter_map(|buffer| {
17075 project
17076 .update(cx, |project, cx| {
17077 project.lsp_store().update(cx, |lsp_store, cx| {
17078 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17079 })
17080 })
17081 .ok()
17082 })
17083 .collect::<FuturesUnordered<_>>()
17084 }) else {
17085 return;
17086 };
17087
17088 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17089 match pull_task {
17090 Ok(()) => {
17091 if editor
17092 .update_in(cx, |editor, window, cx| {
17093 editor.update_diagnostics_state(window, cx);
17094 })
17095 .is_err()
17096 {
17097 return;
17098 }
17099 }
17100 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17101 }
17102 }
17103 });
17104
17105 Some(())
17106 }
17107
17108 pub fn set_selections_from_remote(
17109 &mut self,
17110 selections: Vec<Selection<Anchor>>,
17111 pending_selection: Option<Selection<Anchor>>,
17112 window: &mut Window,
17113 cx: &mut Context<Self>,
17114 ) {
17115 let old_cursor_position = self.selections.newest_anchor().head();
17116 self.selections.change_with(cx, |s| {
17117 s.select_anchors(selections);
17118 if let Some(pending_selection) = pending_selection {
17119 s.set_pending(pending_selection, SelectMode::Character);
17120 } else {
17121 s.clear_pending();
17122 }
17123 });
17124 self.selections_did_change(
17125 false,
17126 &old_cursor_position,
17127 SelectionEffects::default(),
17128 window,
17129 cx,
17130 );
17131 }
17132
17133 pub fn transact(
17134 &mut self,
17135 window: &mut Window,
17136 cx: &mut Context<Self>,
17137 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17138 ) -> Option<TransactionId> {
17139 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17140 this.start_transaction_at(Instant::now(), window, cx);
17141 update(this, window, cx);
17142 this.end_transaction_at(Instant::now(), cx)
17143 })
17144 }
17145
17146 pub fn start_transaction_at(
17147 &mut self,
17148 now: Instant,
17149 window: &mut Window,
17150 cx: &mut Context<Self>,
17151 ) -> Option<TransactionId> {
17152 self.end_selection(window, cx);
17153 if let Some(tx_id) = self
17154 .buffer
17155 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17156 {
17157 self.selection_history
17158 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17159 cx.emit(EditorEvent::TransactionBegun {
17160 transaction_id: tx_id,
17161 });
17162 Some(tx_id)
17163 } else {
17164 None
17165 }
17166 }
17167
17168 pub fn end_transaction_at(
17169 &mut self,
17170 now: Instant,
17171 cx: &mut Context<Self>,
17172 ) -> Option<TransactionId> {
17173 if let Some(transaction_id) = self
17174 .buffer
17175 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17176 {
17177 if let Some((_, end_selections)) =
17178 self.selection_history.transaction_mut(transaction_id)
17179 {
17180 *end_selections = Some(self.selections.disjoint_anchors());
17181 } else {
17182 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17183 }
17184
17185 cx.emit(EditorEvent::Edited { transaction_id });
17186 Some(transaction_id)
17187 } else {
17188 None
17189 }
17190 }
17191
17192 pub fn modify_transaction_selection_history(
17193 &mut self,
17194 transaction_id: TransactionId,
17195 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17196 ) -> bool {
17197 self.selection_history
17198 .transaction_mut(transaction_id)
17199 .map(modify)
17200 .is_some()
17201 }
17202
17203 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17204 if self.selection_mark_mode {
17205 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17206 s.move_with(|_, sel| {
17207 sel.collapse_to(sel.head(), SelectionGoal::None);
17208 });
17209 })
17210 }
17211 self.selection_mark_mode = true;
17212 cx.notify();
17213 }
17214
17215 pub fn swap_selection_ends(
17216 &mut self,
17217 _: &actions::SwapSelectionEnds,
17218 window: &mut Window,
17219 cx: &mut Context<Self>,
17220 ) {
17221 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17222 s.move_with(|_, sel| {
17223 if sel.start != sel.end {
17224 sel.reversed = !sel.reversed
17225 }
17226 });
17227 });
17228 self.request_autoscroll(Autoscroll::newest(), cx);
17229 cx.notify();
17230 }
17231
17232 pub fn toggle_focus(
17233 workspace: &mut Workspace,
17234 _: &actions::ToggleFocus,
17235 window: &mut Window,
17236 cx: &mut Context<Workspace>,
17237 ) {
17238 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17239 return;
17240 };
17241 workspace.activate_item(&item, true, true, window, cx);
17242 }
17243
17244 pub fn toggle_fold(
17245 &mut self,
17246 _: &actions::ToggleFold,
17247 window: &mut Window,
17248 cx: &mut Context<Self>,
17249 ) {
17250 if self.is_singleton(cx) {
17251 let selection = self.selections.newest::<Point>(cx);
17252
17253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17254 let range = if selection.is_empty() {
17255 let point = selection.head().to_display_point(&display_map);
17256 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17257 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17258 .to_point(&display_map);
17259 start..end
17260 } else {
17261 selection.range()
17262 };
17263 if display_map.folds_in_range(range).next().is_some() {
17264 self.unfold_lines(&Default::default(), window, cx)
17265 } else {
17266 self.fold(&Default::default(), window, cx)
17267 }
17268 } else {
17269 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17270 let buffer_ids: HashSet<_> = self
17271 .selections
17272 .disjoint_anchor_ranges()
17273 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17274 .collect();
17275
17276 let should_unfold = buffer_ids
17277 .iter()
17278 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17279
17280 for buffer_id in buffer_ids {
17281 if should_unfold {
17282 self.unfold_buffer(buffer_id, cx);
17283 } else {
17284 self.fold_buffer(buffer_id, cx);
17285 }
17286 }
17287 }
17288 }
17289
17290 pub fn toggle_fold_recursive(
17291 &mut self,
17292 _: &actions::ToggleFoldRecursive,
17293 window: &mut Window,
17294 cx: &mut Context<Self>,
17295 ) {
17296 let selection = self.selections.newest::<Point>(cx);
17297
17298 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17299 let range = if selection.is_empty() {
17300 let point = selection.head().to_display_point(&display_map);
17301 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17302 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17303 .to_point(&display_map);
17304 start..end
17305 } else {
17306 selection.range()
17307 };
17308 if display_map.folds_in_range(range).next().is_some() {
17309 self.unfold_recursive(&Default::default(), window, cx)
17310 } else {
17311 self.fold_recursive(&Default::default(), window, cx)
17312 }
17313 }
17314
17315 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17316 if self.is_singleton(cx) {
17317 let mut to_fold = Vec::new();
17318 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17319 let selections = self.selections.all_adjusted(cx);
17320
17321 for selection in selections {
17322 let range = selection.range().sorted();
17323 let buffer_start_row = range.start.row;
17324
17325 if range.start.row != range.end.row {
17326 let mut found = false;
17327 let mut row = range.start.row;
17328 while row <= range.end.row {
17329 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17330 {
17331 found = true;
17332 row = crease.range().end.row + 1;
17333 to_fold.push(crease);
17334 } else {
17335 row += 1
17336 }
17337 }
17338 if found {
17339 continue;
17340 }
17341 }
17342
17343 for row in (0..=range.start.row).rev() {
17344 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17345 && crease.range().end.row >= buffer_start_row
17346 {
17347 to_fold.push(crease);
17348 if row <= range.start.row {
17349 break;
17350 }
17351 }
17352 }
17353 }
17354
17355 self.fold_creases(to_fold, true, window, cx);
17356 } else {
17357 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17358 let buffer_ids = self
17359 .selections
17360 .disjoint_anchor_ranges()
17361 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17362 .collect::<HashSet<_>>();
17363 for buffer_id in buffer_ids {
17364 self.fold_buffer(buffer_id, cx);
17365 }
17366 }
17367 }
17368
17369 pub fn toggle_fold_all(
17370 &mut self,
17371 _: &actions::ToggleFoldAll,
17372 window: &mut Window,
17373 cx: &mut Context<Self>,
17374 ) {
17375 if self.buffer.read(cx).is_singleton() {
17376 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17377 let has_folds = display_map
17378 .folds_in_range(0..display_map.buffer_snapshot.len())
17379 .next()
17380 .is_some();
17381
17382 if has_folds {
17383 self.unfold_all(&actions::UnfoldAll, window, cx);
17384 } else {
17385 self.fold_all(&actions::FoldAll, window, cx);
17386 }
17387 } else {
17388 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17389 let should_unfold = buffer_ids
17390 .iter()
17391 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17392
17393 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17394 editor
17395 .update_in(cx, |editor, _, cx| {
17396 for buffer_id in buffer_ids {
17397 if should_unfold {
17398 editor.unfold_buffer(buffer_id, cx);
17399 } else {
17400 editor.fold_buffer(buffer_id, cx);
17401 }
17402 }
17403 })
17404 .ok();
17405 });
17406 }
17407 }
17408
17409 fn fold_at_level(
17410 &mut self,
17411 fold_at: &FoldAtLevel,
17412 window: &mut Window,
17413 cx: &mut Context<Self>,
17414 ) {
17415 if !self.buffer.read(cx).is_singleton() {
17416 return;
17417 }
17418
17419 let fold_at_level = fold_at.0;
17420 let snapshot = self.buffer.read(cx).snapshot(cx);
17421 let mut to_fold = Vec::new();
17422 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17423
17424 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17425 while start_row < end_row {
17426 match self
17427 .snapshot(window, cx)
17428 .crease_for_buffer_row(MultiBufferRow(start_row))
17429 {
17430 Some(crease) => {
17431 let nested_start_row = crease.range().start.row + 1;
17432 let nested_end_row = crease.range().end.row;
17433
17434 if current_level < fold_at_level {
17435 stack.push((nested_start_row, nested_end_row, current_level + 1));
17436 } else if current_level == fold_at_level {
17437 to_fold.push(crease);
17438 }
17439
17440 start_row = nested_end_row + 1;
17441 }
17442 None => start_row += 1,
17443 }
17444 }
17445 }
17446
17447 self.fold_creases(to_fold, true, window, cx);
17448 }
17449
17450 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17451 if self.buffer.read(cx).is_singleton() {
17452 let mut fold_ranges = Vec::new();
17453 let snapshot = self.buffer.read(cx).snapshot(cx);
17454
17455 for row in 0..snapshot.max_row().0 {
17456 if let Some(foldable_range) = self
17457 .snapshot(window, cx)
17458 .crease_for_buffer_row(MultiBufferRow(row))
17459 {
17460 fold_ranges.push(foldable_range);
17461 }
17462 }
17463
17464 self.fold_creases(fold_ranges, true, window, cx);
17465 } else {
17466 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17467 editor
17468 .update_in(cx, |editor, _, cx| {
17469 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17470 editor.fold_buffer(buffer_id, cx);
17471 }
17472 })
17473 .ok();
17474 });
17475 }
17476 }
17477
17478 pub fn fold_function_bodies(
17479 &mut self,
17480 _: &actions::FoldFunctionBodies,
17481 window: &mut Window,
17482 cx: &mut Context<Self>,
17483 ) {
17484 let snapshot = self.buffer.read(cx).snapshot(cx);
17485
17486 let ranges = snapshot
17487 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17488 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17489 .collect::<Vec<_>>();
17490
17491 let creases = ranges
17492 .into_iter()
17493 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17494 .collect();
17495
17496 self.fold_creases(creases, true, window, cx);
17497 }
17498
17499 pub fn fold_recursive(
17500 &mut self,
17501 _: &actions::FoldRecursive,
17502 window: &mut Window,
17503 cx: &mut Context<Self>,
17504 ) {
17505 let mut to_fold = Vec::new();
17506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17507 let selections = self.selections.all_adjusted(cx);
17508
17509 for selection in selections {
17510 let range = selection.range().sorted();
17511 let buffer_start_row = range.start.row;
17512
17513 if range.start.row != range.end.row {
17514 let mut found = false;
17515 for row in range.start.row..=range.end.row {
17516 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17517 found = true;
17518 to_fold.push(crease);
17519 }
17520 }
17521 if found {
17522 continue;
17523 }
17524 }
17525
17526 for row in (0..=range.start.row).rev() {
17527 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17528 if crease.range().end.row >= buffer_start_row {
17529 to_fold.push(crease);
17530 } else {
17531 break;
17532 }
17533 }
17534 }
17535 }
17536
17537 self.fold_creases(to_fold, true, window, cx);
17538 }
17539
17540 pub fn fold_at(
17541 &mut self,
17542 buffer_row: MultiBufferRow,
17543 window: &mut Window,
17544 cx: &mut Context<Self>,
17545 ) {
17546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17547
17548 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17549 let autoscroll = self
17550 .selections
17551 .all::<Point>(cx)
17552 .iter()
17553 .any(|selection| crease.range().overlaps(&selection.range()));
17554
17555 self.fold_creases(vec![crease], autoscroll, window, cx);
17556 }
17557 }
17558
17559 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17560 if self.is_singleton(cx) {
17561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17562 let buffer = &display_map.buffer_snapshot;
17563 let selections = self.selections.all::<Point>(cx);
17564 let ranges = selections
17565 .iter()
17566 .map(|s| {
17567 let range = s.display_range(&display_map).sorted();
17568 let mut start = range.start.to_point(&display_map);
17569 let mut end = range.end.to_point(&display_map);
17570 start.column = 0;
17571 end.column = buffer.line_len(MultiBufferRow(end.row));
17572 start..end
17573 })
17574 .collect::<Vec<_>>();
17575
17576 self.unfold_ranges(&ranges, true, true, cx);
17577 } else {
17578 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17579 let buffer_ids = self
17580 .selections
17581 .disjoint_anchor_ranges()
17582 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17583 .collect::<HashSet<_>>();
17584 for buffer_id in buffer_ids {
17585 self.unfold_buffer(buffer_id, cx);
17586 }
17587 }
17588 }
17589
17590 pub fn unfold_recursive(
17591 &mut self,
17592 _: &UnfoldRecursive,
17593 _window: &mut Window,
17594 cx: &mut Context<Self>,
17595 ) {
17596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17597 let selections = self.selections.all::<Point>(cx);
17598 let ranges = selections
17599 .iter()
17600 .map(|s| {
17601 let mut range = s.display_range(&display_map).sorted();
17602 *range.start.column_mut() = 0;
17603 *range.end.column_mut() = display_map.line_len(range.end.row());
17604 let start = range.start.to_point(&display_map);
17605 let end = range.end.to_point(&display_map);
17606 start..end
17607 })
17608 .collect::<Vec<_>>();
17609
17610 self.unfold_ranges(&ranges, true, true, cx);
17611 }
17612
17613 pub fn unfold_at(
17614 &mut self,
17615 buffer_row: MultiBufferRow,
17616 _window: &mut Window,
17617 cx: &mut Context<Self>,
17618 ) {
17619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17620
17621 let intersection_range = Point::new(buffer_row.0, 0)
17622 ..Point::new(
17623 buffer_row.0,
17624 display_map.buffer_snapshot.line_len(buffer_row),
17625 );
17626
17627 let autoscroll = self
17628 .selections
17629 .all::<Point>(cx)
17630 .iter()
17631 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17632
17633 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17634 }
17635
17636 pub fn unfold_all(
17637 &mut self,
17638 _: &actions::UnfoldAll,
17639 _window: &mut Window,
17640 cx: &mut Context<Self>,
17641 ) {
17642 if self.buffer.read(cx).is_singleton() {
17643 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17644 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17645 } else {
17646 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17647 editor
17648 .update(cx, |editor, cx| {
17649 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17650 editor.unfold_buffer(buffer_id, cx);
17651 }
17652 })
17653 .ok();
17654 });
17655 }
17656 }
17657
17658 pub fn fold_selected_ranges(
17659 &mut self,
17660 _: &FoldSelectedRanges,
17661 window: &mut Window,
17662 cx: &mut Context<Self>,
17663 ) {
17664 let selections = self.selections.all_adjusted(cx);
17665 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17666 let ranges = selections
17667 .into_iter()
17668 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17669 .collect::<Vec<_>>();
17670 self.fold_creases(ranges, true, window, cx);
17671 }
17672
17673 pub fn fold_ranges<T: ToOffset + Clone>(
17674 &mut self,
17675 ranges: Vec<Range<T>>,
17676 auto_scroll: bool,
17677 window: &mut Window,
17678 cx: &mut Context<Self>,
17679 ) {
17680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17681 let ranges = ranges
17682 .into_iter()
17683 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17684 .collect::<Vec<_>>();
17685 self.fold_creases(ranges, auto_scroll, window, cx);
17686 }
17687
17688 pub fn fold_creases<T: ToOffset + Clone>(
17689 &mut self,
17690 creases: Vec<Crease<T>>,
17691 auto_scroll: bool,
17692 _window: &mut Window,
17693 cx: &mut Context<Self>,
17694 ) {
17695 if creases.is_empty() {
17696 return;
17697 }
17698
17699 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17700
17701 if auto_scroll {
17702 self.request_autoscroll(Autoscroll::fit(), cx);
17703 }
17704
17705 cx.notify();
17706
17707 self.scrollbar_marker_state.dirty = true;
17708 self.folds_did_change(cx);
17709 }
17710
17711 /// Removes any folds whose ranges intersect any of the given ranges.
17712 pub fn unfold_ranges<T: ToOffset + Clone>(
17713 &mut self,
17714 ranges: &[Range<T>],
17715 inclusive: bool,
17716 auto_scroll: bool,
17717 cx: &mut Context<Self>,
17718 ) {
17719 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17720 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17721 });
17722 self.folds_did_change(cx);
17723 }
17724
17725 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17726 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17727 return;
17728 }
17729 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17730 self.display_map.update(cx, |display_map, cx| {
17731 display_map.fold_buffers([buffer_id], cx)
17732 });
17733 cx.emit(EditorEvent::BufferFoldToggled {
17734 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17735 folded: true,
17736 });
17737 cx.notify();
17738 }
17739
17740 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17741 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17742 return;
17743 }
17744 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17745 self.display_map.update(cx, |display_map, cx| {
17746 display_map.unfold_buffers([buffer_id], cx);
17747 });
17748 cx.emit(EditorEvent::BufferFoldToggled {
17749 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17750 folded: false,
17751 });
17752 cx.notify();
17753 }
17754
17755 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17756 self.display_map.read(cx).is_buffer_folded(buffer)
17757 }
17758
17759 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17760 self.display_map.read(cx).folded_buffers()
17761 }
17762
17763 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17764 self.display_map.update(cx, |display_map, cx| {
17765 display_map.disable_header_for_buffer(buffer_id, cx);
17766 });
17767 cx.notify();
17768 }
17769
17770 /// Removes any folds with the given ranges.
17771 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17772 &mut self,
17773 ranges: &[Range<T>],
17774 type_id: TypeId,
17775 auto_scroll: bool,
17776 cx: &mut Context<Self>,
17777 ) {
17778 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17779 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17780 });
17781 self.folds_did_change(cx);
17782 }
17783
17784 fn remove_folds_with<T: ToOffset + Clone>(
17785 &mut self,
17786 ranges: &[Range<T>],
17787 auto_scroll: bool,
17788 cx: &mut Context<Self>,
17789 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17790 ) {
17791 if ranges.is_empty() {
17792 return;
17793 }
17794
17795 let mut buffers_affected = HashSet::default();
17796 let multi_buffer = self.buffer().read(cx);
17797 for range in ranges {
17798 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17799 buffers_affected.insert(buffer.read(cx).remote_id());
17800 };
17801 }
17802
17803 self.display_map.update(cx, update);
17804
17805 if auto_scroll {
17806 self.request_autoscroll(Autoscroll::fit(), cx);
17807 }
17808
17809 cx.notify();
17810 self.scrollbar_marker_state.dirty = true;
17811 self.active_indent_guides_state.dirty = true;
17812 }
17813
17814 pub fn update_renderer_widths(
17815 &mut self,
17816 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17817 cx: &mut Context<Self>,
17818 ) -> bool {
17819 self.display_map
17820 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17821 }
17822
17823 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17824 self.display_map.read(cx).fold_placeholder.clone()
17825 }
17826
17827 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17828 self.buffer.update(cx, |buffer, cx| {
17829 buffer.set_all_diff_hunks_expanded(cx);
17830 });
17831 }
17832
17833 pub fn expand_all_diff_hunks(
17834 &mut self,
17835 _: &ExpandAllDiffHunks,
17836 _window: &mut Window,
17837 cx: &mut Context<Self>,
17838 ) {
17839 self.buffer.update(cx, |buffer, cx| {
17840 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17841 });
17842 }
17843
17844 pub fn toggle_selected_diff_hunks(
17845 &mut self,
17846 _: &ToggleSelectedDiffHunks,
17847 _window: &mut Window,
17848 cx: &mut Context<Self>,
17849 ) {
17850 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17851 self.toggle_diff_hunks_in_ranges(ranges, cx);
17852 }
17853
17854 pub fn diff_hunks_in_ranges<'a>(
17855 &'a self,
17856 ranges: &'a [Range<Anchor>],
17857 buffer: &'a MultiBufferSnapshot,
17858 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17859 ranges.iter().flat_map(move |range| {
17860 let end_excerpt_id = range.end.excerpt_id;
17861 let range = range.to_point(buffer);
17862 let mut peek_end = range.end;
17863 if range.end.row < buffer.max_row().0 {
17864 peek_end = Point::new(range.end.row + 1, 0);
17865 }
17866 buffer
17867 .diff_hunks_in_range(range.start..peek_end)
17868 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17869 })
17870 }
17871
17872 pub fn has_stageable_diff_hunks_in_ranges(
17873 &self,
17874 ranges: &[Range<Anchor>],
17875 snapshot: &MultiBufferSnapshot,
17876 ) -> bool {
17877 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17878 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17879 }
17880
17881 pub fn toggle_staged_selected_diff_hunks(
17882 &mut self,
17883 _: &::git::ToggleStaged,
17884 _: &mut Window,
17885 cx: &mut Context<Self>,
17886 ) {
17887 let snapshot = self.buffer.read(cx).snapshot(cx);
17888 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17889 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17890 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17891 }
17892
17893 pub fn set_render_diff_hunk_controls(
17894 &mut self,
17895 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17896 cx: &mut Context<Self>,
17897 ) {
17898 self.render_diff_hunk_controls = render_diff_hunk_controls;
17899 cx.notify();
17900 }
17901
17902 pub fn stage_and_next(
17903 &mut self,
17904 _: &::git::StageAndNext,
17905 window: &mut Window,
17906 cx: &mut Context<Self>,
17907 ) {
17908 self.do_stage_or_unstage_and_next(true, window, cx);
17909 }
17910
17911 pub fn unstage_and_next(
17912 &mut self,
17913 _: &::git::UnstageAndNext,
17914 window: &mut Window,
17915 cx: &mut Context<Self>,
17916 ) {
17917 self.do_stage_or_unstage_and_next(false, window, cx);
17918 }
17919
17920 pub fn stage_or_unstage_diff_hunks(
17921 &mut self,
17922 stage: bool,
17923 ranges: Vec<Range<Anchor>>,
17924 cx: &mut Context<Self>,
17925 ) {
17926 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17927 cx.spawn(async move |this, cx| {
17928 task.await?;
17929 this.update(cx, |this, cx| {
17930 let snapshot = this.buffer.read(cx).snapshot(cx);
17931 let chunk_by = this
17932 .diff_hunks_in_ranges(&ranges, &snapshot)
17933 .chunk_by(|hunk| hunk.buffer_id);
17934 for (buffer_id, hunks) in &chunk_by {
17935 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17936 }
17937 })
17938 })
17939 .detach_and_log_err(cx);
17940 }
17941
17942 fn save_buffers_for_ranges_if_needed(
17943 &mut self,
17944 ranges: &[Range<Anchor>],
17945 cx: &mut Context<Editor>,
17946 ) -> Task<Result<()>> {
17947 let multibuffer = self.buffer.read(cx);
17948 let snapshot = multibuffer.read(cx);
17949 let buffer_ids: HashSet<_> = ranges
17950 .iter()
17951 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17952 .collect();
17953 drop(snapshot);
17954
17955 let mut buffers = HashSet::default();
17956 for buffer_id in buffer_ids {
17957 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17958 let buffer = buffer_entity.read(cx);
17959 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17960 {
17961 buffers.insert(buffer_entity);
17962 }
17963 }
17964 }
17965
17966 if let Some(project) = &self.project {
17967 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17968 } else {
17969 Task::ready(Ok(()))
17970 }
17971 }
17972
17973 fn do_stage_or_unstage_and_next(
17974 &mut self,
17975 stage: bool,
17976 window: &mut Window,
17977 cx: &mut Context<Self>,
17978 ) {
17979 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17980
17981 if ranges.iter().any(|range| range.start != range.end) {
17982 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17983 return;
17984 }
17985
17986 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17987 let snapshot = self.snapshot(window, cx);
17988 let position = self.selections.newest::<Point>(cx).head();
17989 let mut row = snapshot
17990 .buffer_snapshot
17991 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17992 .find(|hunk| hunk.row_range.start.0 > position.row)
17993 .map(|hunk| hunk.row_range.start);
17994
17995 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17996 // Outside of the project diff editor, wrap around to the beginning.
17997 if !all_diff_hunks_expanded {
17998 row = row.or_else(|| {
17999 snapshot
18000 .buffer_snapshot
18001 .diff_hunks_in_range(Point::zero()..position)
18002 .find(|hunk| hunk.row_range.end.0 < position.row)
18003 .map(|hunk| hunk.row_range.start)
18004 });
18005 }
18006
18007 if let Some(row) = row {
18008 let destination = Point::new(row.0, 0);
18009 let autoscroll = Autoscroll::center();
18010
18011 self.unfold_ranges(&[destination..destination], false, false, cx);
18012 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18013 s.select_ranges([destination..destination]);
18014 });
18015 }
18016 }
18017
18018 fn do_stage_or_unstage(
18019 &self,
18020 stage: bool,
18021 buffer_id: BufferId,
18022 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18023 cx: &mut App,
18024 ) -> Option<()> {
18025 let project = self.project()?;
18026 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18027 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18028 let buffer_snapshot = buffer.read(cx).snapshot();
18029 let file_exists = buffer_snapshot
18030 .file()
18031 .is_some_and(|file| file.disk_state().exists());
18032 diff.update(cx, |diff, cx| {
18033 diff.stage_or_unstage_hunks(
18034 stage,
18035 &hunks
18036 .map(|hunk| buffer_diff::DiffHunk {
18037 buffer_range: hunk.buffer_range,
18038 diff_base_byte_range: hunk.diff_base_byte_range,
18039 secondary_status: hunk.secondary_status,
18040 range: Point::zero()..Point::zero(), // unused
18041 })
18042 .collect::<Vec<_>>(),
18043 &buffer_snapshot,
18044 file_exists,
18045 cx,
18046 )
18047 });
18048 None
18049 }
18050
18051 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18052 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18053 self.buffer
18054 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18055 }
18056
18057 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18058 self.buffer.update(cx, |buffer, cx| {
18059 let ranges = vec![Anchor::min()..Anchor::max()];
18060 if !buffer.all_diff_hunks_expanded()
18061 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18062 {
18063 buffer.collapse_diff_hunks(ranges, cx);
18064 true
18065 } else {
18066 false
18067 }
18068 })
18069 }
18070
18071 fn toggle_diff_hunks_in_ranges(
18072 &mut self,
18073 ranges: Vec<Range<Anchor>>,
18074 cx: &mut Context<Editor>,
18075 ) {
18076 self.buffer.update(cx, |buffer, cx| {
18077 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18078 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18079 })
18080 }
18081
18082 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18083 self.buffer.update(cx, |buffer, cx| {
18084 let snapshot = buffer.snapshot(cx);
18085 let excerpt_id = range.end.excerpt_id;
18086 let point_range = range.to_point(&snapshot);
18087 let expand = !buffer.single_hunk_is_expanded(range, cx);
18088 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18089 })
18090 }
18091
18092 pub(crate) fn apply_all_diff_hunks(
18093 &mut self,
18094 _: &ApplyAllDiffHunks,
18095 window: &mut Window,
18096 cx: &mut Context<Self>,
18097 ) {
18098 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18099
18100 let buffers = self.buffer.read(cx).all_buffers();
18101 for branch_buffer in buffers {
18102 branch_buffer.update(cx, |branch_buffer, cx| {
18103 branch_buffer.merge_into_base(Vec::new(), cx);
18104 });
18105 }
18106
18107 if let Some(project) = self.project.clone() {
18108 self.save(
18109 SaveOptions {
18110 format: true,
18111 autosave: false,
18112 },
18113 project,
18114 window,
18115 cx,
18116 )
18117 .detach_and_log_err(cx);
18118 }
18119 }
18120
18121 pub(crate) fn apply_selected_diff_hunks(
18122 &mut self,
18123 _: &ApplyDiffHunk,
18124 window: &mut Window,
18125 cx: &mut Context<Self>,
18126 ) {
18127 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18128 let snapshot = self.snapshot(window, cx);
18129 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18130 let mut ranges_by_buffer = HashMap::default();
18131 self.transact(window, cx, |editor, _window, cx| {
18132 for hunk in hunks {
18133 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18134 ranges_by_buffer
18135 .entry(buffer.clone())
18136 .or_insert_with(Vec::new)
18137 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18138 }
18139 }
18140
18141 for (buffer, ranges) in ranges_by_buffer {
18142 buffer.update(cx, |buffer, cx| {
18143 buffer.merge_into_base(ranges, cx);
18144 });
18145 }
18146 });
18147
18148 if let Some(project) = self.project.clone() {
18149 self.save(
18150 SaveOptions {
18151 format: true,
18152 autosave: false,
18153 },
18154 project,
18155 window,
18156 cx,
18157 )
18158 .detach_and_log_err(cx);
18159 }
18160 }
18161
18162 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18163 if hovered != self.gutter_hovered {
18164 self.gutter_hovered = hovered;
18165 cx.notify();
18166 }
18167 }
18168
18169 pub fn insert_blocks(
18170 &mut self,
18171 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18172 autoscroll: Option<Autoscroll>,
18173 cx: &mut Context<Self>,
18174 ) -> Vec<CustomBlockId> {
18175 let blocks = self
18176 .display_map
18177 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18178 if let Some(autoscroll) = autoscroll {
18179 self.request_autoscroll(autoscroll, cx);
18180 }
18181 cx.notify();
18182 blocks
18183 }
18184
18185 pub fn resize_blocks(
18186 &mut self,
18187 heights: HashMap<CustomBlockId, u32>,
18188 autoscroll: Option<Autoscroll>,
18189 cx: &mut Context<Self>,
18190 ) {
18191 self.display_map
18192 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18193 if let Some(autoscroll) = autoscroll {
18194 self.request_autoscroll(autoscroll, cx);
18195 }
18196 cx.notify();
18197 }
18198
18199 pub fn replace_blocks(
18200 &mut self,
18201 renderers: HashMap<CustomBlockId, RenderBlock>,
18202 autoscroll: Option<Autoscroll>,
18203 cx: &mut Context<Self>,
18204 ) {
18205 self.display_map
18206 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18207 if let Some(autoscroll) = autoscroll {
18208 self.request_autoscroll(autoscroll, cx);
18209 }
18210 cx.notify();
18211 }
18212
18213 pub fn remove_blocks(
18214 &mut self,
18215 block_ids: HashSet<CustomBlockId>,
18216 autoscroll: Option<Autoscroll>,
18217 cx: &mut Context<Self>,
18218 ) {
18219 self.display_map.update(cx, |display_map, cx| {
18220 display_map.remove_blocks(block_ids, cx)
18221 });
18222 if let Some(autoscroll) = autoscroll {
18223 self.request_autoscroll(autoscroll, cx);
18224 }
18225 cx.notify();
18226 }
18227
18228 pub fn row_for_block(
18229 &self,
18230 block_id: CustomBlockId,
18231 cx: &mut Context<Self>,
18232 ) -> Option<DisplayRow> {
18233 self.display_map
18234 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18235 }
18236
18237 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18238 self.focused_block = Some(focused_block);
18239 }
18240
18241 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18242 self.focused_block.take()
18243 }
18244
18245 pub fn insert_creases(
18246 &mut self,
18247 creases: impl IntoIterator<Item = Crease<Anchor>>,
18248 cx: &mut Context<Self>,
18249 ) -> Vec<CreaseId> {
18250 self.display_map
18251 .update(cx, |map, cx| map.insert_creases(creases, cx))
18252 }
18253
18254 pub fn remove_creases(
18255 &mut self,
18256 ids: impl IntoIterator<Item = CreaseId>,
18257 cx: &mut Context<Self>,
18258 ) -> Vec<(CreaseId, Range<Anchor>)> {
18259 self.display_map
18260 .update(cx, |map, cx| map.remove_creases(ids, cx))
18261 }
18262
18263 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18264 self.display_map
18265 .update(cx, |map, cx| map.snapshot(cx))
18266 .longest_row()
18267 }
18268
18269 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18270 self.display_map
18271 .update(cx, |map, cx| map.snapshot(cx))
18272 .max_point()
18273 }
18274
18275 pub fn text(&self, cx: &App) -> String {
18276 self.buffer.read(cx).read(cx).text()
18277 }
18278
18279 pub fn is_empty(&self, cx: &App) -> bool {
18280 self.buffer.read(cx).read(cx).is_empty()
18281 }
18282
18283 pub fn text_option(&self, cx: &App) -> Option<String> {
18284 let text = self.text(cx);
18285 let text = text.trim();
18286
18287 if text.is_empty() {
18288 return None;
18289 }
18290
18291 Some(text.to_string())
18292 }
18293
18294 pub fn set_text(
18295 &mut self,
18296 text: impl Into<Arc<str>>,
18297 window: &mut Window,
18298 cx: &mut Context<Self>,
18299 ) {
18300 self.transact(window, cx, |this, _, cx| {
18301 this.buffer
18302 .read(cx)
18303 .as_singleton()
18304 .expect("you can only call set_text on editors for singleton buffers")
18305 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18306 });
18307 }
18308
18309 pub fn display_text(&self, cx: &mut App) -> String {
18310 self.display_map
18311 .update(cx, |map, cx| map.snapshot(cx))
18312 .text()
18313 }
18314
18315 fn create_minimap(
18316 &self,
18317 minimap_settings: MinimapSettings,
18318 window: &mut Window,
18319 cx: &mut Context<Self>,
18320 ) -> Option<Entity<Self>> {
18321 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18322 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18323 }
18324
18325 fn initialize_new_minimap(
18326 &self,
18327 minimap_settings: MinimapSettings,
18328 window: &mut Window,
18329 cx: &mut Context<Self>,
18330 ) -> Entity<Self> {
18331 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18332
18333 let mut minimap = Editor::new_internal(
18334 EditorMode::Minimap {
18335 parent: cx.weak_entity(),
18336 },
18337 self.buffer.clone(),
18338 None,
18339 Some(self.display_map.clone()),
18340 window,
18341 cx,
18342 );
18343 minimap.scroll_manager.clone_state(&self.scroll_manager);
18344 minimap.set_text_style_refinement(TextStyleRefinement {
18345 font_size: Some(MINIMAP_FONT_SIZE),
18346 font_weight: Some(MINIMAP_FONT_WEIGHT),
18347 ..Default::default()
18348 });
18349 minimap.update_minimap_configuration(minimap_settings, cx);
18350 cx.new(|_| minimap)
18351 }
18352
18353 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18354 let current_line_highlight = minimap_settings
18355 .current_line_highlight
18356 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18357 self.set_current_line_highlight(Some(current_line_highlight));
18358 }
18359
18360 pub fn minimap(&self) -> Option<&Entity<Self>> {
18361 self.minimap
18362 .as_ref()
18363 .filter(|_| self.minimap_visibility.visible())
18364 }
18365
18366 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18367 let mut wrap_guides = smallvec![];
18368
18369 if self.show_wrap_guides == Some(false) {
18370 return wrap_guides;
18371 }
18372
18373 let settings = self.buffer.read(cx).language_settings(cx);
18374 if settings.show_wrap_guides {
18375 match self.soft_wrap_mode(cx) {
18376 SoftWrap::Column(soft_wrap) => {
18377 wrap_guides.push((soft_wrap as usize, true));
18378 }
18379 SoftWrap::Bounded(soft_wrap) => {
18380 wrap_guides.push((soft_wrap as usize, true));
18381 }
18382 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18383 }
18384 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18385 }
18386
18387 wrap_guides
18388 }
18389
18390 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18391 let settings = self.buffer.read(cx).language_settings(cx);
18392 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18393 match mode {
18394 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18395 SoftWrap::None
18396 }
18397 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18398 language_settings::SoftWrap::PreferredLineLength => {
18399 SoftWrap::Column(settings.preferred_line_length)
18400 }
18401 language_settings::SoftWrap::Bounded => {
18402 SoftWrap::Bounded(settings.preferred_line_length)
18403 }
18404 }
18405 }
18406
18407 pub fn set_soft_wrap_mode(
18408 &mut self,
18409 mode: language_settings::SoftWrap,
18410
18411 cx: &mut Context<Self>,
18412 ) {
18413 self.soft_wrap_mode_override = Some(mode);
18414 cx.notify();
18415 }
18416
18417 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18418 self.hard_wrap = hard_wrap;
18419 cx.notify();
18420 }
18421
18422 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18423 self.text_style_refinement = Some(style);
18424 }
18425
18426 /// called by the Element so we know what style we were most recently rendered with.
18427 pub(crate) fn set_style(
18428 &mut self,
18429 style: EditorStyle,
18430 window: &mut Window,
18431 cx: &mut Context<Self>,
18432 ) {
18433 // We intentionally do not inform the display map about the minimap style
18434 // so that wrapping is not recalculated and stays consistent for the editor
18435 // and its linked minimap.
18436 if !self.mode.is_minimap() {
18437 let rem_size = window.rem_size();
18438 self.display_map.update(cx, |map, cx| {
18439 map.set_font(
18440 style.text.font(),
18441 style.text.font_size.to_pixels(rem_size),
18442 cx,
18443 )
18444 });
18445 }
18446 self.style = Some(style);
18447 }
18448
18449 pub fn style(&self) -> Option<&EditorStyle> {
18450 self.style.as_ref()
18451 }
18452
18453 // Called by the element. This method is not designed to be called outside of the editor
18454 // element's layout code because it does not notify when rewrapping is computed synchronously.
18455 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18456 self.display_map
18457 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18458 }
18459
18460 pub fn set_soft_wrap(&mut self) {
18461 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18462 }
18463
18464 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18465 if self.soft_wrap_mode_override.is_some() {
18466 self.soft_wrap_mode_override.take();
18467 } else {
18468 let soft_wrap = match self.soft_wrap_mode(cx) {
18469 SoftWrap::GitDiff => return,
18470 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18471 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18472 language_settings::SoftWrap::None
18473 }
18474 };
18475 self.soft_wrap_mode_override = Some(soft_wrap);
18476 }
18477 cx.notify();
18478 }
18479
18480 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18481 let Some(workspace) = self.workspace() else {
18482 return;
18483 };
18484 let fs = workspace.read(cx).app_state().fs.clone();
18485 let current_show = TabBarSettings::get_global(cx).show;
18486 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18487 setting.show = Some(!current_show);
18488 });
18489 }
18490
18491 pub fn toggle_indent_guides(
18492 &mut self,
18493 _: &ToggleIndentGuides,
18494 _: &mut Window,
18495 cx: &mut Context<Self>,
18496 ) {
18497 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18498 self.buffer
18499 .read(cx)
18500 .language_settings(cx)
18501 .indent_guides
18502 .enabled
18503 });
18504 self.show_indent_guides = Some(!currently_enabled);
18505 cx.notify();
18506 }
18507
18508 fn should_show_indent_guides(&self) -> Option<bool> {
18509 self.show_indent_guides
18510 }
18511
18512 pub fn toggle_line_numbers(
18513 &mut self,
18514 _: &ToggleLineNumbers,
18515 _: &mut Window,
18516 cx: &mut Context<Self>,
18517 ) {
18518 let mut editor_settings = EditorSettings::get_global(cx).clone();
18519 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18520 EditorSettings::override_global(editor_settings, cx);
18521 }
18522
18523 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18524 if let Some(show_line_numbers) = self.show_line_numbers {
18525 return show_line_numbers;
18526 }
18527 EditorSettings::get_global(cx).gutter.line_numbers
18528 }
18529
18530 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18531 self.use_relative_line_numbers
18532 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18533 }
18534
18535 pub fn toggle_relative_line_numbers(
18536 &mut self,
18537 _: &ToggleRelativeLineNumbers,
18538 _: &mut Window,
18539 cx: &mut Context<Self>,
18540 ) {
18541 let is_relative = self.should_use_relative_line_numbers(cx);
18542 self.set_relative_line_number(Some(!is_relative), cx)
18543 }
18544
18545 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18546 self.use_relative_line_numbers = is_relative;
18547 cx.notify();
18548 }
18549
18550 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18551 self.show_gutter = show_gutter;
18552 cx.notify();
18553 }
18554
18555 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18556 self.show_scrollbars = ScrollbarAxes {
18557 horizontal: show,
18558 vertical: show,
18559 };
18560 cx.notify();
18561 }
18562
18563 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18564 self.show_scrollbars.vertical = show;
18565 cx.notify();
18566 }
18567
18568 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18569 self.show_scrollbars.horizontal = show;
18570 cx.notify();
18571 }
18572
18573 pub fn set_minimap_visibility(
18574 &mut self,
18575 minimap_visibility: MinimapVisibility,
18576 window: &mut Window,
18577 cx: &mut Context<Self>,
18578 ) {
18579 if self.minimap_visibility != minimap_visibility {
18580 if minimap_visibility.visible() && self.minimap.is_none() {
18581 let minimap_settings = EditorSettings::get_global(cx).minimap;
18582 self.minimap =
18583 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18584 }
18585 self.minimap_visibility = minimap_visibility;
18586 cx.notify();
18587 }
18588 }
18589
18590 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18591 self.set_show_scrollbars(false, cx);
18592 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18593 }
18594
18595 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18596 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18597 }
18598
18599 /// Normally the text in full mode and auto height editors is padded on the
18600 /// left side by roughly half a character width for improved hit testing.
18601 ///
18602 /// Use this method to disable this for cases where this is not wanted (e.g.
18603 /// if you want to align the editor text with some other text above or below)
18604 /// or if you want to add this padding to single-line editors.
18605 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18606 self.offset_content = offset_content;
18607 cx.notify();
18608 }
18609
18610 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18611 self.show_line_numbers = Some(show_line_numbers);
18612 cx.notify();
18613 }
18614
18615 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18616 self.disable_expand_excerpt_buttons = true;
18617 cx.notify();
18618 }
18619
18620 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18621 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18622 cx.notify();
18623 }
18624
18625 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18626 self.show_code_actions = Some(show_code_actions);
18627 cx.notify();
18628 }
18629
18630 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18631 self.show_runnables = Some(show_runnables);
18632 cx.notify();
18633 }
18634
18635 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18636 self.show_breakpoints = Some(show_breakpoints);
18637 cx.notify();
18638 }
18639
18640 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18641 if self.display_map.read(cx).masked != masked {
18642 self.display_map.update(cx, |map, _| map.masked = masked);
18643 }
18644 cx.notify()
18645 }
18646
18647 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18648 self.show_wrap_guides = Some(show_wrap_guides);
18649 cx.notify();
18650 }
18651
18652 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18653 self.show_indent_guides = Some(show_indent_guides);
18654 cx.notify();
18655 }
18656
18657 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18658 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18659 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18660 && let Some(dir) = file.abs_path(cx).parent()
18661 {
18662 return Some(dir.to_owned());
18663 }
18664
18665 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18666 return Some(project_path.path.to_path_buf());
18667 }
18668 }
18669
18670 None
18671 }
18672
18673 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18674 self.active_excerpt(cx)?
18675 .1
18676 .read(cx)
18677 .file()
18678 .and_then(|f| f.as_local())
18679 }
18680
18681 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18682 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18683 let buffer = buffer.read(cx);
18684 if let Some(project_path) = buffer.project_path(cx) {
18685 let project = self.project()?.read(cx);
18686 project.absolute_path(&project_path, cx)
18687 } else {
18688 buffer
18689 .file()
18690 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18691 }
18692 })
18693 }
18694
18695 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18696 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18697 let project_path = buffer.read(cx).project_path(cx)?;
18698 let project = self.project()?.read(cx);
18699 let entry = project.entry_for_path(&project_path, cx)?;
18700 let path = entry.path.to_path_buf();
18701 Some(path)
18702 })
18703 }
18704
18705 pub fn reveal_in_finder(
18706 &mut self,
18707 _: &RevealInFileManager,
18708 _window: &mut Window,
18709 cx: &mut Context<Self>,
18710 ) {
18711 if let Some(target) = self.target_file(cx) {
18712 cx.reveal_path(&target.abs_path(cx));
18713 }
18714 }
18715
18716 pub fn copy_path(
18717 &mut self,
18718 _: &zed_actions::workspace::CopyPath,
18719 _window: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) {
18722 if let Some(path) = self.target_file_abs_path(cx)
18723 && let Some(path) = path.to_str()
18724 {
18725 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18726 }
18727 }
18728
18729 pub fn copy_relative_path(
18730 &mut self,
18731 _: &zed_actions::workspace::CopyRelativePath,
18732 _window: &mut Window,
18733 cx: &mut Context<Self>,
18734 ) {
18735 if let Some(path) = self.target_file_path(cx)
18736 && let Some(path) = path.to_str()
18737 {
18738 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18739 }
18740 }
18741
18742 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18743 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18744 buffer.read(cx).project_path(cx)
18745 } else {
18746 None
18747 }
18748 }
18749
18750 // Returns true if the editor handled a go-to-line request
18751 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18752 maybe!({
18753 let breakpoint_store = self.breakpoint_store.as_ref()?;
18754
18755 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18756 else {
18757 self.clear_row_highlights::<ActiveDebugLine>();
18758 return None;
18759 };
18760
18761 let position = active_stack_frame.position;
18762 let buffer_id = position.buffer_id?;
18763 let snapshot = self
18764 .project
18765 .as_ref()?
18766 .read(cx)
18767 .buffer_for_id(buffer_id, cx)?
18768 .read(cx)
18769 .snapshot();
18770
18771 let mut handled = false;
18772 for (id, ExcerptRange { context, .. }) in
18773 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18774 {
18775 if context.start.cmp(&position, &snapshot).is_ge()
18776 || context.end.cmp(&position, &snapshot).is_lt()
18777 {
18778 continue;
18779 }
18780 let snapshot = self.buffer.read(cx).snapshot(cx);
18781 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18782
18783 handled = true;
18784 self.clear_row_highlights::<ActiveDebugLine>();
18785
18786 self.go_to_line::<ActiveDebugLine>(
18787 multibuffer_anchor,
18788 Some(cx.theme().colors().editor_debugger_active_line_background),
18789 window,
18790 cx,
18791 );
18792
18793 cx.notify();
18794 }
18795
18796 handled.then_some(())
18797 })
18798 .is_some()
18799 }
18800
18801 pub fn copy_file_name_without_extension(
18802 &mut self,
18803 _: &CopyFileNameWithoutExtension,
18804 _: &mut Window,
18805 cx: &mut Context<Self>,
18806 ) {
18807 if let Some(file) = self.target_file(cx)
18808 && let Some(file_stem) = file.path().file_stem()
18809 && let Some(name) = file_stem.to_str()
18810 {
18811 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18812 }
18813 }
18814
18815 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18816 if let Some(file) = self.target_file(cx)
18817 && let Some(file_name) = file.path().file_name()
18818 && let Some(name) = file_name.to_str()
18819 {
18820 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18821 }
18822 }
18823
18824 pub fn toggle_git_blame(
18825 &mut self,
18826 _: &::git::Blame,
18827 window: &mut Window,
18828 cx: &mut Context<Self>,
18829 ) {
18830 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18831
18832 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18833 self.start_git_blame(true, window, cx);
18834 }
18835
18836 cx.notify();
18837 }
18838
18839 pub fn toggle_git_blame_inline(
18840 &mut self,
18841 _: &ToggleGitBlameInline,
18842 window: &mut Window,
18843 cx: &mut Context<Self>,
18844 ) {
18845 self.toggle_git_blame_inline_internal(true, window, cx);
18846 cx.notify();
18847 }
18848
18849 pub fn open_git_blame_commit(
18850 &mut self,
18851 _: &OpenGitBlameCommit,
18852 window: &mut Window,
18853 cx: &mut Context<Self>,
18854 ) {
18855 self.open_git_blame_commit_internal(window, cx);
18856 }
18857
18858 fn open_git_blame_commit_internal(
18859 &mut self,
18860 window: &mut Window,
18861 cx: &mut Context<Self>,
18862 ) -> Option<()> {
18863 let blame = self.blame.as_ref()?;
18864 let snapshot = self.snapshot(window, cx);
18865 let cursor = self.selections.newest::<Point>(cx).head();
18866 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18867 let blame_entry = blame
18868 .update(cx, |blame, cx| {
18869 blame
18870 .blame_for_rows(
18871 &[RowInfo {
18872 buffer_id: Some(buffer.remote_id()),
18873 buffer_row: Some(point.row),
18874 ..Default::default()
18875 }],
18876 cx,
18877 )
18878 .next()
18879 })
18880 .flatten()?;
18881 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18882 let repo = blame.read(cx).repository(cx)?;
18883 let workspace = self.workspace()?.downgrade();
18884 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18885 None
18886 }
18887
18888 pub fn git_blame_inline_enabled(&self) -> bool {
18889 self.git_blame_inline_enabled
18890 }
18891
18892 pub fn toggle_selection_menu(
18893 &mut self,
18894 _: &ToggleSelectionMenu,
18895 _: &mut Window,
18896 cx: &mut Context<Self>,
18897 ) {
18898 self.show_selection_menu = self
18899 .show_selection_menu
18900 .map(|show_selections_menu| !show_selections_menu)
18901 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18902
18903 cx.notify();
18904 }
18905
18906 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18907 self.show_selection_menu
18908 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18909 }
18910
18911 fn start_git_blame(
18912 &mut self,
18913 user_triggered: bool,
18914 window: &mut Window,
18915 cx: &mut Context<Self>,
18916 ) {
18917 if let Some(project) = self.project() {
18918 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18919 return;
18920 };
18921
18922 if buffer.read(cx).file().is_none() {
18923 return;
18924 }
18925
18926 let focused = self.focus_handle(cx).contains_focused(window, cx);
18927
18928 let project = project.clone();
18929 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18930 self.blame_subscription =
18931 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18932 self.blame = Some(blame);
18933 }
18934 }
18935
18936 fn toggle_git_blame_inline_internal(
18937 &mut self,
18938 user_triggered: bool,
18939 window: &mut Window,
18940 cx: &mut Context<Self>,
18941 ) {
18942 if self.git_blame_inline_enabled {
18943 self.git_blame_inline_enabled = false;
18944 self.show_git_blame_inline = false;
18945 self.show_git_blame_inline_delay_task.take();
18946 } else {
18947 self.git_blame_inline_enabled = true;
18948 self.start_git_blame_inline(user_triggered, window, cx);
18949 }
18950
18951 cx.notify();
18952 }
18953
18954 fn start_git_blame_inline(
18955 &mut self,
18956 user_triggered: bool,
18957 window: &mut Window,
18958 cx: &mut Context<Self>,
18959 ) {
18960 self.start_git_blame(user_triggered, window, cx);
18961
18962 if ProjectSettings::get_global(cx)
18963 .git
18964 .inline_blame_delay()
18965 .is_some()
18966 {
18967 self.start_inline_blame_timer(window, cx);
18968 } else {
18969 self.show_git_blame_inline = true
18970 }
18971 }
18972
18973 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18974 self.blame.as_ref()
18975 }
18976
18977 pub fn show_git_blame_gutter(&self) -> bool {
18978 self.show_git_blame_gutter
18979 }
18980
18981 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18982 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18983 }
18984
18985 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18986 self.show_git_blame_inline
18987 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18988 && !self.newest_selection_head_on_empty_line(cx)
18989 && self.has_blame_entries(cx)
18990 }
18991
18992 fn has_blame_entries(&self, cx: &App) -> bool {
18993 self.blame()
18994 .is_some_and(|blame| blame.read(cx).has_generated_entries())
18995 }
18996
18997 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18998 let cursor_anchor = self.selections.newest_anchor().head();
18999
19000 let snapshot = self.buffer.read(cx).snapshot(cx);
19001 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19002
19003 snapshot.line_len(buffer_row) == 0
19004 }
19005
19006 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19007 let buffer_and_selection = maybe!({
19008 let selection = self.selections.newest::<Point>(cx);
19009 let selection_range = selection.range();
19010
19011 let multi_buffer = self.buffer().read(cx);
19012 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19013 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19014
19015 let (buffer, range, _) = if selection.reversed {
19016 buffer_ranges.first()
19017 } else {
19018 buffer_ranges.last()
19019 }?;
19020
19021 let selection = text::ToPoint::to_point(&range.start, buffer).row
19022 ..text::ToPoint::to_point(&range.end, buffer).row;
19023 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19024 });
19025
19026 let Some((buffer, selection)) = buffer_and_selection else {
19027 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19028 };
19029
19030 let Some(project) = self.project() else {
19031 return Task::ready(Err(anyhow!("editor does not have project")));
19032 };
19033
19034 project.update(cx, |project, cx| {
19035 project.get_permalink_to_line(&buffer, selection, cx)
19036 })
19037 }
19038
19039 pub fn copy_permalink_to_line(
19040 &mut self,
19041 _: &CopyPermalinkToLine,
19042 window: &mut Window,
19043 cx: &mut Context<Self>,
19044 ) {
19045 let permalink_task = self.get_permalink_to_line(cx);
19046 let workspace = self.workspace();
19047
19048 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19049 Ok(permalink) => {
19050 cx.update(|_, cx| {
19051 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19052 })
19053 .ok();
19054 }
19055 Err(err) => {
19056 let message = format!("Failed to copy permalink: {err}");
19057
19058 anyhow::Result::<()>::Err(err).log_err();
19059
19060 if let Some(workspace) = workspace {
19061 workspace
19062 .update_in(cx, |workspace, _, cx| {
19063 struct CopyPermalinkToLine;
19064
19065 workspace.show_toast(
19066 Toast::new(
19067 NotificationId::unique::<CopyPermalinkToLine>(),
19068 message,
19069 ),
19070 cx,
19071 )
19072 })
19073 .ok();
19074 }
19075 }
19076 })
19077 .detach();
19078 }
19079
19080 pub fn copy_file_location(
19081 &mut self,
19082 _: &CopyFileLocation,
19083 _: &mut Window,
19084 cx: &mut Context<Self>,
19085 ) {
19086 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19087 if let Some(file) = self.target_file(cx)
19088 && let Some(path) = file.path().to_str()
19089 {
19090 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19091 }
19092 }
19093
19094 pub fn open_permalink_to_line(
19095 &mut self,
19096 _: &OpenPermalinkToLine,
19097 window: &mut Window,
19098 cx: &mut Context<Self>,
19099 ) {
19100 let permalink_task = self.get_permalink_to_line(cx);
19101 let workspace = self.workspace();
19102
19103 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19104 Ok(permalink) => {
19105 cx.update(|_, cx| {
19106 cx.open_url(permalink.as_ref());
19107 })
19108 .ok();
19109 }
19110 Err(err) => {
19111 let message = format!("Failed to open permalink: {err}");
19112
19113 anyhow::Result::<()>::Err(err).log_err();
19114
19115 if let Some(workspace) = workspace {
19116 workspace
19117 .update(cx, |workspace, cx| {
19118 struct OpenPermalinkToLine;
19119
19120 workspace.show_toast(
19121 Toast::new(
19122 NotificationId::unique::<OpenPermalinkToLine>(),
19123 message,
19124 ),
19125 cx,
19126 )
19127 })
19128 .ok();
19129 }
19130 }
19131 })
19132 .detach();
19133 }
19134
19135 pub fn insert_uuid_v4(
19136 &mut self,
19137 _: &InsertUuidV4,
19138 window: &mut Window,
19139 cx: &mut Context<Self>,
19140 ) {
19141 self.insert_uuid(UuidVersion::V4, window, cx);
19142 }
19143
19144 pub fn insert_uuid_v7(
19145 &mut self,
19146 _: &InsertUuidV7,
19147 window: &mut Window,
19148 cx: &mut Context<Self>,
19149 ) {
19150 self.insert_uuid(UuidVersion::V7, window, cx);
19151 }
19152
19153 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19154 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19155 self.transact(window, cx, |this, window, cx| {
19156 let edits = this
19157 .selections
19158 .all::<Point>(cx)
19159 .into_iter()
19160 .map(|selection| {
19161 let uuid = match version {
19162 UuidVersion::V4 => uuid::Uuid::new_v4(),
19163 UuidVersion::V7 => uuid::Uuid::now_v7(),
19164 };
19165
19166 (selection.range(), uuid.to_string())
19167 });
19168 this.edit(edits, cx);
19169 this.refresh_edit_prediction(true, false, window, cx);
19170 });
19171 }
19172
19173 pub fn open_selections_in_multibuffer(
19174 &mut self,
19175 _: &OpenSelectionsInMultibuffer,
19176 window: &mut Window,
19177 cx: &mut Context<Self>,
19178 ) {
19179 let multibuffer = self.buffer.read(cx);
19180
19181 let Some(buffer) = multibuffer.as_singleton() else {
19182 return;
19183 };
19184
19185 let Some(workspace) = self.workspace() else {
19186 return;
19187 };
19188
19189 let title = multibuffer.title(cx).to_string();
19190
19191 let locations = self
19192 .selections
19193 .all_anchors(cx)
19194 .iter()
19195 .map(|selection| Location {
19196 buffer: buffer.clone(),
19197 range: selection.start.text_anchor..selection.end.text_anchor,
19198 })
19199 .collect::<Vec<_>>();
19200
19201 cx.spawn_in(window, async move |_, cx| {
19202 workspace.update_in(cx, |workspace, window, cx| {
19203 Self::open_locations_in_multibuffer(
19204 workspace,
19205 locations,
19206 format!("Selections for '{title}'"),
19207 false,
19208 MultibufferSelectionMode::All,
19209 window,
19210 cx,
19211 );
19212 })
19213 })
19214 .detach();
19215 }
19216
19217 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19218 /// last highlight added will be used.
19219 ///
19220 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19221 pub fn highlight_rows<T: 'static>(
19222 &mut self,
19223 range: Range<Anchor>,
19224 color: Hsla,
19225 options: RowHighlightOptions,
19226 cx: &mut Context<Self>,
19227 ) {
19228 let snapshot = self.buffer().read(cx).snapshot(cx);
19229 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19230 let ix = row_highlights.binary_search_by(|highlight| {
19231 Ordering::Equal
19232 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19233 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19234 });
19235
19236 if let Err(mut ix) = ix {
19237 let index = post_inc(&mut self.highlight_order);
19238
19239 // If this range intersects with the preceding highlight, then merge it with
19240 // the preceding highlight. Otherwise insert a new highlight.
19241 let mut merged = false;
19242 if ix > 0 {
19243 let prev_highlight = &mut row_highlights[ix - 1];
19244 if prev_highlight
19245 .range
19246 .end
19247 .cmp(&range.start, &snapshot)
19248 .is_ge()
19249 {
19250 ix -= 1;
19251 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19252 prev_highlight.range.end = range.end;
19253 }
19254 merged = true;
19255 prev_highlight.index = index;
19256 prev_highlight.color = color;
19257 prev_highlight.options = options;
19258 }
19259 }
19260
19261 if !merged {
19262 row_highlights.insert(
19263 ix,
19264 RowHighlight {
19265 range,
19266 index,
19267 color,
19268 options,
19269 type_id: TypeId::of::<T>(),
19270 },
19271 );
19272 }
19273
19274 // If any of the following highlights intersect with this one, merge them.
19275 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19276 let highlight = &row_highlights[ix];
19277 if next_highlight
19278 .range
19279 .start
19280 .cmp(&highlight.range.end, &snapshot)
19281 .is_le()
19282 {
19283 if next_highlight
19284 .range
19285 .end
19286 .cmp(&highlight.range.end, &snapshot)
19287 .is_gt()
19288 {
19289 row_highlights[ix].range.end = next_highlight.range.end;
19290 }
19291 row_highlights.remove(ix + 1);
19292 } else {
19293 break;
19294 }
19295 }
19296 }
19297 }
19298
19299 /// Remove any highlighted row ranges of the given type that intersect the
19300 /// given ranges.
19301 pub fn remove_highlighted_rows<T: 'static>(
19302 &mut self,
19303 ranges_to_remove: Vec<Range<Anchor>>,
19304 cx: &mut Context<Self>,
19305 ) {
19306 let snapshot = self.buffer().read(cx).snapshot(cx);
19307 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19308 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19309 row_highlights.retain(|highlight| {
19310 while let Some(range_to_remove) = ranges_to_remove.peek() {
19311 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19312 Ordering::Less | Ordering::Equal => {
19313 ranges_to_remove.next();
19314 }
19315 Ordering::Greater => {
19316 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19317 Ordering::Less | Ordering::Equal => {
19318 return false;
19319 }
19320 Ordering::Greater => break,
19321 }
19322 }
19323 }
19324 }
19325
19326 true
19327 })
19328 }
19329
19330 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19331 pub fn clear_row_highlights<T: 'static>(&mut self) {
19332 self.highlighted_rows.remove(&TypeId::of::<T>());
19333 }
19334
19335 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19336 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19337 self.highlighted_rows
19338 .get(&TypeId::of::<T>())
19339 .map_or(&[] as &[_], |vec| vec.as_slice())
19340 .iter()
19341 .map(|highlight| (highlight.range.clone(), highlight.color))
19342 }
19343
19344 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19345 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19346 /// Allows to ignore certain kinds of highlights.
19347 pub fn highlighted_display_rows(
19348 &self,
19349 window: &mut Window,
19350 cx: &mut App,
19351 ) -> BTreeMap<DisplayRow, LineHighlight> {
19352 let snapshot = self.snapshot(window, cx);
19353 let mut used_highlight_orders = HashMap::default();
19354 self.highlighted_rows
19355 .iter()
19356 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19357 .fold(
19358 BTreeMap::<DisplayRow, LineHighlight>::new(),
19359 |mut unique_rows, highlight| {
19360 let start = highlight.range.start.to_display_point(&snapshot);
19361 let end = highlight.range.end.to_display_point(&snapshot);
19362 let start_row = start.row().0;
19363 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19364 && end.column() == 0
19365 {
19366 end.row().0.saturating_sub(1)
19367 } else {
19368 end.row().0
19369 };
19370 for row in start_row..=end_row {
19371 let used_index =
19372 used_highlight_orders.entry(row).or_insert(highlight.index);
19373 if highlight.index >= *used_index {
19374 *used_index = highlight.index;
19375 unique_rows.insert(
19376 DisplayRow(row),
19377 LineHighlight {
19378 include_gutter: highlight.options.include_gutter,
19379 border: None,
19380 background: highlight.color.into(),
19381 type_id: Some(highlight.type_id),
19382 },
19383 );
19384 }
19385 }
19386 unique_rows
19387 },
19388 )
19389 }
19390
19391 pub fn highlighted_display_row_for_autoscroll(
19392 &self,
19393 snapshot: &DisplaySnapshot,
19394 ) -> Option<DisplayRow> {
19395 self.highlighted_rows
19396 .values()
19397 .flat_map(|highlighted_rows| highlighted_rows.iter())
19398 .filter_map(|highlight| {
19399 if highlight.options.autoscroll {
19400 Some(highlight.range.start.to_display_point(snapshot).row())
19401 } else {
19402 None
19403 }
19404 })
19405 .min()
19406 }
19407
19408 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19409 self.highlight_background::<SearchWithinRange>(
19410 ranges,
19411 |colors| colors.colors().editor_document_highlight_read_background,
19412 cx,
19413 )
19414 }
19415
19416 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19417 self.breadcrumb_header = Some(new_header);
19418 }
19419
19420 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19421 self.clear_background_highlights::<SearchWithinRange>(cx);
19422 }
19423
19424 pub fn highlight_background<T: 'static>(
19425 &mut self,
19426 ranges: &[Range<Anchor>],
19427 color_fetcher: fn(&Theme) -> Hsla,
19428 cx: &mut Context<Self>,
19429 ) {
19430 self.background_highlights.insert(
19431 HighlightKey::Type(TypeId::of::<T>()),
19432 (color_fetcher, Arc::from(ranges)),
19433 );
19434 self.scrollbar_marker_state.dirty = true;
19435 cx.notify();
19436 }
19437
19438 pub fn highlight_background_key<T: 'static>(
19439 &mut self,
19440 key: usize,
19441 ranges: &[Range<Anchor>],
19442 color_fetcher: fn(&Theme) -> Hsla,
19443 cx: &mut Context<Self>,
19444 ) {
19445 self.background_highlights.insert(
19446 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19447 (color_fetcher, Arc::from(ranges)),
19448 );
19449 self.scrollbar_marker_state.dirty = true;
19450 cx.notify();
19451 }
19452
19453 pub fn clear_background_highlights<T: 'static>(
19454 &mut self,
19455 cx: &mut Context<Self>,
19456 ) -> Option<BackgroundHighlight> {
19457 let text_highlights = self
19458 .background_highlights
19459 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19460 if !text_highlights.1.is_empty() {
19461 self.scrollbar_marker_state.dirty = true;
19462 cx.notify();
19463 }
19464 Some(text_highlights)
19465 }
19466
19467 pub fn highlight_gutter<T: 'static>(
19468 &mut self,
19469 ranges: impl Into<Vec<Range<Anchor>>>,
19470 color_fetcher: fn(&App) -> Hsla,
19471 cx: &mut Context<Self>,
19472 ) {
19473 self.gutter_highlights
19474 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19475 cx.notify();
19476 }
19477
19478 pub fn clear_gutter_highlights<T: 'static>(
19479 &mut self,
19480 cx: &mut Context<Self>,
19481 ) -> Option<GutterHighlight> {
19482 cx.notify();
19483 self.gutter_highlights.remove(&TypeId::of::<T>())
19484 }
19485
19486 pub fn insert_gutter_highlight<T: 'static>(
19487 &mut self,
19488 range: Range<Anchor>,
19489 color_fetcher: fn(&App) -> Hsla,
19490 cx: &mut Context<Self>,
19491 ) {
19492 let snapshot = self.buffer().read(cx).snapshot(cx);
19493 let mut highlights = self
19494 .gutter_highlights
19495 .remove(&TypeId::of::<T>())
19496 .map(|(_, highlights)| highlights)
19497 .unwrap_or_default();
19498 let ix = highlights.binary_search_by(|highlight| {
19499 Ordering::Equal
19500 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19501 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19502 });
19503 if let Err(ix) = ix {
19504 highlights.insert(ix, range);
19505 }
19506 self.gutter_highlights
19507 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19508 }
19509
19510 pub fn remove_gutter_highlights<T: 'static>(
19511 &mut self,
19512 ranges_to_remove: Vec<Range<Anchor>>,
19513 cx: &mut Context<Self>,
19514 ) {
19515 let snapshot = self.buffer().read(cx).snapshot(cx);
19516 let Some((color_fetcher, mut gutter_highlights)) =
19517 self.gutter_highlights.remove(&TypeId::of::<T>())
19518 else {
19519 return;
19520 };
19521 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19522 gutter_highlights.retain(|highlight| {
19523 while let Some(range_to_remove) = ranges_to_remove.peek() {
19524 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19525 Ordering::Less | Ordering::Equal => {
19526 ranges_to_remove.next();
19527 }
19528 Ordering::Greater => {
19529 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19530 Ordering::Less | Ordering::Equal => {
19531 return false;
19532 }
19533 Ordering::Greater => break,
19534 }
19535 }
19536 }
19537 }
19538
19539 true
19540 });
19541 self.gutter_highlights
19542 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19543 }
19544
19545 #[cfg(feature = "test-support")]
19546 pub fn all_text_highlights(
19547 &self,
19548 window: &mut Window,
19549 cx: &mut Context<Self>,
19550 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19551 let snapshot = self.snapshot(window, cx);
19552 self.display_map.update(cx, |display_map, _| {
19553 display_map
19554 .all_text_highlights()
19555 .map(|highlight| {
19556 let (style, ranges) = highlight.as_ref();
19557 (
19558 *style,
19559 ranges
19560 .iter()
19561 .map(|range| range.clone().to_display_points(&snapshot))
19562 .collect(),
19563 )
19564 })
19565 .collect()
19566 })
19567 }
19568
19569 #[cfg(feature = "test-support")]
19570 pub fn all_text_background_highlights(
19571 &self,
19572 window: &mut Window,
19573 cx: &mut Context<Self>,
19574 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19575 let snapshot = self.snapshot(window, cx);
19576 let buffer = &snapshot.buffer_snapshot;
19577 let start = buffer.anchor_before(0);
19578 let end = buffer.anchor_after(buffer.len());
19579 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19580 }
19581
19582 #[cfg(feature = "test-support")]
19583 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19584 let snapshot = self.buffer().read(cx).snapshot(cx);
19585
19586 let highlights = self
19587 .background_highlights
19588 .get(&HighlightKey::Type(TypeId::of::<
19589 items::BufferSearchHighlights,
19590 >()));
19591
19592 if let Some((_color, ranges)) = highlights {
19593 ranges
19594 .iter()
19595 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19596 .collect_vec()
19597 } else {
19598 vec![]
19599 }
19600 }
19601
19602 fn document_highlights_for_position<'a>(
19603 &'a self,
19604 position: Anchor,
19605 buffer: &'a MultiBufferSnapshot,
19606 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19607 let read_highlights = self
19608 .background_highlights
19609 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19610 .map(|h| &h.1);
19611 let write_highlights = self
19612 .background_highlights
19613 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19614 .map(|h| &h.1);
19615 let left_position = position.bias_left(buffer);
19616 let right_position = position.bias_right(buffer);
19617 read_highlights
19618 .into_iter()
19619 .chain(write_highlights)
19620 .flat_map(move |ranges| {
19621 let start_ix = match ranges.binary_search_by(|probe| {
19622 let cmp = probe.end.cmp(&left_position, buffer);
19623 if cmp.is_ge() {
19624 Ordering::Greater
19625 } else {
19626 Ordering::Less
19627 }
19628 }) {
19629 Ok(i) | Err(i) => i,
19630 };
19631
19632 ranges[start_ix..]
19633 .iter()
19634 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19635 })
19636 }
19637
19638 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19639 self.background_highlights
19640 .get(&HighlightKey::Type(TypeId::of::<T>()))
19641 .is_some_and(|(_, highlights)| !highlights.is_empty())
19642 }
19643
19644 pub fn background_highlights_in_range(
19645 &self,
19646 search_range: Range<Anchor>,
19647 display_snapshot: &DisplaySnapshot,
19648 theme: &Theme,
19649 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19650 let mut results = Vec::new();
19651 for (color_fetcher, ranges) in self.background_highlights.values() {
19652 let color = color_fetcher(theme);
19653 let start_ix = match ranges.binary_search_by(|probe| {
19654 let cmp = probe
19655 .end
19656 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19657 if cmp.is_gt() {
19658 Ordering::Greater
19659 } else {
19660 Ordering::Less
19661 }
19662 }) {
19663 Ok(i) | Err(i) => i,
19664 };
19665 for range in &ranges[start_ix..] {
19666 if range
19667 .start
19668 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19669 .is_ge()
19670 {
19671 break;
19672 }
19673
19674 let start = range.start.to_display_point(display_snapshot);
19675 let end = range.end.to_display_point(display_snapshot);
19676 results.push((start..end, color))
19677 }
19678 }
19679 results
19680 }
19681
19682 pub fn background_highlight_row_ranges<T: 'static>(
19683 &self,
19684 search_range: Range<Anchor>,
19685 display_snapshot: &DisplaySnapshot,
19686 count: usize,
19687 ) -> Vec<RangeInclusive<DisplayPoint>> {
19688 let mut results = Vec::new();
19689 let Some((_, ranges)) = self
19690 .background_highlights
19691 .get(&HighlightKey::Type(TypeId::of::<T>()))
19692 else {
19693 return vec![];
19694 };
19695
19696 let start_ix = match ranges.binary_search_by(|probe| {
19697 let cmp = probe
19698 .end
19699 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19700 if cmp.is_gt() {
19701 Ordering::Greater
19702 } else {
19703 Ordering::Less
19704 }
19705 }) {
19706 Ok(i) | Err(i) => i,
19707 };
19708 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19709 if let (Some(start_display), Some(end_display)) = (start, end) {
19710 results.push(
19711 start_display.to_display_point(display_snapshot)
19712 ..=end_display.to_display_point(display_snapshot),
19713 );
19714 }
19715 };
19716 let mut start_row: Option<Point> = None;
19717 let mut end_row: Option<Point> = None;
19718 if ranges.len() > count {
19719 return Vec::new();
19720 }
19721 for range in &ranges[start_ix..] {
19722 if range
19723 .start
19724 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19725 .is_ge()
19726 {
19727 break;
19728 }
19729 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19730 if let Some(current_row) = &end_row
19731 && end.row == current_row.row
19732 {
19733 continue;
19734 }
19735 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19736 if start_row.is_none() {
19737 assert_eq!(end_row, None);
19738 start_row = Some(start);
19739 end_row = Some(end);
19740 continue;
19741 }
19742 if let Some(current_end) = end_row.as_mut() {
19743 if start.row > current_end.row + 1 {
19744 push_region(start_row, end_row);
19745 start_row = Some(start);
19746 end_row = Some(end);
19747 } else {
19748 // Merge two hunks.
19749 *current_end = end;
19750 }
19751 } else {
19752 unreachable!();
19753 }
19754 }
19755 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19756 push_region(start_row, end_row);
19757 results
19758 }
19759
19760 pub fn gutter_highlights_in_range(
19761 &self,
19762 search_range: Range<Anchor>,
19763 display_snapshot: &DisplaySnapshot,
19764 cx: &App,
19765 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19766 let mut results = Vec::new();
19767 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19768 let color = color_fetcher(cx);
19769 let start_ix = match ranges.binary_search_by(|probe| {
19770 let cmp = probe
19771 .end
19772 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19773 if cmp.is_gt() {
19774 Ordering::Greater
19775 } else {
19776 Ordering::Less
19777 }
19778 }) {
19779 Ok(i) | Err(i) => i,
19780 };
19781 for range in &ranges[start_ix..] {
19782 if range
19783 .start
19784 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19785 .is_ge()
19786 {
19787 break;
19788 }
19789
19790 let start = range.start.to_display_point(display_snapshot);
19791 let end = range.end.to_display_point(display_snapshot);
19792 results.push((start..end, color))
19793 }
19794 }
19795 results
19796 }
19797
19798 /// Get the text ranges corresponding to the redaction query
19799 pub fn redacted_ranges(
19800 &self,
19801 search_range: Range<Anchor>,
19802 display_snapshot: &DisplaySnapshot,
19803 cx: &App,
19804 ) -> Vec<Range<DisplayPoint>> {
19805 display_snapshot
19806 .buffer_snapshot
19807 .redacted_ranges(search_range, |file| {
19808 if let Some(file) = file {
19809 file.is_private()
19810 && EditorSettings::get(
19811 Some(SettingsLocation {
19812 worktree_id: file.worktree_id(cx),
19813 path: file.path().as_ref(),
19814 }),
19815 cx,
19816 )
19817 .redact_private_values
19818 } else {
19819 false
19820 }
19821 })
19822 .map(|range| {
19823 range.start.to_display_point(display_snapshot)
19824 ..range.end.to_display_point(display_snapshot)
19825 })
19826 .collect()
19827 }
19828
19829 pub fn highlight_text_key<T: 'static>(
19830 &mut self,
19831 key: usize,
19832 ranges: Vec<Range<Anchor>>,
19833 style: HighlightStyle,
19834 cx: &mut Context<Self>,
19835 ) {
19836 self.display_map.update(cx, |map, _| {
19837 map.highlight_text(
19838 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19839 ranges,
19840 style,
19841 );
19842 });
19843 cx.notify();
19844 }
19845
19846 pub fn highlight_text<T: 'static>(
19847 &mut self,
19848 ranges: Vec<Range<Anchor>>,
19849 style: HighlightStyle,
19850 cx: &mut Context<Self>,
19851 ) {
19852 self.display_map.update(cx, |map, _| {
19853 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19854 });
19855 cx.notify();
19856 }
19857
19858 pub(crate) fn highlight_inlays<T: 'static>(
19859 &mut self,
19860 highlights: Vec<InlayHighlight>,
19861 style: HighlightStyle,
19862 cx: &mut Context<Self>,
19863 ) {
19864 self.display_map.update(cx, |map, _| {
19865 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19866 });
19867 cx.notify();
19868 }
19869
19870 pub fn text_highlights<'a, T: 'static>(
19871 &'a self,
19872 cx: &'a App,
19873 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19874 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19875 }
19876
19877 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19878 let cleared = self
19879 .display_map
19880 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19881 if cleared {
19882 cx.notify();
19883 }
19884 }
19885
19886 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19887 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19888 && self.focus_handle.is_focused(window)
19889 }
19890
19891 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19892 self.show_cursor_when_unfocused = is_enabled;
19893 cx.notify();
19894 }
19895
19896 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19897 cx.notify();
19898 }
19899
19900 fn on_debug_session_event(
19901 &mut self,
19902 _session: Entity<Session>,
19903 event: &SessionEvent,
19904 cx: &mut Context<Self>,
19905 ) {
19906 if let SessionEvent::InvalidateInlineValue = event {
19907 self.refresh_inline_values(cx);
19908 }
19909 }
19910
19911 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19912 let Some(project) = self.project.clone() else {
19913 return;
19914 };
19915
19916 if !self.inline_value_cache.enabled {
19917 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19918 self.splice_inlays(&inlays, Vec::new(), cx);
19919 return;
19920 }
19921
19922 let current_execution_position = self
19923 .highlighted_rows
19924 .get(&TypeId::of::<ActiveDebugLine>())
19925 .and_then(|lines| lines.last().map(|line| line.range.end));
19926
19927 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19928 let inline_values = editor
19929 .update(cx, |editor, cx| {
19930 let Some(current_execution_position) = current_execution_position else {
19931 return Some(Task::ready(Ok(Vec::new())));
19932 };
19933
19934 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19935 let snapshot = buffer.snapshot(cx);
19936
19937 let excerpt = snapshot.excerpt_containing(
19938 current_execution_position..current_execution_position,
19939 )?;
19940
19941 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19942 })?;
19943
19944 let range =
19945 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19946
19947 project.inline_values(buffer, range, cx)
19948 })
19949 .ok()
19950 .flatten()?
19951 .await
19952 .context("refreshing debugger inlays")
19953 .log_err()?;
19954
19955 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19956
19957 for (buffer_id, inline_value) in inline_values
19958 .into_iter()
19959 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19960 {
19961 buffer_inline_values
19962 .entry(buffer_id)
19963 .or_default()
19964 .push(inline_value);
19965 }
19966
19967 editor
19968 .update(cx, |editor, cx| {
19969 let snapshot = editor.buffer.read(cx).snapshot(cx);
19970 let mut new_inlays = Vec::default();
19971
19972 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19973 let buffer_id = buffer_snapshot.remote_id();
19974 buffer_inline_values
19975 .get(&buffer_id)
19976 .into_iter()
19977 .flatten()
19978 .for_each(|hint| {
19979 let inlay = Inlay::debugger(
19980 post_inc(&mut editor.next_inlay_id),
19981 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19982 hint.text(),
19983 );
19984 if !inlay.text.chars().contains(&'\n') {
19985 new_inlays.push(inlay);
19986 }
19987 });
19988 }
19989
19990 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19991 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19992
19993 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19994 })
19995 .ok()?;
19996 Some(())
19997 });
19998 }
19999
20000 fn on_buffer_event(
20001 &mut self,
20002 multibuffer: &Entity<MultiBuffer>,
20003 event: &multi_buffer::Event,
20004 window: &mut Window,
20005 cx: &mut Context<Self>,
20006 ) {
20007 match event {
20008 multi_buffer::Event::Edited {
20009 singleton_buffer_edited,
20010 edited_buffer,
20011 } => {
20012 self.scrollbar_marker_state.dirty = true;
20013 self.active_indent_guides_state.dirty = true;
20014 self.refresh_active_diagnostics(cx);
20015 self.refresh_code_actions(window, cx);
20016 self.refresh_selected_text_highlights(true, window, cx);
20017 self.refresh_single_line_folds(window, cx);
20018 refresh_matching_bracket_highlights(self, window, cx);
20019 if self.has_active_edit_prediction() {
20020 self.update_visible_edit_prediction(window, cx);
20021 }
20022 if let Some(project) = self.project.as_ref()
20023 && let Some(edited_buffer) = edited_buffer
20024 {
20025 project.update(cx, |project, cx| {
20026 self.registered_buffers
20027 .entry(edited_buffer.read(cx).remote_id())
20028 .or_insert_with(|| {
20029 project.register_buffer_with_language_servers(edited_buffer, cx)
20030 });
20031 });
20032 }
20033 cx.emit(EditorEvent::BufferEdited);
20034 cx.emit(SearchEvent::MatchesInvalidated);
20035
20036 if let Some(buffer) = edited_buffer {
20037 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20038 }
20039
20040 if *singleton_buffer_edited {
20041 if let Some(buffer) = edited_buffer
20042 && buffer.read(cx).file().is_none()
20043 {
20044 cx.emit(EditorEvent::TitleChanged);
20045 }
20046 if let Some(project) = &self.project {
20047 #[allow(clippy::mutable_key_type)]
20048 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20049 multibuffer
20050 .all_buffers()
20051 .into_iter()
20052 .filter_map(|buffer| {
20053 buffer.update(cx, |buffer, cx| {
20054 let language = buffer.language()?;
20055 let should_discard = project.update(cx, |project, cx| {
20056 project.is_local()
20057 && !project.has_language_servers_for(buffer, cx)
20058 });
20059 should_discard.not().then_some(language.clone())
20060 })
20061 })
20062 .collect::<HashSet<_>>()
20063 });
20064 if !languages_affected.is_empty() {
20065 self.refresh_inlay_hints(
20066 InlayHintRefreshReason::BufferEdited(languages_affected),
20067 cx,
20068 );
20069 }
20070 }
20071 }
20072
20073 let Some(project) = &self.project else { return };
20074 let (telemetry, is_via_ssh) = {
20075 let project = project.read(cx);
20076 let telemetry = project.client().telemetry().clone();
20077 let is_via_ssh = project.is_via_ssh();
20078 (telemetry, is_via_ssh)
20079 };
20080 refresh_linked_ranges(self, window, cx);
20081 telemetry.log_edit_event("editor", is_via_ssh);
20082 }
20083 multi_buffer::Event::ExcerptsAdded {
20084 buffer,
20085 predecessor,
20086 excerpts,
20087 } => {
20088 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20089 let buffer_id = buffer.read(cx).remote_id();
20090 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20091 && let Some(project) = &self.project
20092 {
20093 update_uncommitted_diff_for_buffer(
20094 cx.entity(),
20095 project,
20096 [buffer.clone()],
20097 self.buffer.clone(),
20098 cx,
20099 )
20100 .detach();
20101 }
20102 self.update_lsp_data(false, Some(buffer_id), window, cx);
20103 cx.emit(EditorEvent::ExcerptsAdded {
20104 buffer: buffer.clone(),
20105 predecessor: *predecessor,
20106 excerpts: excerpts.clone(),
20107 });
20108 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20109 }
20110 multi_buffer::Event::ExcerptsRemoved {
20111 ids,
20112 removed_buffer_ids,
20113 } => {
20114 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20115 let buffer = self.buffer.read(cx);
20116 self.registered_buffers
20117 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20118 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20119 cx.emit(EditorEvent::ExcerptsRemoved {
20120 ids: ids.clone(),
20121 removed_buffer_ids: removed_buffer_ids.clone(),
20122 });
20123 }
20124 multi_buffer::Event::ExcerptsEdited {
20125 excerpt_ids,
20126 buffer_ids,
20127 } => {
20128 self.display_map.update(cx, |map, cx| {
20129 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20130 });
20131 cx.emit(EditorEvent::ExcerptsEdited {
20132 ids: excerpt_ids.clone(),
20133 });
20134 }
20135 multi_buffer::Event::ExcerptsExpanded { ids } => {
20136 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20137 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20138 }
20139 multi_buffer::Event::Reparsed(buffer_id) => {
20140 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20141 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20142
20143 cx.emit(EditorEvent::Reparsed(*buffer_id));
20144 }
20145 multi_buffer::Event::DiffHunksToggled => {
20146 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20147 }
20148 multi_buffer::Event::LanguageChanged(buffer_id) => {
20149 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20150 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20151 cx.emit(EditorEvent::Reparsed(*buffer_id));
20152 cx.notify();
20153 }
20154 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20155 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20156 multi_buffer::Event::FileHandleChanged
20157 | multi_buffer::Event::Reloaded
20158 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20159 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20160 multi_buffer::Event::DiagnosticsUpdated => {
20161 self.update_diagnostics_state(window, cx);
20162 }
20163 _ => {}
20164 };
20165 }
20166
20167 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20168 if !self.diagnostics_enabled() {
20169 return;
20170 }
20171 self.refresh_active_diagnostics(cx);
20172 self.refresh_inline_diagnostics(true, window, cx);
20173 self.scrollbar_marker_state.dirty = true;
20174 cx.notify();
20175 }
20176
20177 pub fn start_temporary_diff_override(&mut self) {
20178 self.load_diff_task.take();
20179 self.temporary_diff_override = true;
20180 }
20181
20182 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20183 self.temporary_diff_override = false;
20184 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20185 self.buffer.update(cx, |buffer, cx| {
20186 buffer.set_all_diff_hunks_collapsed(cx);
20187 });
20188
20189 if let Some(project) = self.project.clone() {
20190 self.load_diff_task = Some(
20191 update_uncommitted_diff_for_buffer(
20192 cx.entity(),
20193 &project,
20194 self.buffer.read(cx).all_buffers(),
20195 self.buffer.clone(),
20196 cx,
20197 )
20198 .shared(),
20199 );
20200 }
20201 }
20202
20203 fn on_display_map_changed(
20204 &mut self,
20205 _: Entity<DisplayMap>,
20206 _: &mut Window,
20207 cx: &mut Context<Self>,
20208 ) {
20209 cx.notify();
20210 }
20211
20212 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20213 if self.diagnostics_enabled() {
20214 let new_severity = EditorSettings::get_global(cx)
20215 .diagnostics_max_severity
20216 .unwrap_or(DiagnosticSeverity::Hint);
20217 self.set_max_diagnostics_severity(new_severity, cx);
20218 }
20219 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20220 self.update_edit_prediction_settings(cx);
20221 self.refresh_edit_prediction(true, false, window, cx);
20222 self.refresh_inline_values(cx);
20223 self.refresh_inlay_hints(
20224 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20225 self.selections.newest_anchor().head(),
20226 &self.buffer.read(cx).snapshot(cx),
20227 cx,
20228 )),
20229 cx,
20230 );
20231
20232 let old_cursor_shape = self.cursor_shape;
20233 let old_show_breadcrumbs = self.show_breadcrumbs;
20234
20235 {
20236 let editor_settings = EditorSettings::get_global(cx);
20237 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20238 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20239 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20240 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20241 }
20242
20243 if old_cursor_shape != self.cursor_shape {
20244 cx.emit(EditorEvent::CursorShapeChanged);
20245 }
20246
20247 if old_show_breadcrumbs != self.show_breadcrumbs {
20248 cx.emit(EditorEvent::BreadcrumbsChanged);
20249 }
20250
20251 let project_settings = ProjectSettings::get_global(cx);
20252 self.serialize_dirty_buffers =
20253 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20254
20255 if self.mode.is_full() {
20256 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20257 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20258 if self.show_inline_diagnostics != show_inline_diagnostics {
20259 self.show_inline_diagnostics = show_inline_diagnostics;
20260 self.refresh_inline_diagnostics(false, window, cx);
20261 }
20262
20263 if self.git_blame_inline_enabled != inline_blame_enabled {
20264 self.toggle_git_blame_inline_internal(false, window, cx);
20265 }
20266
20267 let minimap_settings = EditorSettings::get_global(cx).minimap;
20268 if self.minimap_visibility != MinimapVisibility::Disabled {
20269 if self.minimap_visibility.settings_visibility()
20270 != minimap_settings.minimap_enabled()
20271 {
20272 self.set_minimap_visibility(
20273 MinimapVisibility::for_mode(self.mode(), cx),
20274 window,
20275 cx,
20276 );
20277 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20278 minimap_entity.update(cx, |minimap_editor, cx| {
20279 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20280 })
20281 }
20282 }
20283 }
20284
20285 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20286 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20287 }) {
20288 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20289 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20290 }
20291 self.refresh_colors(false, None, window, cx);
20292 }
20293
20294 cx.notify();
20295 }
20296
20297 pub fn set_searchable(&mut self, searchable: bool) {
20298 self.searchable = searchable;
20299 }
20300
20301 pub fn searchable(&self) -> bool {
20302 self.searchable
20303 }
20304
20305 fn open_proposed_changes_editor(
20306 &mut self,
20307 _: &OpenProposedChangesEditor,
20308 window: &mut Window,
20309 cx: &mut Context<Self>,
20310 ) {
20311 let Some(workspace) = self.workspace() else {
20312 cx.propagate();
20313 return;
20314 };
20315
20316 let selections = self.selections.all::<usize>(cx);
20317 let multi_buffer = self.buffer.read(cx);
20318 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20319 let mut new_selections_by_buffer = HashMap::default();
20320 for selection in selections {
20321 for (buffer, range, _) in
20322 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20323 {
20324 let mut range = range.to_point(buffer);
20325 range.start.column = 0;
20326 range.end.column = buffer.line_len(range.end.row);
20327 new_selections_by_buffer
20328 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20329 .or_insert(Vec::new())
20330 .push(range)
20331 }
20332 }
20333
20334 let proposed_changes_buffers = new_selections_by_buffer
20335 .into_iter()
20336 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20337 .collect::<Vec<_>>();
20338 let proposed_changes_editor = cx.new(|cx| {
20339 ProposedChangesEditor::new(
20340 "Proposed changes",
20341 proposed_changes_buffers,
20342 self.project.clone(),
20343 window,
20344 cx,
20345 )
20346 });
20347
20348 window.defer(cx, move |window, cx| {
20349 workspace.update(cx, |workspace, cx| {
20350 workspace.active_pane().update(cx, |pane, cx| {
20351 pane.add_item(
20352 Box::new(proposed_changes_editor),
20353 true,
20354 true,
20355 None,
20356 window,
20357 cx,
20358 );
20359 });
20360 });
20361 });
20362 }
20363
20364 pub fn open_excerpts_in_split(
20365 &mut self,
20366 _: &OpenExcerptsSplit,
20367 window: &mut Window,
20368 cx: &mut Context<Self>,
20369 ) {
20370 self.open_excerpts_common(None, true, window, cx)
20371 }
20372
20373 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20374 self.open_excerpts_common(None, false, window, cx)
20375 }
20376
20377 fn open_excerpts_common(
20378 &mut self,
20379 jump_data: Option<JumpData>,
20380 split: bool,
20381 window: &mut Window,
20382 cx: &mut Context<Self>,
20383 ) {
20384 let Some(workspace) = self.workspace() else {
20385 cx.propagate();
20386 return;
20387 };
20388
20389 if self.buffer.read(cx).is_singleton() {
20390 cx.propagate();
20391 return;
20392 }
20393
20394 let mut new_selections_by_buffer = HashMap::default();
20395 match &jump_data {
20396 Some(JumpData::MultiBufferPoint {
20397 excerpt_id,
20398 position,
20399 anchor,
20400 line_offset_from_top,
20401 }) => {
20402 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20403 if let Some(buffer) = multi_buffer_snapshot
20404 .buffer_id_for_excerpt(*excerpt_id)
20405 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20406 {
20407 let buffer_snapshot = buffer.read(cx).snapshot();
20408 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20409 language::ToPoint::to_point(anchor, &buffer_snapshot)
20410 } else {
20411 buffer_snapshot.clip_point(*position, Bias::Left)
20412 };
20413 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20414 new_selections_by_buffer.insert(
20415 buffer,
20416 (
20417 vec![jump_to_offset..jump_to_offset],
20418 Some(*line_offset_from_top),
20419 ),
20420 );
20421 }
20422 }
20423 Some(JumpData::MultiBufferRow {
20424 row,
20425 line_offset_from_top,
20426 }) => {
20427 let point = MultiBufferPoint::new(row.0, 0);
20428 if let Some((buffer, buffer_point, _)) =
20429 self.buffer.read(cx).point_to_buffer_point(point, cx)
20430 {
20431 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20432 new_selections_by_buffer
20433 .entry(buffer)
20434 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20435 .0
20436 .push(buffer_offset..buffer_offset)
20437 }
20438 }
20439 None => {
20440 let selections = self.selections.all::<usize>(cx);
20441 let multi_buffer = self.buffer.read(cx);
20442 for selection in selections {
20443 for (snapshot, range, _, anchor) in multi_buffer
20444 .snapshot(cx)
20445 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20446 {
20447 if let Some(anchor) = anchor {
20448 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20449 else {
20450 continue;
20451 };
20452 let offset = text::ToOffset::to_offset(
20453 &anchor.text_anchor,
20454 &buffer_handle.read(cx).snapshot(),
20455 );
20456 let range = offset..offset;
20457 new_selections_by_buffer
20458 .entry(buffer_handle)
20459 .or_insert((Vec::new(), None))
20460 .0
20461 .push(range)
20462 } else {
20463 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20464 else {
20465 continue;
20466 };
20467 new_selections_by_buffer
20468 .entry(buffer_handle)
20469 .or_insert((Vec::new(), None))
20470 .0
20471 .push(range)
20472 }
20473 }
20474 }
20475 }
20476 }
20477
20478 new_selections_by_buffer
20479 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20480
20481 if new_selections_by_buffer.is_empty() {
20482 return;
20483 }
20484
20485 // We defer the pane interaction because we ourselves are a workspace item
20486 // and activating a new item causes the pane to call a method on us reentrantly,
20487 // which panics if we're on the stack.
20488 window.defer(cx, move |window, cx| {
20489 workspace.update(cx, |workspace, cx| {
20490 let pane = if split {
20491 workspace.adjacent_pane(window, cx)
20492 } else {
20493 workspace.active_pane().clone()
20494 };
20495
20496 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20497 let editor = buffer
20498 .read(cx)
20499 .file()
20500 .is_none()
20501 .then(|| {
20502 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20503 // so `workspace.open_project_item` will never find them, always opening a new editor.
20504 // Instead, we try to activate the existing editor in the pane first.
20505 let (editor, pane_item_index) =
20506 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20507 let editor = item.downcast::<Editor>()?;
20508 let singleton_buffer =
20509 editor.read(cx).buffer().read(cx).as_singleton()?;
20510 if singleton_buffer == buffer {
20511 Some((editor, i))
20512 } else {
20513 None
20514 }
20515 })?;
20516 pane.update(cx, |pane, cx| {
20517 pane.activate_item(pane_item_index, true, true, window, cx)
20518 });
20519 Some(editor)
20520 })
20521 .flatten()
20522 .unwrap_or_else(|| {
20523 workspace.open_project_item::<Self>(
20524 pane.clone(),
20525 buffer,
20526 true,
20527 true,
20528 window,
20529 cx,
20530 )
20531 });
20532
20533 editor.update(cx, |editor, cx| {
20534 let autoscroll = match scroll_offset {
20535 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20536 None => Autoscroll::newest(),
20537 };
20538 let nav_history = editor.nav_history.take();
20539 editor.change_selections(
20540 SelectionEffects::scroll(autoscroll),
20541 window,
20542 cx,
20543 |s| {
20544 s.select_ranges(ranges);
20545 },
20546 );
20547 editor.nav_history = nav_history;
20548 });
20549 }
20550 })
20551 });
20552 }
20553
20554 // For now, don't allow opening excerpts in buffers that aren't backed by
20555 // regular project files.
20556 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20557 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20558 }
20559
20560 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20561 let snapshot = self.buffer.read(cx).read(cx);
20562 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20563 Some(
20564 ranges
20565 .iter()
20566 .map(move |range| {
20567 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20568 })
20569 .collect(),
20570 )
20571 }
20572
20573 fn selection_replacement_ranges(
20574 &self,
20575 range: Range<OffsetUtf16>,
20576 cx: &mut App,
20577 ) -> Vec<Range<OffsetUtf16>> {
20578 let selections = self.selections.all::<OffsetUtf16>(cx);
20579 let newest_selection = selections
20580 .iter()
20581 .max_by_key(|selection| selection.id)
20582 .unwrap();
20583 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20584 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20585 let snapshot = self.buffer.read(cx).read(cx);
20586 selections
20587 .into_iter()
20588 .map(|mut selection| {
20589 selection.start.0 =
20590 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20591 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20592 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20593 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20594 })
20595 .collect()
20596 }
20597
20598 fn report_editor_event(
20599 &self,
20600 reported_event: ReportEditorEvent,
20601 file_extension: Option<String>,
20602 cx: &App,
20603 ) {
20604 if cfg!(any(test, feature = "test-support")) {
20605 return;
20606 }
20607
20608 let Some(project) = &self.project else { return };
20609
20610 // If None, we are in a file without an extension
20611 let file = self
20612 .buffer
20613 .read(cx)
20614 .as_singleton()
20615 .and_then(|b| b.read(cx).file());
20616 let file_extension = file_extension.or(file
20617 .as_ref()
20618 .and_then(|file| Path::new(file.file_name(cx)).extension())
20619 .and_then(|e| e.to_str())
20620 .map(|a| a.to_string()));
20621
20622 let vim_mode = vim_enabled(cx);
20623
20624 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20625 let copilot_enabled = edit_predictions_provider
20626 == language::language_settings::EditPredictionProvider::Copilot;
20627 let copilot_enabled_for_language = self
20628 .buffer
20629 .read(cx)
20630 .language_settings(cx)
20631 .show_edit_predictions;
20632
20633 let project = project.read(cx);
20634 let event_type = reported_event.event_type();
20635
20636 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20637 telemetry::event!(
20638 event_type,
20639 type = if auto_saved {"autosave"} else {"manual"},
20640 file_extension,
20641 vim_mode,
20642 copilot_enabled,
20643 copilot_enabled_for_language,
20644 edit_predictions_provider,
20645 is_via_ssh = project.is_via_ssh(),
20646 );
20647 } else {
20648 telemetry::event!(
20649 event_type,
20650 file_extension,
20651 vim_mode,
20652 copilot_enabled,
20653 copilot_enabled_for_language,
20654 edit_predictions_provider,
20655 is_via_ssh = project.is_via_ssh(),
20656 );
20657 };
20658 }
20659
20660 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20661 /// with each line being an array of {text, highlight} objects.
20662 fn copy_highlight_json(
20663 &mut self,
20664 _: &CopyHighlightJson,
20665 window: &mut Window,
20666 cx: &mut Context<Self>,
20667 ) {
20668 #[derive(Serialize)]
20669 struct Chunk<'a> {
20670 text: String,
20671 highlight: Option<&'a str>,
20672 }
20673
20674 let snapshot = self.buffer.read(cx).snapshot(cx);
20675 let range = self
20676 .selected_text_range(false, window, cx)
20677 .and_then(|selection| {
20678 if selection.range.is_empty() {
20679 None
20680 } else {
20681 Some(selection.range)
20682 }
20683 })
20684 .unwrap_or_else(|| 0..snapshot.len());
20685
20686 let chunks = snapshot.chunks(range, true);
20687 let mut lines = Vec::new();
20688 let mut line: VecDeque<Chunk> = VecDeque::new();
20689
20690 let Some(style) = self.style.as_ref() else {
20691 return;
20692 };
20693
20694 for chunk in chunks {
20695 let highlight = chunk
20696 .syntax_highlight_id
20697 .and_then(|id| id.name(&style.syntax));
20698 let mut chunk_lines = chunk.text.split('\n').peekable();
20699 while let Some(text) = chunk_lines.next() {
20700 let mut merged_with_last_token = false;
20701 if let Some(last_token) = line.back_mut()
20702 && last_token.highlight == highlight
20703 {
20704 last_token.text.push_str(text);
20705 merged_with_last_token = true;
20706 }
20707
20708 if !merged_with_last_token {
20709 line.push_back(Chunk {
20710 text: text.into(),
20711 highlight,
20712 });
20713 }
20714
20715 if chunk_lines.peek().is_some() {
20716 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20717 line.pop_front();
20718 }
20719 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20720 line.pop_back();
20721 }
20722
20723 lines.push(mem::take(&mut line));
20724 }
20725 }
20726 }
20727
20728 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20729 return;
20730 };
20731 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20732 }
20733
20734 pub fn open_context_menu(
20735 &mut self,
20736 _: &OpenContextMenu,
20737 window: &mut Window,
20738 cx: &mut Context<Self>,
20739 ) {
20740 self.request_autoscroll(Autoscroll::newest(), cx);
20741 let position = self.selections.newest_display(cx).start;
20742 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20743 }
20744
20745 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20746 &self.inlay_hint_cache
20747 }
20748
20749 pub fn replay_insert_event(
20750 &mut self,
20751 text: &str,
20752 relative_utf16_range: Option<Range<isize>>,
20753 window: &mut Window,
20754 cx: &mut Context<Self>,
20755 ) {
20756 if !self.input_enabled {
20757 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20758 return;
20759 }
20760 if let Some(relative_utf16_range) = relative_utf16_range {
20761 let selections = self.selections.all::<OffsetUtf16>(cx);
20762 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20763 let new_ranges = selections.into_iter().map(|range| {
20764 let start = OffsetUtf16(
20765 range
20766 .head()
20767 .0
20768 .saturating_add_signed(relative_utf16_range.start),
20769 );
20770 let end = OffsetUtf16(
20771 range
20772 .head()
20773 .0
20774 .saturating_add_signed(relative_utf16_range.end),
20775 );
20776 start..end
20777 });
20778 s.select_ranges(new_ranges);
20779 });
20780 }
20781
20782 self.handle_input(text, window, cx);
20783 }
20784
20785 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20786 let Some(provider) = self.semantics_provider.as_ref() else {
20787 return false;
20788 };
20789
20790 let mut supports = false;
20791 self.buffer().update(cx, |this, cx| {
20792 this.for_each_buffer(|buffer| {
20793 supports |= provider.supports_inlay_hints(buffer, cx);
20794 });
20795 });
20796
20797 supports
20798 }
20799
20800 pub fn is_focused(&self, window: &Window) -> bool {
20801 self.focus_handle.is_focused(window)
20802 }
20803
20804 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20805 cx.emit(EditorEvent::Focused);
20806
20807 if let Some(descendant) = self
20808 .last_focused_descendant
20809 .take()
20810 .and_then(|descendant| descendant.upgrade())
20811 {
20812 window.focus(&descendant);
20813 } else {
20814 if let Some(blame) = self.blame.as_ref() {
20815 blame.update(cx, GitBlame::focus)
20816 }
20817
20818 self.blink_manager.update(cx, BlinkManager::enable);
20819 self.show_cursor_names(window, cx);
20820 self.buffer.update(cx, |buffer, cx| {
20821 buffer.finalize_last_transaction(cx);
20822 if self.leader_id.is_none() {
20823 buffer.set_active_selections(
20824 &self.selections.disjoint_anchors(),
20825 self.selections.line_mode,
20826 self.cursor_shape,
20827 cx,
20828 );
20829 }
20830 });
20831 }
20832 }
20833
20834 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20835 cx.emit(EditorEvent::FocusedIn)
20836 }
20837
20838 fn handle_focus_out(
20839 &mut self,
20840 event: FocusOutEvent,
20841 _window: &mut Window,
20842 cx: &mut Context<Self>,
20843 ) {
20844 if event.blurred != self.focus_handle {
20845 self.last_focused_descendant = Some(event.blurred);
20846 }
20847 self.selection_drag_state = SelectionDragState::None;
20848 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20849 }
20850
20851 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20852 self.blink_manager.update(cx, BlinkManager::disable);
20853 self.buffer
20854 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20855
20856 if let Some(blame) = self.blame.as_ref() {
20857 blame.update(cx, GitBlame::blur)
20858 }
20859 if !self.hover_state.focused(window, cx) {
20860 hide_hover(self, cx);
20861 }
20862 if !self
20863 .context_menu
20864 .borrow()
20865 .as_ref()
20866 .is_some_and(|context_menu| context_menu.focused(window, cx))
20867 {
20868 self.hide_context_menu(window, cx);
20869 }
20870 self.discard_edit_prediction(false, cx);
20871 cx.emit(EditorEvent::Blurred);
20872 cx.notify();
20873 }
20874
20875 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20876 let mut pending: String = window
20877 .pending_input_keystrokes()
20878 .into_iter()
20879 .flatten()
20880 .filter_map(|keystroke| {
20881 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20882 keystroke.key_char.clone()
20883 } else {
20884 None
20885 }
20886 })
20887 .collect();
20888
20889 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20890 pending = "".to_string();
20891 }
20892
20893 let existing_pending = self
20894 .text_highlights::<PendingInput>(cx)
20895 .map(|(_, ranges)| ranges.to_vec());
20896 if existing_pending.is_none() && pending.is_empty() {
20897 return;
20898 }
20899 let transaction =
20900 self.transact(window, cx, |this, window, cx| {
20901 let selections = this.selections.all::<usize>(cx);
20902 let edits = selections
20903 .iter()
20904 .map(|selection| (selection.end..selection.end, pending.clone()));
20905 this.edit(edits, cx);
20906 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20907 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20908 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20909 }));
20910 });
20911 if let Some(existing_ranges) = existing_pending {
20912 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20913 this.edit(edits, cx);
20914 }
20915 });
20916
20917 let snapshot = self.snapshot(window, cx);
20918 let ranges = self
20919 .selections
20920 .all::<usize>(cx)
20921 .into_iter()
20922 .map(|selection| {
20923 snapshot.buffer_snapshot.anchor_after(selection.end)
20924 ..snapshot
20925 .buffer_snapshot
20926 .anchor_before(selection.end + pending.len())
20927 })
20928 .collect();
20929
20930 if pending.is_empty() {
20931 self.clear_highlights::<PendingInput>(cx);
20932 } else {
20933 self.highlight_text::<PendingInput>(
20934 ranges,
20935 HighlightStyle {
20936 underline: Some(UnderlineStyle {
20937 thickness: px(1.),
20938 color: None,
20939 wavy: false,
20940 }),
20941 ..Default::default()
20942 },
20943 cx,
20944 );
20945 }
20946
20947 self.ime_transaction = self.ime_transaction.or(transaction);
20948 if let Some(transaction) = self.ime_transaction {
20949 self.buffer.update(cx, |buffer, cx| {
20950 buffer.group_until_transaction(transaction, cx);
20951 });
20952 }
20953
20954 if self.text_highlights::<PendingInput>(cx).is_none() {
20955 self.ime_transaction.take();
20956 }
20957 }
20958
20959 pub fn register_action_renderer(
20960 &mut self,
20961 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20962 ) -> Subscription {
20963 let id = self.next_editor_action_id.post_inc();
20964 self.editor_actions
20965 .borrow_mut()
20966 .insert(id, Box::new(listener));
20967
20968 let editor_actions = self.editor_actions.clone();
20969 Subscription::new(move || {
20970 editor_actions.borrow_mut().remove(&id);
20971 })
20972 }
20973
20974 pub fn register_action<A: Action>(
20975 &mut self,
20976 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20977 ) -> Subscription {
20978 let id = self.next_editor_action_id.post_inc();
20979 let listener = Arc::new(listener);
20980 self.editor_actions.borrow_mut().insert(
20981 id,
20982 Box::new(move |_, window, _| {
20983 let listener = listener.clone();
20984 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20985 let action = action.downcast_ref().unwrap();
20986 if phase == DispatchPhase::Bubble {
20987 listener(action, window, cx)
20988 }
20989 })
20990 }),
20991 );
20992
20993 let editor_actions = self.editor_actions.clone();
20994 Subscription::new(move || {
20995 editor_actions.borrow_mut().remove(&id);
20996 })
20997 }
20998
20999 pub fn file_header_size(&self) -> u32 {
21000 FILE_HEADER_HEIGHT
21001 }
21002
21003 pub fn restore(
21004 &mut self,
21005 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21006 window: &mut Window,
21007 cx: &mut Context<Self>,
21008 ) {
21009 let workspace = self.workspace();
21010 let project = self.project();
21011 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21012 let mut tasks = Vec::new();
21013 for (buffer_id, changes) in revert_changes {
21014 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21015 buffer.update(cx, |buffer, cx| {
21016 buffer.edit(
21017 changes
21018 .into_iter()
21019 .map(|(range, text)| (range, text.to_string())),
21020 None,
21021 cx,
21022 );
21023 });
21024
21025 if let Some(project) =
21026 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21027 {
21028 project.update(cx, |project, cx| {
21029 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21030 })
21031 }
21032 }
21033 }
21034 tasks
21035 });
21036 cx.spawn_in(window, async move |_, cx| {
21037 for (buffer, task) in save_tasks {
21038 let result = task.await;
21039 if result.is_err() {
21040 let Some(path) = buffer
21041 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21042 .ok()
21043 else {
21044 continue;
21045 };
21046 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21047 let Some(task) = cx
21048 .update_window_entity(workspace, |workspace, window, cx| {
21049 workspace
21050 .open_path_preview(path, None, false, false, false, window, cx)
21051 })
21052 .ok()
21053 else {
21054 continue;
21055 };
21056 task.await.log_err();
21057 }
21058 }
21059 }
21060 })
21061 .detach();
21062 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21063 selections.refresh()
21064 });
21065 }
21066
21067 pub fn to_pixel_point(
21068 &self,
21069 source: multi_buffer::Anchor,
21070 editor_snapshot: &EditorSnapshot,
21071 window: &mut Window,
21072 ) -> Option<gpui::Point<Pixels>> {
21073 let source_point = source.to_display_point(editor_snapshot);
21074 self.display_to_pixel_point(source_point, editor_snapshot, window)
21075 }
21076
21077 pub fn display_to_pixel_point(
21078 &self,
21079 source: DisplayPoint,
21080 editor_snapshot: &EditorSnapshot,
21081 window: &mut Window,
21082 ) -> Option<gpui::Point<Pixels>> {
21083 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21084 let text_layout_details = self.text_layout_details(window);
21085 let scroll_top = text_layout_details
21086 .scroll_anchor
21087 .scroll_position(editor_snapshot)
21088 .y;
21089
21090 if source.row().as_f32() < scroll_top.floor() {
21091 return None;
21092 }
21093 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21094 let source_y = line_height * (source.row().as_f32() - scroll_top);
21095 Some(gpui::Point::new(source_x, source_y))
21096 }
21097
21098 pub fn has_visible_completions_menu(&self) -> bool {
21099 !self.edit_prediction_preview_is_active()
21100 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21101 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21102 })
21103 }
21104
21105 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21106 if self.mode.is_minimap() {
21107 return;
21108 }
21109 self.addons
21110 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21111 }
21112
21113 pub fn unregister_addon<T: Addon>(&mut self) {
21114 self.addons.remove(&std::any::TypeId::of::<T>());
21115 }
21116
21117 pub fn addon<T: Addon>(&self) -> Option<&T> {
21118 let type_id = std::any::TypeId::of::<T>();
21119 self.addons
21120 .get(&type_id)
21121 .and_then(|item| item.to_any().downcast_ref::<T>())
21122 }
21123
21124 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21125 let type_id = std::any::TypeId::of::<T>();
21126 self.addons
21127 .get_mut(&type_id)
21128 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21129 }
21130
21131 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21132 let text_layout_details = self.text_layout_details(window);
21133 let style = &text_layout_details.editor_style;
21134 let font_id = window.text_system().resolve_font(&style.text.font());
21135 let font_size = style.text.font_size.to_pixels(window.rem_size());
21136 let line_height = style.text.line_height_in_pixels(window.rem_size());
21137 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21138 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21139
21140 CharacterDimensions {
21141 em_width,
21142 em_advance,
21143 line_height,
21144 }
21145 }
21146
21147 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21148 self.load_diff_task.clone()
21149 }
21150
21151 fn read_metadata_from_db(
21152 &mut self,
21153 item_id: u64,
21154 workspace_id: WorkspaceId,
21155 window: &mut Window,
21156 cx: &mut Context<Editor>,
21157 ) {
21158 if self.is_singleton(cx)
21159 && !self.mode.is_minimap()
21160 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21161 {
21162 let buffer_snapshot = OnceCell::new();
21163
21164 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21165 && !folds.is_empty()
21166 {
21167 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21168 self.fold_ranges(
21169 folds
21170 .into_iter()
21171 .map(|(start, end)| {
21172 snapshot.clip_offset(start, Bias::Left)
21173 ..snapshot.clip_offset(end, Bias::Right)
21174 })
21175 .collect(),
21176 false,
21177 window,
21178 cx,
21179 );
21180 }
21181
21182 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21183 && !selections.is_empty()
21184 {
21185 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21186 // skip adding the initial selection to selection history
21187 self.selection_history.mode = SelectionHistoryMode::Skipping;
21188 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21189 s.select_ranges(selections.into_iter().map(|(start, end)| {
21190 snapshot.clip_offset(start, Bias::Left)
21191 ..snapshot.clip_offset(end, Bias::Right)
21192 }));
21193 });
21194 self.selection_history.mode = SelectionHistoryMode::Normal;
21195 };
21196 }
21197
21198 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21199 }
21200
21201 fn update_lsp_data(
21202 &mut self,
21203 ignore_cache: bool,
21204 for_buffer: Option<BufferId>,
21205 window: &mut Window,
21206 cx: &mut Context<'_, Self>,
21207 ) {
21208 self.pull_diagnostics(for_buffer, window, cx);
21209 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21210 }
21211}
21212
21213fn vim_enabled(cx: &App) -> bool {
21214 cx.global::<SettingsStore>()
21215 .raw_user_settings()
21216 .get("vim_mode")
21217 == Some(&serde_json::Value::Bool(true))
21218}
21219
21220fn process_completion_for_edit(
21221 completion: &Completion,
21222 intent: CompletionIntent,
21223 buffer: &Entity<Buffer>,
21224 cursor_position: &text::Anchor,
21225 cx: &mut Context<Editor>,
21226) -> CompletionEdit {
21227 let buffer = buffer.read(cx);
21228 let buffer_snapshot = buffer.snapshot();
21229 let (snippet, new_text) = if completion.is_snippet() {
21230 // Workaround for typescript language server issues so that methods don't expand within
21231 // strings and functions with type expressions. The previous point is used because the query
21232 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21233 let mut snippet_source = completion.new_text.clone();
21234 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21235 previous_point.column = previous_point.column.saturating_sub(1);
21236 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21237 && scope.prefers_label_for_snippet_in_completion()
21238 && let Some(label) = completion.label()
21239 && matches!(
21240 completion.kind(),
21241 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21242 )
21243 {
21244 snippet_source = label;
21245 }
21246 match Snippet::parse(&snippet_source).log_err() {
21247 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21248 None => (None, completion.new_text.clone()),
21249 }
21250 } else {
21251 (None, completion.new_text.clone())
21252 };
21253
21254 let mut range_to_replace = {
21255 let replace_range = &completion.replace_range;
21256 if let CompletionSource::Lsp {
21257 insert_range: Some(insert_range),
21258 ..
21259 } = &completion.source
21260 {
21261 debug_assert_eq!(
21262 insert_range.start, replace_range.start,
21263 "insert_range and replace_range should start at the same position"
21264 );
21265 debug_assert!(
21266 insert_range
21267 .start
21268 .cmp(cursor_position, &buffer_snapshot)
21269 .is_le(),
21270 "insert_range should start before or at cursor position"
21271 );
21272 debug_assert!(
21273 replace_range
21274 .start
21275 .cmp(cursor_position, &buffer_snapshot)
21276 .is_le(),
21277 "replace_range should start before or at cursor position"
21278 );
21279
21280 let should_replace = match intent {
21281 CompletionIntent::CompleteWithInsert => false,
21282 CompletionIntent::CompleteWithReplace => true,
21283 CompletionIntent::Complete | CompletionIntent::Compose => {
21284 let insert_mode =
21285 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21286 .completions
21287 .lsp_insert_mode;
21288 match insert_mode {
21289 LspInsertMode::Insert => false,
21290 LspInsertMode::Replace => true,
21291 LspInsertMode::ReplaceSubsequence => {
21292 let mut text_to_replace = buffer.chars_for_range(
21293 buffer.anchor_before(replace_range.start)
21294 ..buffer.anchor_after(replace_range.end),
21295 );
21296 let mut current_needle = text_to_replace.next();
21297 for haystack_ch in completion.label.text.chars() {
21298 if let Some(needle_ch) = current_needle
21299 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21300 {
21301 current_needle = text_to_replace.next();
21302 }
21303 }
21304 current_needle.is_none()
21305 }
21306 LspInsertMode::ReplaceSuffix => {
21307 if replace_range
21308 .end
21309 .cmp(cursor_position, &buffer_snapshot)
21310 .is_gt()
21311 {
21312 let range_after_cursor = *cursor_position..replace_range.end;
21313 let text_after_cursor = buffer
21314 .text_for_range(
21315 buffer.anchor_before(range_after_cursor.start)
21316 ..buffer.anchor_after(range_after_cursor.end),
21317 )
21318 .collect::<String>()
21319 .to_ascii_lowercase();
21320 completion
21321 .label
21322 .text
21323 .to_ascii_lowercase()
21324 .ends_with(&text_after_cursor)
21325 } else {
21326 true
21327 }
21328 }
21329 }
21330 }
21331 };
21332
21333 if should_replace {
21334 replace_range.clone()
21335 } else {
21336 insert_range.clone()
21337 }
21338 } else {
21339 replace_range.clone()
21340 }
21341 };
21342
21343 if range_to_replace
21344 .end
21345 .cmp(cursor_position, &buffer_snapshot)
21346 .is_lt()
21347 {
21348 range_to_replace.end = *cursor_position;
21349 }
21350
21351 CompletionEdit {
21352 new_text,
21353 replace_range: range_to_replace.to_offset(buffer),
21354 snippet,
21355 }
21356}
21357
21358struct CompletionEdit {
21359 new_text: String,
21360 replace_range: Range<usize>,
21361 snippet: Option<Snippet>,
21362}
21363
21364fn insert_extra_newline_brackets(
21365 buffer: &MultiBufferSnapshot,
21366 range: Range<usize>,
21367 language: &language::LanguageScope,
21368) -> bool {
21369 let leading_whitespace_len = buffer
21370 .reversed_chars_at(range.start)
21371 .take_while(|c| c.is_whitespace() && *c != '\n')
21372 .map(|c| c.len_utf8())
21373 .sum::<usize>();
21374 let trailing_whitespace_len = buffer
21375 .chars_at(range.end)
21376 .take_while(|c| c.is_whitespace() && *c != '\n')
21377 .map(|c| c.len_utf8())
21378 .sum::<usize>();
21379 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21380
21381 language.brackets().any(|(pair, enabled)| {
21382 let pair_start = pair.start.trim_end();
21383 let pair_end = pair.end.trim_start();
21384
21385 enabled
21386 && pair.newline
21387 && buffer.contains_str_at(range.end, pair_end)
21388 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21389 })
21390}
21391
21392fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21393 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21394 [(buffer, range, _)] => (*buffer, range.clone()),
21395 _ => return false,
21396 };
21397 let pair = {
21398 let mut result: Option<BracketMatch> = None;
21399
21400 for pair in buffer
21401 .all_bracket_ranges(range.clone())
21402 .filter(move |pair| {
21403 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21404 })
21405 {
21406 let len = pair.close_range.end - pair.open_range.start;
21407
21408 if let Some(existing) = &result {
21409 let existing_len = existing.close_range.end - existing.open_range.start;
21410 if len > existing_len {
21411 continue;
21412 }
21413 }
21414
21415 result = Some(pair);
21416 }
21417
21418 result
21419 };
21420 let Some(pair) = pair else {
21421 return false;
21422 };
21423 pair.newline_only
21424 && buffer
21425 .chars_for_range(pair.open_range.end..range.start)
21426 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21427 .all(|c| c.is_whitespace() && c != '\n')
21428}
21429
21430fn update_uncommitted_diff_for_buffer(
21431 editor: Entity<Editor>,
21432 project: &Entity<Project>,
21433 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21434 buffer: Entity<MultiBuffer>,
21435 cx: &mut App,
21436) -> Task<()> {
21437 let mut tasks = Vec::new();
21438 project.update(cx, |project, cx| {
21439 for buffer in buffers {
21440 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21441 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21442 }
21443 }
21444 });
21445 cx.spawn(async move |cx| {
21446 let diffs = future::join_all(tasks).await;
21447 if editor
21448 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21449 .unwrap_or(false)
21450 {
21451 return;
21452 }
21453
21454 buffer
21455 .update(cx, |buffer, cx| {
21456 for diff in diffs.into_iter().flatten() {
21457 buffer.add_diff(diff, cx);
21458 }
21459 })
21460 .ok();
21461 })
21462}
21463
21464fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21465 let tab_size = tab_size.get() as usize;
21466 let mut width = offset;
21467
21468 for ch in text.chars() {
21469 width += if ch == '\t' {
21470 tab_size - (width % tab_size)
21471 } else {
21472 1
21473 };
21474 }
21475
21476 width - offset
21477}
21478
21479#[cfg(test)]
21480mod tests {
21481 use super::*;
21482
21483 #[test]
21484 fn test_string_size_with_expanded_tabs() {
21485 let nz = |val| NonZeroU32::new(val).unwrap();
21486 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21487 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21488 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21489 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21490 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21491 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21492 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21493 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21494 }
21495}
21496
21497/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21498struct WordBreakingTokenizer<'a> {
21499 input: &'a str,
21500}
21501
21502impl<'a> WordBreakingTokenizer<'a> {
21503 fn new(input: &'a str) -> Self {
21504 Self { input }
21505 }
21506}
21507
21508fn is_char_ideographic(ch: char) -> bool {
21509 use unicode_script::Script::*;
21510 use unicode_script::UnicodeScript;
21511 matches!(ch.script(), Han | Tangut | Yi)
21512}
21513
21514fn is_grapheme_ideographic(text: &str) -> bool {
21515 text.chars().any(is_char_ideographic)
21516}
21517
21518fn is_grapheme_whitespace(text: &str) -> bool {
21519 text.chars().any(|x| x.is_whitespace())
21520}
21521
21522fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21523 text.chars()
21524 .next()
21525 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21526}
21527
21528#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21529enum WordBreakToken<'a> {
21530 Word { token: &'a str, grapheme_len: usize },
21531 InlineWhitespace { token: &'a str, grapheme_len: usize },
21532 Newline,
21533}
21534
21535impl<'a> Iterator for WordBreakingTokenizer<'a> {
21536 /// Yields a span, the count of graphemes in the token, and whether it was
21537 /// whitespace. Note that it also breaks at word boundaries.
21538 type Item = WordBreakToken<'a>;
21539
21540 fn next(&mut self) -> Option<Self::Item> {
21541 use unicode_segmentation::UnicodeSegmentation;
21542 if self.input.is_empty() {
21543 return None;
21544 }
21545
21546 let mut iter = self.input.graphemes(true).peekable();
21547 let mut offset = 0;
21548 let mut grapheme_len = 0;
21549 if let Some(first_grapheme) = iter.next() {
21550 let is_newline = first_grapheme == "\n";
21551 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21552 offset += first_grapheme.len();
21553 grapheme_len += 1;
21554 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21555 if let Some(grapheme) = iter.peek().copied()
21556 && should_stay_with_preceding_ideograph(grapheme)
21557 {
21558 offset += grapheme.len();
21559 grapheme_len += 1;
21560 }
21561 } else {
21562 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21563 let mut next_word_bound = words.peek().copied();
21564 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21565 next_word_bound = words.next();
21566 }
21567 while let Some(grapheme) = iter.peek().copied() {
21568 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21569 break;
21570 };
21571 if is_grapheme_whitespace(grapheme) != is_whitespace
21572 || (grapheme == "\n") != is_newline
21573 {
21574 break;
21575 };
21576 offset += grapheme.len();
21577 grapheme_len += 1;
21578 iter.next();
21579 }
21580 }
21581 let token = &self.input[..offset];
21582 self.input = &self.input[offset..];
21583 if token == "\n" {
21584 Some(WordBreakToken::Newline)
21585 } else if is_whitespace {
21586 Some(WordBreakToken::InlineWhitespace {
21587 token,
21588 grapheme_len,
21589 })
21590 } else {
21591 Some(WordBreakToken::Word {
21592 token,
21593 grapheme_len,
21594 })
21595 }
21596 } else {
21597 None
21598 }
21599 }
21600}
21601
21602#[test]
21603fn test_word_breaking_tokenizer() {
21604 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21605 ("", &[]),
21606 (" ", &[whitespace(" ", 2)]),
21607 ("Ʒ", &[word("Ʒ", 1)]),
21608 ("Ǽ", &[word("Ǽ", 1)]),
21609 ("⋑", &[word("⋑", 1)]),
21610 ("⋑⋑", &[word("⋑⋑", 2)]),
21611 (
21612 "原理,进而",
21613 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21614 ),
21615 (
21616 "hello world",
21617 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21618 ),
21619 (
21620 "hello, world",
21621 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21622 ),
21623 (
21624 " hello world",
21625 &[
21626 whitespace(" ", 2),
21627 word("hello", 5),
21628 whitespace(" ", 1),
21629 word("world", 5),
21630 ],
21631 ),
21632 (
21633 "这是什么 \n 钢笔",
21634 &[
21635 word("这", 1),
21636 word("是", 1),
21637 word("什", 1),
21638 word("么", 1),
21639 whitespace(" ", 1),
21640 newline(),
21641 whitespace(" ", 1),
21642 word("钢", 1),
21643 word("笔", 1),
21644 ],
21645 ),
21646 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21647 ];
21648
21649 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21650 WordBreakToken::Word {
21651 token,
21652 grapheme_len,
21653 }
21654 }
21655
21656 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21657 WordBreakToken::InlineWhitespace {
21658 token,
21659 grapheme_len,
21660 }
21661 }
21662
21663 fn newline() -> WordBreakToken<'static> {
21664 WordBreakToken::Newline
21665 }
21666
21667 for (input, result) in tests {
21668 assert_eq!(
21669 WordBreakingTokenizer::new(input)
21670 .collect::<Vec<_>>()
21671 .as_slice(),
21672 *result,
21673 );
21674 }
21675}
21676
21677fn wrap_with_prefix(
21678 first_line_prefix: String,
21679 subsequent_lines_prefix: String,
21680 unwrapped_text: String,
21681 wrap_column: usize,
21682 tab_size: NonZeroU32,
21683 preserve_existing_whitespace: bool,
21684) -> String {
21685 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21686 let subsequent_lines_prefix_len =
21687 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21688 let mut wrapped_text = String::new();
21689 let mut current_line = first_line_prefix;
21690 let mut is_first_line = true;
21691
21692 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21693 let mut current_line_len = first_line_prefix_len;
21694 let mut in_whitespace = false;
21695 for token in tokenizer {
21696 let have_preceding_whitespace = in_whitespace;
21697 match token {
21698 WordBreakToken::Word {
21699 token,
21700 grapheme_len,
21701 } => {
21702 in_whitespace = false;
21703 let current_prefix_len = if is_first_line {
21704 first_line_prefix_len
21705 } else {
21706 subsequent_lines_prefix_len
21707 };
21708 if current_line_len + grapheme_len > wrap_column
21709 && current_line_len != current_prefix_len
21710 {
21711 wrapped_text.push_str(current_line.trim_end());
21712 wrapped_text.push('\n');
21713 is_first_line = false;
21714 current_line = subsequent_lines_prefix.clone();
21715 current_line_len = subsequent_lines_prefix_len;
21716 }
21717 current_line.push_str(token);
21718 current_line_len += grapheme_len;
21719 }
21720 WordBreakToken::InlineWhitespace {
21721 mut token,
21722 mut grapheme_len,
21723 } => {
21724 in_whitespace = true;
21725 if have_preceding_whitespace && !preserve_existing_whitespace {
21726 continue;
21727 }
21728 if !preserve_existing_whitespace {
21729 token = " ";
21730 grapheme_len = 1;
21731 }
21732 let current_prefix_len = if is_first_line {
21733 first_line_prefix_len
21734 } else {
21735 subsequent_lines_prefix_len
21736 };
21737 if current_line_len + grapheme_len > wrap_column {
21738 wrapped_text.push_str(current_line.trim_end());
21739 wrapped_text.push('\n');
21740 is_first_line = false;
21741 current_line = subsequent_lines_prefix.clone();
21742 current_line_len = subsequent_lines_prefix_len;
21743 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21744 current_line.push_str(token);
21745 current_line_len += grapheme_len;
21746 }
21747 }
21748 WordBreakToken::Newline => {
21749 in_whitespace = true;
21750 let current_prefix_len = if is_first_line {
21751 first_line_prefix_len
21752 } else {
21753 subsequent_lines_prefix_len
21754 };
21755 if preserve_existing_whitespace {
21756 wrapped_text.push_str(current_line.trim_end());
21757 wrapped_text.push('\n');
21758 is_first_line = false;
21759 current_line = subsequent_lines_prefix.clone();
21760 current_line_len = subsequent_lines_prefix_len;
21761 } else if have_preceding_whitespace {
21762 continue;
21763 } else if current_line_len + 1 > wrap_column
21764 && current_line_len != current_prefix_len
21765 {
21766 wrapped_text.push_str(current_line.trim_end());
21767 wrapped_text.push('\n');
21768 is_first_line = false;
21769 current_line = subsequent_lines_prefix.clone();
21770 current_line_len = subsequent_lines_prefix_len;
21771 } else if current_line_len != current_prefix_len {
21772 current_line.push(' ');
21773 current_line_len += 1;
21774 }
21775 }
21776 }
21777 }
21778
21779 if !current_line.is_empty() {
21780 wrapped_text.push_str(¤t_line);
21781 }
21782 wrapped_text
21783}
21784
21785#[test]
21786fn test_wrap_with_prefix() {
21787 assert_eq!(
21788 wrap_with_prefix(
21789 "# ".to_string(),
21790 "# ".to_string(),
21791 "abcdefg".to_string(),
21792 4,
21793 NonZeroU32::new(4).unwrap(),
21794 false,
21795 ),
21796 "# abcdefg"
21797 );
21798 assert_eq!(
21799 wrap_with_prefix(
21800 "".to_string(),
21801 "".to_string(),
21802 "\thello world".to_string(),
21803 8,
21804 NonZeroU32::new(4).unwrap(),
21805 false,
21806 ),
21807 "hello\nworld"
21808 );
21809 assert_eq!(
21810 wrap_with_prefix(
21811 "// ".to_string(),
21812 "// ".to_string(),
21813 "xx \nyy zz aa bb cc".to_string(),
21814 12,
21815 NonZeroU32::new(4).unwrap(),
21816 false,
21817 ),
21818 "// xx yy zz\n// aa bb cc"
21819 );
21820 assert_eq!(
21821 wrap_with_prefix(
21822 String::new(),
21823 String::new(),
21824 "这是什么 \n 钢笔".to_string(),
21825 3,
21826 NonZeroU32::new(4).unwrap(),
21827 false,
21828 ),
21829 "这是什\n么 钢\n笔"
21830 );
21831}
21832
21833pub trait CollaborationHub {
21834 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21835 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21836 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21837}
21838
21839impl CollaborationHub for Entity<Project> {
21840 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21841 self.read(cx).collaborators()
21842 }
21843
21844 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21845 self.read(cx).user_store().read(cx).participant_indices()
21846 }
21847
21848 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21849 let this = self.read(cx);
21850 let user_ids = this.collaborators().values().map(|c| c.user_id);
21851 this.user_store().read(cx).participant_names(user_ids, cx)
21852 }
21853}
21854
21855pub trait SemanticsProvider {
21856 fn hover(
21857 &self,
21858 buffer: &Entity<Buffer>,
21859 position: text::Anchor,
21860 cx: &mut App,
21861 ) -> Option<Task<Option<Vec<project::Hover>>>>;
21862
21863 fn inline_values(
21864 &self,
21865 buffer_handle: Entity<Buffer>,
21866 range: Range<text::Anchor>,
21867 cx: &mut App,
21868 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21869
21870 fn inlay_hints(
21871 &self,
21872 buffer_handle: Entity<Buffer>,
21873 range: Range<text::Anchor>,
21874 cx: &mut App,
21875 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21876
21877 fn resolve_inlay_hint(
21878 &self,
21879 hint: InlayHint,
21880 buffer_handle: Entity<Buffer>,
21881 server_id: LanguageServerId,
21882 cx: &mut App,
21883 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21884
21885 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21886
21887 fn document_highlights(
21888 &self,
21889 buffer: &Entity<Buffer>,
21890 position: text::Anchor,
21891 cx: &mut App,
21892 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21893
21894 fn definitions(
21895 &self,
21896 buffer: &Entity<Buffer>,
21897 position: text::Anchor,
21898 kind: GotoDefinitionKind,
21899 cx: &mut App,
21900 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
21901
21902 fn range_for_rename(
21903 &self,
21904 buffer: &Entity<Buffer>,
21905 position: text::Anchor,
21906 cx: &mut App,
21907 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21908
21909 fn perform_rename(
21910 &self,
21911 buffer: &Entity<Buffer>,
21912 position: text::Anchor,
21913 new_name: String,
21914 cx: &mut App,
21915 ) -> Option<Task<Result<ProjectTransaction>>>;
21916}
21917
21918pub trait CompletionProvider {
21919 fn completions(
21920 &self,
21921 excerpt_id: ExcerptId,
21922 buffer: &Entity<Buffer>,
21923 buffer_position: text::Anchor,
21924 trigger: CompletionContext,
21925 window: &mut Window,
21926 cx: &mut Context<Editor>,
21927 ) -> Task<Result<Vec<CompletionResponse>>>;
21928
21929 fn resolve_completions(
21930 &self,
21931 _buffer: Entity<Buffer>,
21932 _completion_indices: Vec<usize>,
21933 _completions: Rc<RefCell<Box<[Completion]>>>,
21934 _cx: &mut Context<Editor>,
21935 ) -> Task<Result<bool>> {
21936 Task::ready(Ok(false))
21937 }
21938
21939 fn apply_additional_edits_for_completion(
21940 &self,
21941 _buffer: Entity<Buffer>,
21942 _completions: Rc<RefCell<Box<[Completion]>>>,
21943 _completion_index: usize,
21944 _push_to_history: bool,
21945 _cx: &mut Context<Editor>,
21946 ) -> Task<Result<Option<language::Transaction>>> {
21947 Task::ready(Ok(None))
21948 }
21949
21950 fn is_completion_trigger(
21951 &self,
21952 buffer: &Entity<Buffer>,
21953 position: language::Anchor,
21954 text: &str,
21955 trigger_in_words: bool,
21956 menu_is_open: bool,
21957 cx: &mut Context<Editor>,
21958 ) -> bool;
21959
21960 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21961
21962 fn sort_completions(&self) -> bool {
21963 true
21964 }
21965
21966 fn filter_completions(&self) -> bool {
21967 true
21968 }
21969}
21970
21971pub trait CodeActionProvider {
21972 fn id(&self) -> Arc<str>;
21973
21974 fn code_actions(
21975 &self,
21976 buffer: &Entity<Buffer>,
21977 range: Range<text::Anchor>,
21978 window: &mut Window,
21979 cx: &mut App,
21980 ) -> Task<Result<Vec<CodeAction>>>;
21981
21982 fn apply_code_action(
21983 &self,
21984 buffer_handle: Entity<Buffer>,
21985 action: CodeAction,
21986 excerpt_id: ExcerptId,
21987 push_to_history: bool,
21988 window: &mut Window,
21989 cx: &mut App,
21990 ) -> Task<Result<ProjectTransaction>>;
21991}
21992
21993impl CodeActionProvider for Entity<Project> {
21994 fn id(&self) -> Arc<str> {
21995 "project".into()
21996 }
21997
21998 fn code_actions(
21999 &self,
22000 buffer: &Entity<Buffer>,
22001 range: Range<text::Anchor>,
22002 _window: &mut Window,
22003 cx: &mut App,
22004 ) -> Task<Result<Vec<CodeAction>>> {
22005 self.update(cx, |project, cx| {
22006 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22007 let code_actions = project.code_actions(buffer, range, None, cx);
22008 cx.background_spawn(async move {
22009 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22010 Ok(code_lens_actions
22011 .context("code lens fetch")?
22012 .into_iter()
22013 .flatten()
22014 .chain(
22015 code_actions
22016 .context("code action fetch")?
22017 .into_iter()
22018 .flatten(),
22019 )
22020 .collect())
22021 })
22022 })
22023 }
22024
22025 fn apply_code_action(
22026 &self,
22027 buffer_handle: Entity<Buffer>,
22028 action: CodeAction,
22029 _excerpt_id: ExcerptId,
22030 push_to_history: bool,
22031 _window: &mut Window,
22032 cx: &mut App,
22033 ) -> Task<Result<ProjectTransaction>> {
22034 self.update(cx, |project, cx| {
22035 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22036 })
22037 }
22038}
22039
22040fn snippet_completions(
22041 project: &Project,
22042 buffer: &Entity<Buffer>,
22043 buffer_position: text::Anchor,
22044 cx: &mut App,
22045) -> Task<Result<CompletionResponse>> {
22046 let languages = buffer.read(cx).languages_at(buffer_position);
22047 let snippet_store = project.snippets().read(cx);
22048
22049 let scopes: Vec<_> = languages
22050 .iter()
22051 .filter_map(|language| {
22052 let language_name = language.lsp_id();
22053 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22054
22055 if snippets.is_empty() {
22056 None
22057 } else {
22058 Some((language.default_scope(), snippets))
22059 }
22060 })
22061 .collect();
22062
22063 if scopes.is_empty() {
22064 return Task::ready(Ok(CompletionResponse {
22065 completions: vec![],
22066 is_incomplete: false,
22067 }));
22068 }
22069
22070 let snapshot = buffer.read(cx).text_snapshot();
22071 let chars: String = snapshot
22072 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22073 .collect();
22074 let executor = cx.background_executor().clone();
22075
22076 cx.background_spawn(async move {
22077 let mut is_incomplete = false;
22078 let mut completions: Vec<Completion> = Vec::new();
22079 for (scope, snippets) in scopes.into_iter() {
22080 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22081 let mut last_word = chars
22082 .chars()
22083 .take_while(|c| classifier.is_word(*c))
22084 .collect::<String>();
22085 last_word = last_word.chars().rev().collect();
22086
22087 if last_word.is_empty() {
22088 return Ok(CompletionResponse {
22089 completions: vec![],
22090 is_incomplete: true,
22091 });
22092 }
22093
22094 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22095 let to_lsp = |point: &text::Anchor| {
22096 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22097 point_to_lsp(end)
22098 };
22099 let lsp_end = to_lsp(&buffer_position);
22100
22101 let candidates = snippets
22102 .iter()
22103 .enumerate()
22104 .flat_map(|(ix, snippet)| {
22105 snippet
22106 .prefix
22107 .iter()
22108 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22109 })
22110 .collect::<Vec<StringMatchCandidate>>();
22111
22112 const MAX_RESULTS: usize = 100;
22113 let mut matches = fuzzy::match_strings(
22114 &candidates,
22115 &last_word,
22116 last_word.chars().any(|c| c.is_uppercase()),
22117 true,
22118 MAX_RESULTS,
22119 &Default::default(),
22120 executor.clone(),
22121 )
22122 .await;
22123
22124 if matches.len() >= MAX_RESULTS {
22125 is_incomplete = true;
22126 }
22127
22128 // Remove all candidates where the query's start does not match the start of any word in the candidate
22129 if let Some(query_start) = last_word.chars().next() {
22130 matches.retain(|string_match| {
22131 split_words(&string_match.string).any(|word| {
22132 // Check that the first codepoint of the word as lowercase matches the first
22133 // codepoint of the query as lowercase
22134 word.chars()
22135 .flat_map(|codepoint| codepoint.to_lowercase())
22136 .zip(query_start.to_lowercase())
22137 .all(|(word_cp, query_cp)| word_cp == query_cp)
22138 })
22139 });
22140 }
22141
22142 let matched_strings = matches
22143 .into_iter()
22144 .map(|m| m.string)
22145 .collect::<HashSet<_>>();
22146
22147 completions.extend(snippets.iter().filter_map(|snippet| {
22148 let matching_prefix = snippet
22149 .prefix
22150 .iter()
22151 .find(|prefix| matched_strings.contains(*prefix))?;
22152 let start = as_offset - last_word.len();
22153 let start = snapshot.anchor_before(start);
22154 let range = start..buffer_position;
22155 let lsp_start = to_lsp(&start);
22156 let lsp_range = lsp::Range {
22157 start: lsp_start,
22158 end: lsp_end,
22159 };
22160 Some(Completion {
22161 replace_range: range,
22162 new_text: snippet.body.clone(),
22163 source: CompletionSource::Lsp {
22164 insert_range: None,
22165 server_id: LanguageServerId(usize::MAX),
22166 resolved: true,
22167 lsp_completion: Box::new(lsp::CompletionItem {
22168 label: snippet.prefix.first().unwrap().clone(),
22169 kind: Some(CompletionItemKind::SNIPPET),
22170 label_details: snippet.description.as_ref().map(|description| {
22171 lsp::CompletionItemLabelDetails {
22172 detail: Some(description.clone()),
22173 description: None,
22174 }
22175 }),
22176 insert_text_format: Some(InsertTextFormat::SNIPPET),
22177 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22178 lsp::InsertReplaceEdit {
22179 new_text: snippet.body.clone(),
22180 insert: lsp_range,
22181 replace: lsp_range,
22182 },
22183 )),
22184 filter_text: Some(snippet.body.clone()),
22185 sort_text: Some(char::MAX.to_string()),
22186 ..lsp::CompletionItem::default()
22187 }),
22188 lsp_defaults: None,
22189 },
22190 label: CodeLabel {
22191 text: matching_prefix.clone(),
22192 runs: Vec::new(),
22193 filter_range: 0..matching_prefix.len(),
22194 },
22195 icon_path: None,
22196 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22197 single_line: snippet.name.clone().into(),
22198 plain_text: snippet
22199 .description
22200 .clone()
22201 .map(|description| description.into()),
22202 }),
22203 insert_text_mode: None,
22204 confirm: None,
22205 })
22206 }))
22207 }
22208
22209 Ok(CompletionResponse {
22210 completions,
22211 is_incomplete,
22212 })
22213 })
22214}
22215
22216impl CompletionProvider for Entity<Project> {
22217 fn completions(
22218 &self,
22219 _excerpt_id: ExcerptId,
22220 buffer: &Entity<Buffer>,
22221 buffer_position: text::Anchor,
22222 options: CompletionContext,
22223 _window: &mut Window,
22224 cx: &mut Context<Editor>,
22225 ) -> Task<Result<Vec<CompletionResponse>>> {
22226 self.update(cx, |project, cx| {
22227 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22228 let project_completions = project.completions(buffer, buffer_position, options, cx);
22229 cx.background_spawn(async move {
22230 let mut responses = project_completions.await?;
22231 let snippets = snippets.await?;
22232 if !snippets.completions.is_empty() {
22233 responses.push(snippets);
22234 }
22235 Ok(responses)
22236 })
22237 })
22238 }
22239
22240 fn resolve_completions(
22241 &self,
22242 buffer: Entity<Buffer>,
22243 completion_indices: Vec<usize>,
22244 completions: Rc<RefCell<Box<[Completion]>>>,
22245 cx: &mut Context<Editor>,
22246 ) -> Task<Result<bool>> {
22247 self.update(cx, |project, cx| {
22248 project.lsp_store().update(cx, |lsp_store, cx| {
22249 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22250 })
22251 })
22252 }
22253
22254 fn apply_additional_edits_for_completion(
22255 &self,
22256 buffer: Entity<Buffer>,
22257 completions: Rc<RefCell<Box<[Completion]>>>,
22258 completion_index: usize,
22259 push_to_history: bool,
22260 cx: &mut Context<Editor>,
22261 ) -> Task<Result<Option<language::Transaction>>> {
22262 self.update(cx, |project, cx| {
22263 project.lsp_store().update(cx, |lsp_store, cx| {
22264 lsp_store.apply_additional_edits_for_completion(
22265 buffer,
22266 completions,
22267 completion_index,
22268 push_to_history,
22269 cx,
22270 )
22271 })
22272 })
22273 }
22274
22275 fn is_completion_trigger(
22276 &self,
22277 buffer: &Entity<Buffer>,
22278 position: language::Anchor,
22279 text: &str,
22280 trigger_in_words: bool,
22281 menu_is_open: bool,
22282 cx: &mut Context<Editor>,
22283 ) -> bool {
22284 let mut chars = text.chars();
22285 let char = if let Some(char) = chars.next() {
22286 char
22287 } else {
22288 return false;
22289 };
22290 if chars.next().is_some() {
22291 return false;
22292 }
22293
22294 let buffer = buffer.read(cx);
22295 let snapshot = buffer.snapshot();
22296 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22297 return false;
22298 }
22299 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22300 if trigger_in_words && classifier.is_word(char) {
22301 return true;
22302 }
22303
22304 buffer.completion_triggers().contains(text)
22305 }
22306}
22307
22308impl SemanticsProvider for Entity<Project> {
22309 fn hover(
22310 &self,
22311 buffer: &Entity<Buffer>,
22312 position: text::Anchor,
22313 cx: &mut App,
22314 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22315 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22316 }
22317
22318 fn document_highlights(
22319 &self,
22320 buffer: &Entity<Buffer>,
22321 position: text::Anchor,
22322 cx: &mut App,
22323 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22324 Some(self.update(cx, |project, cx| {
22325 project.document_highlights(buffer, position, cx)
22326 }))
22327 }
22328
22329 fn definitions(
22330 &self,
22331 buffer: &Entity<Buffer>,
22332 position: text::Anchor,
22333 kind: GotoDefinitionKind,
22334 cx: &mut App,
22335 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22336 Some(self.update(cx, |project, cx| match kind {
22337 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22338 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22339 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22340 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22341 }))
22342 }
22343
22344 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22345 self.update(cx, |project, cx| {
22346 if project
22347 .active_debug_session(cx)
22348 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22349 {
22350 return true;
22351 }
22352
22353 buffer.update(cx, |buffer, cx| {
22354 project.any_language_server_supports_inlay_hints(buffer, cx)
22355 })
22356 })
22357 }
22358
22359 fn inline_values(
22360 &self,
22361 buffer_handle: Entity<Buffer>,
22362 range: Range<text::Anchor>,
22363 cx: &mut App,
22364 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22365 self.update(cx, |project, cx| {
22366 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22367
22368 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22369 })
22370 }
22371
22372 fn inlay_hints(
22373 &self,
22374 buffer_handle: Entity<Buffer>,
22375 range: Range<text::Anchor>,
22376 cx: &mut App,
22377 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22378 Some(self.update(cx, |project, cx| {
22379 project.inlay_hints(buffer_handle, range, cx)
22380 }))
22381 }
22382
22383 fn resolve_inlay_hint(
22384 &self,
22385 hint: InlayHint,
22386 buffer_handle: Entity<Buffer>,
22387 server_id: LanguageServerId,
22388 cx: &mut App,
22389 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22390 Some(self.update(cx, |project, cx| {
22391 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22392 }))
22393 }
22394
22395 fn range_for_rename(
22396 &self,
22397 buffer: &Entity<Buffer>,
22398 position: text::Anchor,
22399 cx: &mut App,
22400 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22401 Some(self.update(cx, |project, cx| {
22402 let buffer = buffer.clone();
22403 let task = project.prepare_rename(buffer.clone(), position, cx);
22404 cx.spawn(async move |_, cx| {
22405 Ok(match task.await? {
22406 PrepareRenameResponse::Success(range) => Some(range),
22407 PrepareRenameResponse::InvalidPosition => None,
22408 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22409 // Fallback on using TreeSitter info to determine identifier range
22410 buffer.read_with(cx, |buffer, _| {
22411 let snapshot = buffer.snapshot();
22412 let (range, kind) = snapshot.surrounding_word(position, false);
22413 if kind != Some(CharKind::Word) {
22414 return None;
22415 }
22416 Some(
22417 snapshot.anchor_before(range.start)
22418 ..snapshot.anchor_after(range.end),
22419 )
22420 })?
22421 }
22422 })
22423 })
22424 }))
22425 }
22426
22427 fn perform_rename(
22428 &self,
22429 buffer: &Entity<Buffer>,
22430 position: text::Anchor,
22431 new_name: String,
22432 cx: &mut App,
22433 ) -> Option<Task<Result<ProjectTransaction>>> {
22434 Some(self.update(cx, |project, cx| {
22435 project.perform_rename(buffer.clone(), position, new_name, cx)
22436 }))
22437 }
22438}
22439
22440fn inlay_hint_settings(
22441 location: Anchor,
22442 snapshot: &MultiBufferSnapshot,
22443 cx: &mut Context<Editor>,
22444) -> InlayHintSettings {
22445 let file = snapshot.file_at(location);
22446 let language = snapshot.language_at(location).map(|l| l.name());
22447 language_settings(language, file, cx).inlay_hints
22448}
22449
22450fn consume_contiguous_rows(
22451 contiguous_row_selections: &mut Vec<Selection<Point>>,
22452 selection: &Selection<Point>,
22453 display_map: &DisplaySnapshot,
22454 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22455) -> (MultiBufferRow, MultiBufferRow) {
22456 contiguous_row_selections.push(selection.clone());
22457 let start_row = starting_row(selection, display_map);
22458 let mut end_row = ending_row(selection, display_map);
22459
22460 while let Some(next_selection) = selections.peek() {
22461 if next_selection.start.row <= end_row.0 {
22462 end_row = ending_row(next_selection, display_map);
22463 contiguous_row_selections.push(selections.next().unwrap().clone());
22464 } else {
22465 break;
22466 }
22467 }
22468 (start_row, end_row)
22469}
22470
22471fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22472 if selection.start.column > 0 {
22473 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22474 } else {
22475 MultiBufferRow(selection.start.row)
22476 }
22477}
22478
22479fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22480 if next_selection.end.column > 0 || next_selection.is_empty() {
22481 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22482 } else {
22483 MultiBufferRow(next_selection.end.row)
22484 }
22485}
22486
22487impl EditorSnapshot {
22488 pub fn remote_selections_in_range<'a>(
22489 &'a self,
22490 range: &'a Range<Anchor>,
22491 collaboration_hub: &dyn CollaborationHub,
22492 cx: &'a App,
22493 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22494 let participant_names = collaboration_hub.user_names(cx);
22495 let participant_indices = collaboration_hub.user_participant_indices(cx);
22496 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22497 let collaborators_by_replica_id = collaborators_by_peer_id
22498 .values()
22499 .map(|collaborator| (collaborator.replica_id, collaborator))
22500 .collect::<HashMap<_, _>>();
22501 self.buffer_snapshot
22502 .selections_in_range(range, false)
22503 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22504 if replica_id == AGENT_REPLICA_ID {
22505 Some(RemoteSelection {
22506 replica_id,
22507 selection,
22508 cursor_shape,
22509 line_mode,
22510 collaborator_id: CollaboratorId::Agent,
22511 user_name: Some("Agent".into()),
22512 color: cx.theme().players().agent(),
22513 })
22514 } else {
22515 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22516 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22517 let user_name = participant_names.get(&collaborator.user_id).cloned();
22518 Some(RemoteSelection {
22519 replica_id,
22520 selection,
22521 cursor_shape,
22522 line_mode,
22523 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22524 user_name,
22525 color: if let Some(index) = participant_index {
22526 cx.theme().players().color_for_participant(index.0)
22527 } else {
22528 cx.theme().players().absent()
22529 },
22530 })
22531 }
22532 })
22533 }
22534
22535 pub fn hunks_for_ranges(
22536 &self,
22537 ranges: impl IntoIterator<Item = Range<Point>>,
22538 ) -> Vec<MultiBufferDiffHunk> {
22539 let mut hunks = Vec::new();
22540 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22541 HashMap::default();
22542 for query_range in ranges {
22543 let query_rows =
22544 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22545 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22546 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22547 ) {
22548 // Include deleted hunks that are adjacent to the query range, because
22549 // otherwise they would be missed.
22550 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22551 if hunk.status().is_deleted() {
22552 intersects_range |= hunk.row_range.start == query_rows.end;
22553 intersects_range |= hunk.row_range.end == query_rows.start;
22554 }
22555 if intersects_range {
22556 if !processed_buffer_rows
22557 .entry(hunk.buffer_id)
22558 .or_default()
22559 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22560 {
22561 continue;
22562 }
22563 hunks.push(hunk);
22564 }
22565 }
22566 }
22567
22568 hunks
22569 }
22570
22571 fn display_diff_hunks_for_rows<'a>(
22572 &'a self,
22573 display_rows: Range<DisplayRow>,
22574 folded_buffers: &'a HashSet<BufferId>,
22575 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22576 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22577 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22578
22579 self.buffer_snapshot
22580 .diff_hunks_in_range(buffer_start..buffer_end)
22581 .filter_map(|hunk| {
22582 if folded_buffers.contains(&hunk.buffer_id) {
22583 return None;
22584 }
22585
22586 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22587 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22588
22589 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22590 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22591
22592 let display_hunk = if hunk_display_start.column() != 0 {
22593 DisplayDiffHunk::Folded {
22594 display_row: hunk_display_start.row(),
22595 }
22596 } else {
22597 let mut end_row = hunk_display_end.row();
22598 if hunk_display_end.column() > 0 {
22599 end_row.0 += 1;
22600 }
22601 let is_created_file = hunk.is_created_file();
22602 DisplayDiffHunk::Unfolded {
22603 status: hunk.status(),
22604 diff_base_byte_range: hunk.diff_base_byte_range,
22605 display_row_range: hunk_display_start.row()..end_row,
22606 multi_buffer_range: Anchor::range_in_buffer(
22607 hunk.excerpt_id,
22608 hunk.buffer_id,
22609 hunk.buffer_range,
22610 ),
22611 is_created_file,
22612 }
22613 };
22614
22615 Some(display_hunk)
22616 })
22617 }
22618
22619 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22620 self.display_snapshot.buffer_snapshot.language_at(position)
22621 }
22622
22623 pub fn is_focused(&self) -> bool {
22624 self.is_focused
22625 }
22626
22627 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22628 self.placeholder_text.as_ref()
22629 }
22630
22631 pub fn scroll_position(&self) -> gpui::Point<f32> {
22632 self.scroll_anchor.scroll_position(&self.display_snapshot)
22633 }
22634
22635 fn gutter_dimensions(
22636 &self,
22637 font_id: FontId,
22638 font_size: Pixels,
22639 max_line_number_width: Pixels,
22640 cx: &App,
22641 ) -> Option<GutterDimensions> {
22642 if !self.show_gutter {
22643 return None;
22644 }
22645
22646 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22647 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22648
22649 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22650 matches!(
22651 ProjectSettings::get_global(cx).git.git_gutter,
22652 Some(GitGutterSetting::TrackedFiles)
22653 )
22654 });
22655 let gutter_settings = EditorSettings::get_global(cx).gutter;
22656 let show_line_numbers = self
22657 .show_line_numbers
22658 .unwrap_or(gutter_settings.line_numbers);
22659 let line_gutter_width = if show_line_numbers {
22660 // Avoid flicker-like gutter resizes when the line number gains another digit by
22661 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22662 let min_width_for_number_on_gutter =
22663 ch_advance * gutter_settings.min_line_number_digits as f32;
22664 max_line_number_width.max(min_width_for_number_on_gutter)
22665 } else {
22666 0.0.into()
22667 };
22668
22669 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22670 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22671
22672 let git_blame_entries_width =
22673 self.git_blame_gutter_max_author_length
22674 .map(|max_author_length| {
22675 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22676 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22677
22678 /// The number of characters to dedicate to gaps and margins.
22679 const SPACING_WIDTH: usize = 4;
22680
22681 let max_char_count = max_author_length.min(renderer.max_author_length())
22682 + ::git::SHORT_SHA_LENGTH
22683 + MAX_RELATIVE_TIMESTAMP.len()
22684 + SPACING_WIDTH;
22685
22686 ch_advance * max_char_count
22687 });
22688
22689 let is_singleton = self.buffer_snapshot.is_singleton();
22690
22691 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22692 left_padding += if !is_singleton {
22693 ch_width * 4.0
22694 } else if show_runnables || show_breakpoints {
22695 ch_width * 3.0
22696 } else if show_git_gutter && show_line_numbers {
22697 ch_width * 2.0
22698 } else if show_git_gutter || show_line_numbers {
22699 ch_width
22700 } else {
22701 px(0.)
22702 };
22703
22704 let shows_folds = is_singleton && gutter_settings.folds;
22705
22706 let right_padding = if shows_folds && show_line_numbers {
22707 ch_width * 4.0
22708 } else if shows_folds || (!is_singleton && show_line_numbers) {
22709 ch_width * 3.0
22710 } else if show_line_numbers {
22711 ch_width
22712 } else {
22713 px(0.)
22714 };
22715
22716 Some(GutterDimensions {
22717 left_padding,
22718 right_padding,
22719 width: line_gutter_width + left_padding + right_padding,
22720 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22721 git_blame_entries_width,
22722 })
22723 }
22724
22725 pub fn render_crease_toggle(
22726 &self,
22727 buffer_row: MultiBufferRow,
22728 row_contains_cursor: bool,
22729 editor: Entity<Editor>,
22730 window: &mut Window,
22731 cx: &mut App,
22732 ) -> Option<AnyElement> {
22733 let folded = self.is_line_folded(buffer_row);
22734 let mut is_foldable = false;
22735
22736 if let Some(crease) = self
22737 .crease_snapshot
22738 .query_row(buffer_row, &self.buffer_snapshot)
22739 {
22740 is_foldable = true;
22741 match crease {
22742 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22743 if let Some(render_toggle) = render_toggle {
22744 let toggle_callback =
22745 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22746 if folded {
22747 editor.update(cx, |editor, cx| {
22748 editor.fold_at(buffer_row, window, cx)
22749 });
22750 } else {
22751 editor.update(cx, |editor, cx| {
22752 editor.unfold_at(buffer_row, window, cx)
22753 });
22754 }
22755 });
22756 return Some((render_toggle)(
22757 buffer_row,
22758 folded,
22759 toggle_callback,
22760 window,
22761 cx,
22762 ));
22763 }
22764 }
22765 }
22766 }
22767
22768 is_foldable |= self.starts_indent(buffer_row);
22769
22770 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22771 Some(
22772 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22773 .toggle_state(folded)
22774 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22775 if folded {
22776 this.unfold_at(buffer_row, window, cx);
22777 } else {
22778 this.fold_at(buffer_row, window, cx);
22779 }
22780 }))
22781 .into_any_element(),
22782 )
22783 } else {
22784 None
22785 }
22786 }
22787
22788 pub fn render_crease_trailer(
22789 &self,
22790 buffer_row: MultiBufferRow,
22791 window: &mut Window,
22792 cx: &mut App,
22793 ) -> Option<AnyElement> {
22794 let folded = self.is_line_folded(buffer_row);
22795 if let Crease::Inline { render_trailer, .. } = self
22796 .crease_snapshot
22797 .query_row(buffer_row, &self.buffer_snapshot)?
22798 {
22799 let render_trailer = render_trailer.as_ref()?;
22800 Some(render_trailer(buffer_row, folded, window, cx))
22801 } else {
22802 None
22803 }
22804 }
22805}
22806
22807impl Deref for EditorSnapshot {
22808 type Target = DisplaySnapshot;
22809
22810 fn deref(&self) -> &Self::Target {
22811 &self.display_snapshot
22812 }
22813}
22814
22815#[derive(Clone, Debug, PartialEq, Eq)]
22816pub enum EditorEvent {
22817 InputIgnored {
22818 text: Arc<str>,
22819 },
22820 InputHandled {
22821 utf16_range_to_replace: Option<Range<isize>>,
22822 text: Arc<str>,
22823 },
22824 ExcerptsAdded {
22825 buffer: Entity<Buffer>,
22826 predecessor: ExcerptId,
22827 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22828 },
22829 ExcerptsRemoved {
22830 ids: Vec<ExcerptId>,
22831 removed_buffer_ids: Vec<BufferId>,
22832 },
22833 BufferFoldToggled {
22834 ids: Vec<ExcerptId>,
22835 folded: bool,
22836 },
22837 ExcerptsEdited {
22838 ids: Vec<ExcerptId>,
22839 },
22840 ExcerptsExpanded {
22841 ids: Vec<ExcerptId>,
22842 },
22843 BufferEdited,
22844 Edited {
22845 transaction_id: clock::Lamport,
22846 },
22847 Reparsed(BufferId),
22848 Focused,
22849 FocusedIn,
22850 Blurred,
22851 DirtyChanged,
22852 Saved,
22853 TitleChanged,
22854 DiffBaseChanged,
22855 SelectionsChanged {
22856 local: bool,
22857 },
22858 ScrollPositionChanged {
22859 local: bool,
22860 autoscroll: bool,
22861 },
22862 Closed,
22863 TransactionUndone {
22864 transaction_id: clock::Lamport,
22865 },
22866 TransactionBegun {
22867 transaction_id: clock::Lamport,
22868 },
22869 Reloaded,
22870 CursorShapeChanged,
22871 BreadcrumbsChanged,
22872 PushedToNavHistory {
22873 anchor: Anchor,
22874 is_deactivate: bool,
22875 },
22876}
22877
22878impl EventEmitter<EditorEvent> for Editor {}
22879
22880impl Focusable for Editor {
22881 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22882 self.focus_handle.clone()
22883 }
22884}
22885
22886impl Render for Editor {
22887 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22888 let settings = ThemeSettings::get_global(cx);
22889
22890 let mut text_style = match self.mode {
22891 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
22892 color: cx.theme().colors().editor_foreground,
22893 font_family: settings.ui_font.family.clone(),
22894 font_features: settings.ui_font.features.clone(),
22895 font_fallbacks: settings.ui_font.fallbacks.clone(),
22896 font_size: rems(0.875).into(),
22897 font_weight: settings.ui_font.weight,
22898 line_height: relative(settings.buffer_line_height.value()),
22899 ..Default::default()
22900 },
22901 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22902 color: cx.theme().colors().editor_foreground,
22903 font_family: settings.buffer_font.family.clone(),
22904 font_features: settings.buffer_font.features.clone(),
22905 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22906 font_size: settings.buffer_font_size(cx).into(),
22907 font_weight: settings.buffer_font.weight,
22908 line_height: relative(settings.buffer_line_height.value()),
22909 ..Default::default()
22910 },
22911 };
22912 if let Some(text_style_refinement) = &self.text_style_refinement {
22913 text_style.refine(text_style_refinement)
22914 }
22915
22916 let background = match self.mode {
22917 EditorMode::SingleLine => cx.theme().system().transparent,
22918 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22919 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22920 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22921 };
22922
22923 EditorElement::new(
22924 &cx.entity(),
22925 EditorStyle {
22926 background,
22927 border: cx.theme().colors().border,
22928 local_player: cx.theme().players().local(),
22929 text: text_style,
22930 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22931 syntax: cx.theme().syntax().clone(),
22932 status: cx.theme().status().clone(),
22933 inlay_hints_style: make_inlay_hints_style(cx),
22934 edit_prediction_styles: make_suggestion_styles(cx),
22935 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22936 show_underlines: self.diagnostics_enabled(),
22937 },
22938 )
22939 }
22940}
22941
22942impl EntityInputHandler for Editor {
22943 fn text_for_range(
22944 &mut self,
22945 range_utf16: Range<usize>,
22946 adjusted_range: &mut Option<Range<usize>>,
22947 _: &mut Window,
22948 cx: &mut Context<Self>,
22949 ) -> Option<String> {
22950 let snapshot = self.buffer.read(cx).read(cx);
22951 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22952 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22953 if (start.0..end.0) != range_utf16 {
22954 adjusted_range.replace(start.0..end.0);
22955 }
22956 Some(snapshot.text_for_range(start..end).collect())
22957 }
22958
22959 fn selected_text_range(
22960 &mut self,
22961 ignore_disabled_input: bool,
22962 _: &mut Window,
22963 cx: &mut Context<Self>,
22964 ) -> Option<UTF16Selection> {
22965 // Prevent the IME menu from appearing when holding down an alphabetic key
22966 // while input is disabled.
22967 if !ignore_disabled_input && !self.input_enabled {
22968 return None;
22969 }
22970
22971 let selection = self.selections.newest::<OffsetUtf16>(cx);
22972 let range = selection.range();
22973
22974 Some(UTF16Selection {
22975 range: range.start.0..range.end.0,
22976 reversed: selection.reversed,
22977 })
22978 }
22979
22980 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22981 let snapshot = self.buffer.read(cx).read(cx);
22982 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22983 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22984 }
22985
22986 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22987 self.clear_highlights::<InputComposition>(cx);
22988 self.ime_transaction.take();
22989 }
22990
22991 fn replace_text_in_range(
22992 &mut self,
22993 range_utf16: Option<Range<usize>>,
22994 text: &str,
22995 window: &mut Window,
22996 cx: &mut Context<Self>,
22997 ) {
22998 if !self.input_enabled {
22999 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23000 return;
23001 }
23002
23003 self.transact(window, cx, |this, window, cx| {
23004 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23005 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23006 Some(this.selection_replacement_ranges(range_utf16, cx))
23007 } else {
23008 this.marked_text_ranges(cx)
23009 };
23010
23011 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23012 let newest_selection_id = this.selections.newest_anchor().id;
23013 this.selections
23014 .all::<OffsetUtf16>(cx)
23015 .iter()
23016 .zip(ranges_to_replace.iter())
23017 .find_map(|(selection, range)| {
23018 if selection.id == newest_selection_id {
23019 Some(
23020 (range.start.0 as isize - selection.head().0 as isize)
23021 ..(range.end.0 as isize - selection.head().0 as isize),
23022 )
23023 } else {
23024 None
23025 }
23026 })
23027 });
23028
23029 cx.emit(EditorEvent::InputHandled {
23030 utf16_range_to_replace: range_to_replace,
23031 text: text.into(),
23032 });
23033
23034 if let Some(new_selected_ranges) = new_selected_ranges {
23035 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23036 selections.select_ranges(new_selected_ranges)
23037 });
23038 this.backspace(&Default::default(), window, cx);
23039 }
23040
23041 this.handle_input(text, window, cx);
23042 });
23043
23044 if let Some(transaction) = self.ime_transaction {
23045 self.buffer.update(cx, |buffer, cx| {
23046 buffer.group_until_transaction(transaction, cx);
23047 });
23048 }
23049
23050 self.unmark_text(window, cx);
23051 }
23052
23053 fn replace_and_mark_text_in_range(
23054 &mut self,
23055 range_utf16: Option<Range<usize>>,
23056 text: &str,
23057 new_selected_range_utf16: Option<Range<usize>>,
23058 window: &mut Window,
23059 cx: &mut Context<Self>,
23060 ) {
23061 if !self.input_enabled {
23062 return;
23063 }
23064
23065 let transaction = self.transact(window, cx, |this, window, cx| {
23066 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23067 let snapshot = this.buffer.read(cx).read(cx);
23068 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23069 for marked_range in &mut marked_ranges {
23070 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23071 marked_range.start.0 += relative_range_utf16.start;
23072 marked_range.start =
23073 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23074 marked_range.end =
23075 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23076 }
23077 }
23078 Some(marked_ranges)
23079 } else if let Some(range_utf16) = range_utf16 {
23080 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23081 Some(this.selection_replacement_ranges(range_utf16, cx))
23082 } else {
23083 None
23084 };
23085
23086 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23087 let newest_selection_id = this.selections.newest_anchor().id;
23088 this.selections
23089 .all::<OffsetUtf16>(cx)
23090 .iter()
23091 .zip(ranges_to_replace.iter())
23092 .find_map(|(selection, range)| {
23093 if selection.id == newest_selection_id {
23094 Some(
23095 (range.start.0 as isize - selection.head().0 as isize)
23096 ..(range.end.0 as isize - selection.head().0 as isize),
23097 )
23098 } else {
23099 None
23100 }
23101 })
23102 });
23103
23104 cx.emit(EditorEvent::InputHandled {
23105 utf16_range_to_replace: range_to_replace,
23106 text: text.into(),
23107 });
23108
23109 if let Some(ranges) = ranges_to_replace {
23110 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23111 s.select_ranges(ranges)
23112 });
23113 }
23114
23115 let marked_ranges = {
23116 let snapshot = this.buffer.read(cx).read(cx);
23117 this.selections
23118 .disjoint_anchors()
23119 .iter()
23120 .map(|selection| {
23121 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23122 })
23123 .collect::<Vec<_>>()
23124 };
23125
23126 if text.is_empty() {
23127 this.unmark_text(window, cx);
23128 } else {
23129 this.highlight_text::<InputComposition>(
23130 marked_ranges.clone(),
23131 HighlightStyle {
23132 underline: Some(UnderlineStyle {
23133 thickness: px(1.),
23134 color: None,
23135 wavy: false,
23136 }),
23137 ..Default::default()
23138 },
23139 cx,
23140 );
23141 }
23142
23143 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23144 let use_autoclose = this.use_autoclose;
23145 let use_auto_surround = this.use_auto_surround;
23146 this.set_use_autoclose(false);
23147 this.set_use_auto_surround(false);
23148 this.handle_input(text, window, cx);
23149 this.set_use_autoclose(use_autoclose);
23150 this.set_use_auto_surround(use_auto_surround);
23151
23152 if let Some(new_selected_range) = new_selected_range_utf16 {
23153 let snapshot = this.buffer.read(cx).read(cx);
23154 let new_selected_ranges = marked_ranges
23155 .into_iter()
23156 .map(|marked_range| {
23157 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23158 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23159 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23160 snapshot.clip_offset_utf16(new_start, Bias::Left)
23161 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23162 })
23163 .collect::<Vec<_>>();
23164
23165 drop(snapshot);
23166 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23167 selections.select_ranges(new_selected_ranges)
23168 });
23169 }
23170 });
23171
23172 self.ime_transaction = self.ime_transaction.or(transaction);
23173 if let Some(transaction) = self.ime_transaction {
23174 self.buffer.update(cx, |buffer, cx| {
23175 buffer.group_until_transaction(transaction, cx);
23176 });
23177 }
23178
23179 if self.text_highlights::<InputComposition>(cx).is_none() {
23180 self.ime_transaction.take();
23181 }
23182 }
23183
23184 fn bounds_for_range(
23185 &mut self,
23186 range_utf16: Range<usize>,
23187 element_bounds: gpui::Bounds<Pixels>,
23188 window: &mut Window,
23189 cx: &mut Context<Self>,
23190 ) -> Option<gpui::Bounds<Pixels>> {
23191 let text_layout_details = self.text_layout_details(window);
23192 let CharacterDimensions {
23193 em_width,
23194 em_advance,
23195 line_height,
23196 } = self.character_dimensions(window);
23197
23198 let snapshot = self.snapshot(window, cx);
23199 let scroll_position = snapshot.scroll_position();
23200 let scroll_left = scroll_position.x * em_advance;
23201
23202 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23203 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23204 + self.gutter_dimensions.full_width();
23205 let y = line_height * (start.row().as_f32() - scroll_position.y);
23206
23207 Some(Bounds {
23208 origin: element_bounds.origin + point(x, y),
23209 size: size(em_width, line_height),
23210 })
23211 }
23212
23213 fn character_index_for_point(
23214 &mut self,
23215 point: gpui::Point<Pixels>,
23216 _window: &mut Window,
23217 _cx: &mut Context<Self>,
23218 ) -> Option<usize> {
23219 let position_map = self.last_position_map.as_ref()?;
23220 if !position_map.text_hitbox.contains(&point) {
23221 return None;
23222 }
23223 let display_point = position_map.point_for_position(point).previous_valid;
23224 let anchor = position_map
23225 .snapshot
23226 .display_point_to_anchor(display_point, Bias::Left);
23227 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23228 Some(utf16_offset.0)
23229 }
23230}
23231
23232trait SelectionExt {
23233 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23234 fn spanned_rows(
23235 &self,
23236 include_end_if_at_line_start: bool,
23237 map: &DisplaySnapshot,
23238 ) -> Range<MultiBufferRow>;
23239}
23240
23241impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23242 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23243 let start = self
23244 .start
23245 .to_point(&map.buffer_snapshot)
23246 .to_display_point(map);
23247 let end = self
23248 .end
23249 .to_point(&map.buffer_snapshot)
23250 .to_display_point(map);
23251 if self.reversed {
23252 end..start
23253 } else {
23254 start..end
23255 }
23256 }
23257
23258 fn spanned_rows(
23259 &self,
23260 include_end_if_at_line_start: bool,
23261 map: &DisplaySnapshot,
23262 ) -> Range<MultiBufferRow> {
23263 let start = self.start.to_point(&map.buffer_snapshot);
23264 let mut end = self.end.to_point(&map.buffer_snapshot);
23265 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23266 end.row -= 1;
23267 }
23268
23269 let buffer_start = map.prev_line_boundary(start).0;
23270 let buffer_end = map.next_line_boundary(end).0;
23271 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23272 }
23273}
23274
23275impl<T: InvalidationRegion> InvalidationStack<T> {
23276 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23277 where
23278 S: Clone + ToOffset,
23279 {
23280 while let Some(region) = self.last() {
23281 let all_selections_inside_invalidation_ranges =
23282 if selections.len() == region.ranges().len() {
23283 selections
23284 .iter()
23285 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23286 .all(|(selection, invalidation_range)| {
23287 let head = selection.head().to_offset(buffer);
23288 invalidation_range.start <= head && invalidation_range.end >= head
23289 })
23290 } else {
23291 false
23292 };
23293
23294 if all_selections_inside_invalidation_ranges {
23295 break;
23296 } else {
23297 self.pop();
23298 }
23299 }
23300 }
23301}
23302
23303impl<T> Default for InvalidationStack<T> {
23304 fn default() -> Self {
23305 Self(Default::default())
23306 }
23307}
23308
23309impl<T> Deref for InvalidationStack<T> {
23310 type Target = Vec<T>;
23311
23312 fn deref(&self) -> &Self::Target {
23313 &self.0
23314 }
23315}
23316
23317impl<T> DerefMut for InvalidationStack<T> {
23318 fn deref_mut(&mut self) -> &mut Self::Target {
23319 &mut self.0
23320 }
23321}
23322
23323impl InvalidationRegion for SnippetState {
23324 fn ranges(&self) -> &[Range<Anchor>] {
23325 &self.ranges[self.active_index]
23326 }
23327}
23328
23329fn edit_prediction_edit_text(
23330 current_snapshot: &BufferSnapshot,
23331 edits: &[(Range<Anchor>, String)],
23332 edit_preview: &EditPreview,
23333 include_deletions: bool,
23334 cx: &App,
23335) -> HighlightedText {
23336 let edits = edits
23337 .iter()
23338 .map(|(anchor, text)| {
23339 (
23340 anchor.start.text_anchor..anchor.end.text_anchor,
23341 text.clone(),
23342 )
23343 })
23344 .collect::<Vec<_>>();
23345
23346 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23347}
23348
23349fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23350 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23351 // Just show the raw edit text with basic styling
23352 let mut text = String::new();
23353 let mut highlights = Vec::new();
23354
23355 let insertion_highlight_style = HighlightStyle {
23356 color: Some(cx.theme().colors().text),
23357 ..Default::default()
23358 };
23359
23360 for (_, edit_text) in edits {
23361 let start_offset = text.len();
23362 text.push_str(edit_text);
23363 let end_offset = text.len();
23364
23365 if start_offset < end_offset {
23366 highlights.push((start_offset..end_offset, insertion_highlight_style));
23367 }
23368 }
23369
23370 HighlightedText {
23371 text: text.into(),
23372 highlights,
23373 }
23374}
23375
23376pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23377 match severity {
23378 lsp::DiagnosticSeverity::ERROR => colors.error,
23379 lsp::DiagnosticSeverity::WARNING => colors.warning,
23380 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23381 lsp::DiagnosticSeverity::HINT => colors.info,
23382 _ => colors.ignored,
23383 }
23384}
23385
23386pub fn styled_runs_for_code_label<'a>(
23387 label: &'a CodeLabel,
23388 syntax_theme: &'a theme::SyntaxTheme,
23389) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23390 let fade_out = HighlightStyle {
23391 fade_out: Some(0.35),
23392 ..Default::default()
23393 };
23394
23395 let mut prev_end = label.filter_range.end;
23396 label
23397 .runs
23398 .iter()
23399 .enumerate()
23400 .flat_map(move |(ix, (range, highlight_id))| {
23401 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23402 style
23403 } else {
23404 return Default::default();
23405 };
23406 let mut muted_style = style;
23407 muted_style.highlight(fade_out);
23408
23409 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23410 if range.start >= label.filter_range.end {
23411 if range.start > prev_end {
23412 runs.push((prev_end..range.start, fade_out));
23413 }
23414 runs.push((range.clone(), muted_style));
23415 } else if range.end <= label.filter_range.end {
23416 runs.push((range.clone(), style));
23417 } else {
23418 runs.push((range.start..label.filter_range.end, style));
23419 runs.push((label.filter_range.end..range.end, muted_style));
23420 }
23421 prev_end = cmp::max(prev_end, range.end);
23422
23423 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23424 runs.push((prev_end..label.text.len(), fade_out));
23425 }
23426
23427 runs
23428 })
23429}
23430
23431pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23432 let mut prev_index = 0;
23433 let mut prev_codepoint: Option<char> = None;
23434 text.char_indices()
23435 .chain([(text.len(), '\0')])
23436 .filter_map(move |(index, codepoint)| {
23437 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23438 let is_boundary = index == text.len()
23439 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23440 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23441 if is_boundary {
23442 let chunk = &text[prev_index..index];
23443 prev_index = index;
23444 Some(chunk)
23445 } else {
23446 None
23447 }
23448 })
23449}
23450
23451pub trait RangeToAnchorExt: Sized {
23452 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23453
23454 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23455 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23456 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23457 }
23458}
23459
23460impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23461 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23462 let start_offset = self.start.to_offset(snapshot);
23463 let end_offset = self.end.to_offset(snapshot);
23464 if start_offset == end_offset {
23465 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23466 } else {
23467 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23468 }
23469 }
23470}
23471
23472pub trait RowExt {
23473 fn as_f32(&self) -> f32;
23474
23475 fn next_row(&self) -> Self;
23476
23477 fn previous_row(&self) -> Self;
23478
23479 fn minus(&self, other: Self) -> u32;
23480}
23481
23482impl RowExt for DisplayRow {
23483 fn as_f32(&self) -> f32 {
23484 self.0 as f32
23485 }
23486
23487 fn next_row(&self) -> Self {
23488 Self(self.0 + 1)
23489 }
23490
23491 fn previous_row(&self) -> Self {
23492 Self(self.0.saturating_sub(1))
23493 }
23494
23495 fn minus(&self, other: Self) -> u32 {
23496 self.0 - other.0
23497 }
23498}
23499
23500impl RowExt for MultiBufferRow {
23501 fn as_f32(&self) -> f32 {
23502 self.0 as f32
23503 }
23504
23505 fn next_row(&self) -> Self {
23506 Self(self.0 + 1)
23507 }
23508
23509 fn previous_row(&self) -> Self {
23510 Self(self.0.saturating_sub(1))
23511 }
23512
23513 fn minus(&self, other: Self) -> u32 {
23514 self.0 - other.0
23515 }
23516}
23517
23518trait RowRangeExt {
23519 type Row;
23520
23521 fn len(&self) -> usize;
23522
23523 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23524}
23525
23526impl RowRangeExt for Range<MultiBufferRow> {
23527 type Row = MultiBufferRow;
23528
23529 fn len(&self) -> usize {
23530 (self.end.0 - self.start.0) as usize
23531 }
23532
23533 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23534 (self.start.0..self.end.0).map(MultiBufferRow)
23535 }
23536}
23537
23538impl RowRangeExt for Range<DisplayRow> {
23539 type Row = DisplayRow;
23540
23541 fn len(&self) -> usize {
23542 (self.end.0 - self.start.0) as usize
23543 }
23544
23545 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23546 (self.start.0..self.end.0).map(DisplayRow)
23547 }
23548}
23549
23550/// If select range has more than one line, we
23551/// just point the cursor to range.start.
23552fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23553 if range.start.row == range.end.row {
23554 range
23555 } else {
23556 range.start..range.start
23557 }
23558}
23559pub struct KillRing(ClipboardItem);
23560impl Global for KillRing {}
23561
23562const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23563
23564enum BreakpointPromptEditAction {
23565 Log,
23566 Condition,
23567 HitCondition,
23568}
23569
23570struct BreakpointPromptEditor {
23571 pub(crate) prompt: Entity<Editor>,
23572 editor: WeakEntity<Editor>,
23573 breakpoint_anchor: Anchor,
23574 breakpoint: Breakpoint,
23575 edit_action: BreakpointPromptEditAction,
23576 block_ids: HashSet<CustomBlockId>,
23577 editor_margins: Arc<Mutex<EditorMargins>>,
23578 _subscriptions: Vec<Subscription>,
23579}
23580
23581impl BreakpointPromptEditor {
23582 const MAX_LINES: u8 = 4;
23583
23584 fn new(
23585 editor: WeakEntity<Editor>,
23586 breakpoint_anchor: Anchor,
23587 breakpoint: Breakpoint,
23588 edit_action: BreakpointPromptEditAction,
23589 window: &mut Window,
23590 cx: &mut Context<Self>,
23591 ) -> Self {
23592 let base_text = match edit_action {
23593 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23594 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23595 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23596 }
23597 .map(|msg| msg.to_string())
23598 .unwrap_or_default();
23599
23600 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23601 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23602
23603 let prompt = cx.new(|cx| {
23604 let mut prompt = Editor::new(
23605 EditorMode::AutoHeight {
23606 min_lines: 1,
23607 max_lines: Some(Self::MAX_LINES as usize),
23608 },
23609 buffer,
23610 None,
23611 window,
23612 cx,
23613 );
23614 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23615 prompt.set_show_cursor_when_unfocused(false, cx);
23616 prompt.set_placeholder_text(
23617 match edit_action {
23618 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23619 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23620 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23621 },
23622 cx,
23623 );
23624
23625 prompt
23626 });
23627
23628 Self {
23629 prompt,
23630 editor,
23631 breakpoint_anchor,
23632 breakpoint,
23633 edit_action,
23634 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23635 block_ids: Default::default(),
23636 _subscriptions: vec![],
23637 }
23638 }
23639
23640 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23641 self.block_ids.extend(block_ids)
23642 }
23643
23644 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23645 if let Some(editor) = self.editor.upgrade() {
23646 let message = self
23647 .prompt
23648 .read(cx)
23649 .buffer
23650 .read(cx)
23651 .as_singleton()
23652 .expect("A multi buffer in breakpoint prompt isn't possible")
23653 .read(cx)
23654 .as_rope()
23655 .to_string();
23656
23657 editor.update(cx, |editor, cx| {
23658 editor.edit_breakpoint_at_anchor(
23659 self.breakpoint_anchor,
23660 self.breakpoint.clone(),
23661 match self.edit_action {
23662 BreakpointPromptEditAction::Log => {
23663 BreakpointEditAction::EditLogMessage(message.into())
23664 }
23665 BreakpointPromptEditAction::Condition => {
23666 BreakpointEditAction::EditCondition(message.into())
23667 }
23668 BreakpointPromptEditAction::HitCondition => {
23669 BreakpointEditAction::EditHitCondition(message.into())
23670 }
23671 },
23672 cx,
23673 );
23674
23675 editor.remove_blocks(self.block_ids.clone(), None, cx);
23676 cx.focus_self(window);
23677 });
23678 }
23679 }
23680
23681 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23682 self.editor
23683 .update(cx, |editor, cx| {
23684 editor.remove_blocks(self.block_ids.clone(), None, cx);
23685 window.focus(&editor.focus_handle);
23686 })
23687 .log_err();
23688 }
23689
23690 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23691 let settings = ThemeSettings::get_global(cx);
23692 let text_style = TextStyle {
23693 color: if self.prompt.read(cx).read_only(cx) {
23694 cx.theme().colors().text_disabled
23695 } else {
23696 cx.theme().colors().text
23697 },
23698 font_family: settings.buffer_font.family.clone(),
23699 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23700 font_size: settings.buffer_font_size(cx).into(),
23701 font_weight: settings.buffer_font.weight,
23702 line_height: relative(settings.buffer_line_height.value()),
23703 ..Default::default()
23704 };
23705 EditorElement::new(
23706 &self.prompt,
23707 EditorStyle {
23708 background: cx.theme().colors().editor_background,
23709 local_player: cx.theme().players().local(),
23710 text: text_style,
23711 ..Default::default()
23712 },
23713 )
23714 }
23715}
23716
23717impl Render for BreakpointPromptEditor {
23718 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23719 let editor_margins = *self.editor_margins.lock();
23720 let gutter_dimensions = editor_margins.gutter;
23721 h_flex()
23722 .key_context("Editor")
23723 .bg(cx.theme().colors().editor_background)
23724 .border_y_1()
23725 .border_color(cx.theme().status().info_border)
23726 .size_full()
23727 .py(window.line_height() / 2.5)
23728 .on_action(cx.listener(Self::confirm))
23729 .on_action(cx.listener(Self::cancel))
23730 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23731 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23732 }
23733}
23734
23735impl Focusable for BreakpointPromptEditor {
23736 fn focus_handle(&self, cx: &App) -> FocusHandle {
23737 self.prompt.focus_handle(cx)
23738 }
23739}
23740
23741fn all_edits_insertions_or_deletions(
23742 edits: &Vec<(Range<Anchor>, String)>,
23743 snapshot: &MultiBufferSnapshot,
23744) -> bool {
23745 let mut all_insertions = true;
23746 let mut all_deletions = true;
23747
23748 for (range, new_text) in edits.iter() {
23749 let range_is_empty = range.to_offset(snapshot).is_empty();
23750 let text_is_empty = new_text.is_empty();
23751
23752 if range_is_empty != text_is_empty {
23753 if range_is_empty {
23754 all_deletions = false;
23755 } else {
23756 all_insertions = false;
23757 }
23758 } else {
23759 return false;
23760 }
23761
23762 if !all_insertions && !all_deletions {
23763 return false;
23764 }
23765 }
23766 all_insertions || all_deletions
23767}
23768
23769struct MissingEditPredictionKeybindingTooltip;
23770
23771impl Render for MissingEditPredictionKeybindingTooltip {
23772 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23773 ui::tooltip_container(window, cx, |container, _, cx| {
23774 container
23775 .flex_shrink_0()
23776 .max_w_80()
23777 .min_h(rems_from_px(124.))
23778 .justify_between()
23779 .child(
23780 v_flex()
23781 .flex_1()
23782 .text_ui_sm(cx)
23783 .child(Label::new("Conflict with Accept Keybinding"))
23784 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23785 )
23786 .child(
23787 h_flex()
23788 .pb_1()
23789 .gap_1()
23790 .items_end()
23791 .w_full()
23792 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23793 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23794 }))
23795 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23796 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23797 })),
23798 )
23799 })
23800 }
23801}
23802
23803#[derive(Debug, Clone, Copy, PartialEq)]
23804pub struct LineHighlight {
23805 pub background: Background,
23806 pub border: Option<gpui::Hsla>,
23807 pub include_gutter: bool,
23808 pub type_id: Option<TypeId>,
23809}
23810
23811struct LineManipulationResult {
23812 pub new_text: String,
23813 pub line_count_before: usize,
23814 pub line_count_after: usize,
23815}
23816
23817fn render_diff_hunk_controls(
23818 row: u32,
23819 status: &DiffHunkStatus,
23820 hunk_range: Range<Anchor>,
23821 is_created_file: bool,
23822 line_height: Pixels,
23823 editor: &Entity<Editor>,
23824 _window: &mut Window,
23825 cx: &mut App,
23826) -> AnyElement {
23827 h_flex()
23828 .h(line_height)
23829 .mr_1()
23830 .gap_1()
23831 .px_0p5()
23832 .pb_1()
23833 .border_x_1()
23834 .border_b_1()
23835 .border_color(cx.theme().colors().border_variant)
23836 .rounded_b_lg()
23837 .bg(cx.theme().colors().editor_background)
23838 .gap_1()
23839 .block_mouse_except_scroll()
23840 .shadow_md()
23841 .child(if status.has_secondary_hunk() {
23842 Button::new(("stage", row as u64), "Stage")
23843 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23844 .tooltip({
23845 let focus_handle = editor.focus_handle(cx);
23846 move |window, cx| {
23847 Tooltip::for_action_in(
23848 "Stage Hunk",
23849 &::git::ToggleStaged,
23850 &focus_handle,
23851 window,
23852 cx,
23853 )
23854 }
23855 })
23856 .on_click({
23857 let editor = editor.clone();
23858 move |_event, _window, cx| {
23859 editor.update(cx, |editor, cx| {
23860 editor.stage_or_unstage_diff_hunks(
23861 true,
23862 vec![hunk_range.start..hunk_range.start],
23863 cx,
23864 );
23865 });
23866 }
23867 })
23868 } else {
23869 Button::new(("unstage", row as u64), "Unstage")
23870 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23871 .tooltip({
23872 let focus_handle = editor.focus_handle(cx);
23873 move |window, cx| {
23874 Tooltip::for_action_in(
23875 "Unstage Hunk",
23876 &::git::ToggleStaged,
23877 &focus_handle,
23878 window,
23879 cx,
23880 )
23881 }
23882 })
23883 .on_click({
23884 let editor = editor.clone();
23885 move |_event, _window, cx| {
23886 editor.update(cx, |editor, cx| {
23887 editor.stage_or_unstage_diff_hunks(
23888 false,
23889 vec![hunk_range.start..hunk_range.start],
23890 cx,
23891 );
23892 });
23893 }
23894 })
23895 })
23896 .child(
23897 Button::new(("restore", row as u64), "Restore")
23898 .tooltip({
23899 let focus_handle = editor.focus_handle(cx);
23900 move |window, cx| {
23901 Tooltip::for_action_in(
23902 "Restore Hunk",
23903 &::git::Restore,
23904 &focus_handle,
23905 window,
23906 cx,
23907 )
23908 }
23909 })
23910 .on_click({
23911 let editor = editor.clone();
23912 move |_event, window, cx| {
23913 editor.update(cx, |editor, cx| {
23914 let snapshot = editor.snapshot(window, cx);
23915 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23916 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23917 });
23918 }
23919 })
23920 .disabled(is_created_file),
23921 )
23922 .when(
23923 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23924 |el| {
23925 el.child(
23926 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23927 .shape(IconButtonShape::Square)
23928 .icon_size(IconSize::Small)
23929 // .disabled(!has_multiple_hunks)
23930 .tooltip({
23931 let focus_handle = editor.focus_handle(cx);
23932 move |window, cx| {
23933 Tooltip::for_action_in(
23934 "Next Hunk",
23935 &GoToHunk,
23936 &focus_handle,
23937 window,
23938 cx,
23939 )
23940 }
23941 })
23942 .on_click({
23943 let editor = editor.clone();
23944 move |_event, window, cx| {
23945 editor.update(cx, |editor, cx| {
23946 let snapshot = editor.snapshot(window, cx);
23947 let position =
23948 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23949 editor.go_to_hunk_before_or_after_position(
23950 &snapshot,
23951 position,
23952 Direction::Next,
23953 window,
23954 cx,
23955 );
23956 editor.expand_selected_diff_hunks(cx);
23957 });
23958 }
23959 }),
23960 )
23961 .child(
23962 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23963 .shape(IconButtonShape::Square)
23964 .icon_size(IconSize::Small)
23965 // .disabled(!has_multiple_hunks)
23966 .tooltip({
23967 let focus_handle = editor.focus_handle(cx);
23968 move |window, cx| {
23969 Tooltip::for_action_in(
23970 "Previous Hunk",
23971 &GoToPreviousHunk,
23972 &focus_handle,
23973 window,
23974 cx,
23975 )
23976 }
23977 })
23978 .on_click({
23979 let editor = editor.clone();
23980 move |_event, window, cx| {
23981 editor.update(cx, |editor, cx| {
23982 let snapshot = editor.snapshot(window, cx);
23983 let point =
23984 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23985 editor.go_to_hunk_before_or_after_position(
23986 &snapshot,
23987 point,
23988 Direction::Prev,
23989 window,
23990 cx,
23991 );
23992 editor.expand_selected_diff_hunks(cx);
23993 });
23994 }
23995 }),
23996 )
23997 },
23998 )
23999 .into_any_element()
24000}