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 },
1903 ));
1904 if let Some(task_inventory) = project
1905 .read(cx)
1906 .task_store()
1907 .read(cx)
1908 .task_inventory()
1909 .cloned()
1910 {
1911 project_subscriptions.push(cx.observe_in(
1912 &task_inventory,
1913 window,
1914 |editor, _, window, cx| {
1915 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1916 },
1917 ));
1918 };
1919
1920 project_subscriptions.push(cx.subscribe_in(
1921 &project.read(cx).breakpoint_store(),
1922 window,
1923 |editor, _, event, window, cx| match event {
1924 BreakpointStoreEvent::ClearDebugLines => {
1925 editor.clear_row_highlights::<ActiveDebugLine>();
1926 editor.refresh_inline_values(cx);
1927 }
1928 BreakpointStoreEvent::SetDebugLine => {
1929 if editor.go_to_active_debug_line(window, cx) {
1930 cx.stop_propagation();
1931 }
1932
1933 editor.refresh_inline_values(cx);
1934 }
1935 _ => {}
1936 },
1937 ));
1938 let git_store = project.read(cx).git_store().clone();
1939 let project = project.clone();
1940 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1941 if let GitStoreEvent::RepositoryUpdated(
1942 _,
1943 RepositoryEvent::Updated {
1944 new_instance: true, ..
1945 },
1946 _,
1947 ) = event
1948 {
1949 this.load_diff_task = Some(
1950 update_uncommitted_diff_for_buffer(
1951 cx.entity(),
1952 &project,
1953 this.buffer.read(cx).all_buffers(),
1954 this.buffer.clone(),
1955 cx,
1956 )
1957 .shared(),
1958 );
1959 }
1960 }));
1961 }
1962
1963 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1964
1965 let inlay_hint_settings =
1966 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1967 let focus_handle = cx.focus_handle();
1968 if !is_minimap {
1969 cx.on_focus(&focus_handle, window, Self::handle_focus)
1970 .detach();
1971 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1972 .detach();
1973 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1974 .detach();
1975 cx.on_blur(&focus_handle, window, Self::handle_blur)
1976 .detach();
1977 cx.observe_pending_input(window, Self::observe_pending_input)
1978 .detach();
1979 }
1980
1981 let show_indent_guides =
1982 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
1983 Some(false)
1984 } else {
1985 None
1986 };
1987
1988 let breakpoint_store = match (&mode, project.as_ref()) {
1989 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1990 _ => None,
1991 };
1992
1993 let mut code_action_providers = Vec::new();
1994 let mut load_uncommitted_diff = None;
1995 if let Some(project) = project.clone() {
1996 load_uncommitted_diff = Some(
1997 update_uncommitted_diff_for_buffer(
1998 cx.entity(),
1999 &project,
2000 buffer.read(cx).all_buffers(),
2001 buffer.clone(),
2002 cx,
2003 )
2004 .shared(),
2005 );
2006 code_action_providers.push(Rc::new(project) as Rc<_>);
2007 }
2008
2009 let mut editor = Self {
2010 focus_handle,
2011 show_cursor_when_unfocused: false,
2012 last_focused_descendant: None,
2013 buffer: buffer.clone(),
2014 display_map: display_map.clone(),
2015 selections,
2016 scroll_manager: ScrollManager::new(cx),
2017 columnar_selection_state: None,
2018 add_selections_state: None,
2019 select_next_state: None,
2020 select_prev_state: None,
2021 selection_history: SelectionHistory::default(),
2022 defer_selection_effects: false,
2023 deferred_selection_effects_state: None,
2024 autoclose_regions: Vec::new(),
2025 snippet_stack: InvalidationStack::default(),
2026 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2027 ime_transaction: None,
2028 active_diagnostics: ActiveDiagnostic::None,
2029 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2030 inline_diagnostics_update: Task::ready(()),
2031 inline_diagnostics: Vec::new(),
2032 soft_wrap_mode_override,
2033 diagnostics_max_severity,
2034 hard_wrap: None,
2035 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2036 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2037 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2038 project,
2039 blink_manager: blink_manager.clone(),
2040 show_local_selections: true,
2041 show_scrollbars: ScrollbarAxes {
2042 horizontal: full_mode,
2043 vertical: full_mode,
2044 },
2045 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2046 offset_content: !matches!(mode, EditorMode::SingleLine),
2047 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2048 show_gutter: full_mode,
2049 show_line_numbers: (!full_mode).then_some(false),
2050 use_relative_line_numbers: None,
2051 disable_expand_excerpt_buttons: !full_mode,
2052 show_git_diff_gutter: None,
2053 show_code_actions: None,
2054 show_runnables: None,
2055 show_breakpoints: None,
2056 show_wrap_guides: None,
2057 show_indent_guides,
2058 placeholder_text: None,
2059 highlight_order: 0,
2060 highlighted_rows: HashMap::default(),
2061 background_highlights: TreeMap::default(),
2062 gutter_highlights: TreeMap::default(),
2063 scrollbar_marker_state: ScrollbarMarkerState::default(),
2064 active_indent_guides_state: ActiveIndentGuidesState::default(),
2065 nav_history: None,
2066 context_menu: RefCell::new(None),
2067 context_menu_options: None,
2068 mouse_context_menu: None,
2069 completion_tasks: Vec::new(),
2070 inline_blame_popover: None,
2071 inline_blame_popover_show_task: None,
2072 signature_help_state: SignatureHelpState::default(),
2073 auto_signature_help: None,
2074 find_all_references_task_sources: Vec::new(),
2075 next_completion_id: 0,
2076 next_inlay_id: 0,
2077 code_action_providers,
2078 available_code_actions: None,
2079 code_actions_task: None,
2080 quick_selection_highlight_task: None,
2081 debounced_selection_highlight_task: None,
2082 document_highlights_task: None,
2083 linked_editing_range_task: None,
2084 pending_rename: None,
2085 searchable: !is_minimap,
2086 cursor_shape: EditorSettings::get_global(cx)
2087 .cursor_shape
2088 .unwrap_or_default(),
2089 current_line_highlight: None,
2090 autoindent_mode: Some(AutoindentMode::EachLine),
2091 collapse_matches: false,
2092 workspace: None,
2093 input_enabled: !is_minimap,
2094 use_modal_editing: full_mode,
2095 read_only: is_minimap,
2096 use_autoclose: true,
2097 use_auto_surround: true,
2098 auto_replace_emoji_shortcode: false,
2099 jsx_tag_auto_close_enabled_in_any_buffer: false,
2100 leader_id: None,
2101 remote_id: None,
2102 hover_state: HoverState::default(),
2103 pending_mouse_down: None,
2104 hovered_link_state: None,
2105 edit_prediction_provider: None,
2106 active_edit_prediction: None,
2107 stale_edit_prediction_in_menu: None,
2108 edit_prediction_preview: EditPredictionPreview::Inactive {
2109 released_too_fast: false,
2110 },
2111 inline_diagnostics_enabled: full_mode,
2112 diagnostics_enabled: full_mode,
2113 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2114 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2115 gutter_hovered: false,
2116 pixel_position_of_newest_cursor: None,
2117 last_bounds: None,
2118 last_position_map: None,
2119 expect_bounds_change: None,
2120 gutter_dimensions: GutterDimensions::default(),
2121 style: None,
2122 show_cursor_names: false,
2123 hovered_cursors: HashMap::default(),
2124 next_editor_action_id: EditorActionId::default(),
2125 editor_actions: Rc::default(),
2126 edit_predictions_hidden_for_vim_mode: false,
2127 show_edit_predictions_override: None,
2128 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2129 edit_prediction_settings: EditPredictionSettings::Disabled,
2130 edit_prediction_indent_conflict: false,
2131 edit_prediction_requires_modifier_in_indent_conflict: true,
2132 custom_context_menu: None,
2133 show_git_blame_gutter: false,
2134 show_git_blame_inline: false,
2135 show_selection_menu: None,
2136 show_git_blame_inline_delay_task: None,
2137 git_blame_inline_enabled: full_mode
2138 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2139 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2140 serialize_dirty_buffers: !is_minimap
2141 && ProjectSettings::get_global(cx)
2142 .session
2143 .restore_unsaved_buffers,
2144 blame: None,
2145 blame_subscription: None,
2146 tasks: BTreeMap::default(),
2147
2148 breakpoint_store,
2149 gutter_breakpoint_indicator: (None, None),
2150 hovered_diff_hunk_row: None,
2151 _subscriptions: (!is_minimap)
2152 .then(|| {
2153 vec![
2154 cx.observe(&buffer, Self::on_buffer_changed),
2155 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2156 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2157 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2158 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2159 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2160 cx.observe_window_activation(window, |editor, window, cx| {
2161 let active = window.is_window_active();
2162 editor.blink_manager.update(cx, |blink_manager, cx| {
2163 if active {
2164 blink_manager.enable(cx);
2165 } else {
2166 blink_manager.disable(cx);
2167 }
2168 });
2169 if active {
2170 editor.show_mouse_cursor(cx);
2171 }
2172 }),
2173 ]
2174 })
2175 .unwrap_or_default(),
2176 tasks_update_task: None,
2177 pull_diagnostics_task: Task::ready(()),
2178 colors: None,
2179 next_color_inlay_id: 0,
2180 linked_edit_ranges: Default::default(),
2181 in_project_search: false,
2182 previous_search_ranges: None,
2183 breadcrumb_header: None,
2184 focused_block: None,
2185 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2186 addons: HashMap::default(),
2187 registered_buffers: HashMap::default(),
2188 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2189 selection_mark_mode: false,
2190 toggle_fold_multiple_buffers: Task::ready(()),
2191 serialize_selections: Task::ready(()),
2192 serialize_folds: Task::ready(()),
2193 text_style_refinement: None,
2194 load_diff_task: load_uncommitted_diff,
2195 temporary_diff_override: false,
2196 mouse_cursor_hidden: false,
2197 minimap: None,
2198 hide_mouse_mode: EditorSettings::get_global(cx)
2199 .hide_mouse
2200 .unwrap_or_default(),
2201 change_list: ChangeList::new(),
2202 mode,
2203 selection_drag_state: SelectionDragState::None,
2204 folding_newlines: Task::ready(()),
2205 };
2206
2207 if is_minimap {
2208 return editor;
2209 }
2210
2211 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2212 editor
2213 ._subscriptions
2214 .push(cx.observe(breakpoints, |_, _, cx| {
2215 cx.notify();
2216 }));
2217 }
2218 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2219 editor._subscriptions.extend(project_subscriptions);
2220
2221 editor._subscriptions.push(cx.subscribe_in(
2222 &cx.entity(),
2223 window,
2224 |editor, _, e: &EditorEvent, window, cx| match e {
2225 EditorEvent::ScrollPositionChanged { local, .. } => {
2226 if *local {
2227 let new_anchor = editor.scroll_manager.anchor();
2228 let snapshot = editor.snapshot(window, cx);
2229 editor.update_restoration_data(cx, move |data| {
2230 data.scroll_position = (
2231 new_anchor.top_row(&snapshot.buffer_snapshot),
2232 new_anchor.offset,
2233 );
2234 });
2235 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2236 editor.inline_blame_popover.take();
2237 }
2238 }
2239 EditorEvent::Edited { .. } => {
2240 if !vim_enabled(cx) {
2241 let (map, selections) = editor.selections.all_adjusted_display(cx);
2242 let pop_state = editor
2243 .change_list
2244 .last()
2245 .map(|previous| {
2246 previous.len() == selections.len()
2247 && previous.iter().enumerate().all(|(ix, p)| {
2248 p.to_display_point(&map).row()
2249 == selections[ix].head().row()
2250 })
2251 })
2252 .unwrap_or(false);
2253 let new_positions = selections
2254 .into_iter()
2255 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2256 .collect();
2257 editor
2258 .change_list
2259 .push_to_change_list(pop_state, new_positions);
2260 }
2261 }
2262 _ => (),
2263 },
2264 ));
2265
2266 if let Some(dap_store) = editor
2267 .project
2268 .as_ref()
2269 .map(|project| project.read(cx).dap_store())
2270 {
2271 let weak_editor = cx.weak_entity();
2272
2273 editor
2274 ._subscriptions
2275 .push(
2276 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2277 let session_entity = cx.entity();
2278 weak_editor
2279 .update(cx, |editor, cx| {
2280 editor._subscriptions.push(
2281 cx.subscribe(&session_entity, Self::on_debug_session_event),
2282 );
2283 })
2284 .ok();
2285 }),
2286 );
2287
2288 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2289 editor
2290 ._subscriptions
2291 .push(cx.subscribe(&session, Self::on_debug_session_event));
2292 }
2293 }
2294
2295 // skip adding the initial selection to selection history
2296 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2297 editor.end_selection(window, cx);
2298 editor.selection_history.mode = SelectionHistoryMode::Normal;
2299
2300 editor.scroll_manager.show_scrollbars(window, cx);
2301 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2302
2303 if full_mode {
2304 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2305 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2306
2307 if editor.git_blame_inline_enabled {
2308 editor.start_git_blame_inline(false, window, cx);
2309 }
2310
2311 editor.go_to_active_debug_line(window, cx);
2312
2313 if let Some(buffer) = buffer.read(cx).as_singleton()
2314 && let Some(project) = editor.project()
2315 {
2316 let handle = project.update(cx, |project, cx| {
2317 project.register_buffer_with_language_servers(&buffer, cx)
2318 });
2319 editor
2320 .registered_buffers
2321 .insert(buffer.read(cx).remote_id(), handle);
2322 }
2323
2324 editor.minimap =
2325 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2326 editor.colors = Some(LspColorData::new(cx));
2327 editor.update_lsp_data(false, None, window, cx);
2328 }
2329
2330 if editor.mode.is_full() {
2331 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2332 }
2333
2334 editor
2335 }
2336
2337 pub fn deploy_mouse_context_menu(
2338 &mut self,
2339 position: gpui::Point<Pixels>,
2340 context_menu: Entity<ContextMenu>,
2341 window: &mut Window,
2342 cx: &mut Context<Self>,
2343 ) {
2344 self.mouse_context_menu = Some(MouseContextMenu::new(
2345 self,
2346 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2347 context_menu,
2348 window,
2349 cx,
2350 ));
2351 }
2352
2353 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2354 self.mouse_context_menu
2355 .as_ref()
2356 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2357 }
2358
2359 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2360 if self
2361 .selections
2362 .pending
2363 .as_ref()
2364 .is_some_and(|pending_selection| {
2365 let snapshot = self.buffer().read(cx).snapshot(cx);
2366 pending_selection
2367 .selection
2368 .range()
2369 .includes(range, &snapshot)
2370 })
2371 {
2372 return true;
2373 }
2374
2375 self.selections
2376 .disjoint_in_range::<usize>(range.clone(), cx)
2377 .into_iter()
2378 .any(|selection| {
2379 // This is needed to cover a corner case, if we just check for an existing
2380 // selection in the fold range, having a cursor at the start of the fold
2381 // marks it as selected. Non-empty selections don't cause this.
2382 let length = selection.end - selection.start;
2383 length > 0
2384 })
2385 }
2386
2387 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2388 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2389 }
2390
2391 fn key_context_internal(
2392 &self,
2393 has_active_edit_prediction: bool,
2394 window: &Window,
2395 cx: &App,
2396 ) -> KeyContext {
2397 let mut key_context = KeyContext::new_with_defaults();
2398 key_context.add("Editor");
2399 let mode = match self.mode {
2400 EditorMode::SingleLine => "single_line",
2401 EditorMode::AutoHeight { .. } => "auto_height",
2402 EditorMode::Minimap { .. } => "minimap",
2403 EditorMode::Full { .. } => "full",
2404 };
2405
2406 if EditorSettings::jupyter_enabled(cx) {
2407 key_context.add("jupyter");
2408 }
2409
2410 key_context.set("mode", mode);
2411 if self.pending_rename.is_some() {
2412 key_context.add("renaming");
2413 }
2414
2415 match self.context_menu.borrow().as_ref() {
2416 Some(CodeContextMenu::Completions(menu)) => {
2417 if menu.visible() {
2418 key_context.add("menu");
2419 key_context.add("showing_completions");
2420 }
2421 }
2422 Some(CodeContextMenu::CodeActions(menu)) => {
2423 if menu.visible() {
2424 key_context.add("menu");
2425 key_context.add("showing_code_actions")
2426 }
2427 }
2428 None => {}
2429 }
2430
2431 if self.signature_help_state.has_multiple_signatures() {
2432 key_context.add("showing_signature_help");
2433 }
2434
2435 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2436 if !self.focus_handle(cx).contains_focused(window, cx)
2437 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2438 {
2439 for addon in self.addons.values() {
2440 addon.extend_key_context(&mut key_context, cx)
2441 }
2442 }
2443
2444 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2445 if let Some(extension) = singleton_buffer
2446 .read(cx)
2447 .file()
2448 .and_then(|file| file.path().extension()?.to_str())
2449 {
2450 key_context.set("extension", extension.to_string());
2451 }
2452 } else {
2453 key_context.add("multibuffer");
2454 }
2455
2456 if has_active_edit_prediction {
2457 if self.edit_prediction_in_conflict() {
2458 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2459 } else {
2460 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2461 key_context.add("copilot_suggestion");
2462 }
2463 }
2464
2465 if self.selection_mark_mode {
2466 key_context.add("selection_mode");
2467 }
2468
2469 key_context
2470 }
2471
2472 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2473 if self.mouse_cursor_hidden {
2474 self.mouse_cursor_hidden = false;
2475 cx.notify();
2476 }
2477 }
2478
2479 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2480 let hide_mouse_cursor = match origin {
2481 HideMouseCursorOrigin::TypingAction => {
2482 matches!(
2483 self.hide_mouse_mode,
2484 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2485 )
2486 }
2487 HideMouseCursorOrigin::MovementAction => {
2488 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2489 }
2490 };
2491 if self.mouse_cursor_hidden != hide_mouse_cursor {
2492 self.mouse_cursor_hidden = hide_mouse_cursor;
2493 cx.notify();
2494 }
2495 }
2496
2497 pub fn edit_prediction_in_conflict(&self) -> bool {
2498 if !self.show_edit_predictions_in_menu() {
2499 return false;
2500 }
2501
2502 let showing_completions = self
2503 .context_menu
2504 .borrow()
2505 .as_ref()
2506 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2507
2508 showing_completions
2509 || self.edit_prediction_requires_modifier()
2510 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2511 // bindings to insert tab characters.
2512 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2513 }
2514
2515 pub fn accept_edit_prediction_keybind(
2516 &self,
2517 accept_partial: bool,
2518 window: &Window,
2519 cx: &App,
2520 ) -> AcceptEditPredictionBinding {
2521 let key_context = self.key_context_internal(true, window, cx);
2522 let in_conflict = self.edit_prediction_in_conflict();
2523
2524 let bindings = if accept_partial {
2525 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2526 } else {
2527 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2528 };
2529
2530 // TODO: if the binding contains multiple keystrokes, display all of them, not
2531 // just the first one.
2532 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2533 !in_conflict
2534 || binding
2535 .keystrokes()
2536 .first()
2537 .is_some_and(|keystroke| keystroke.modifiers.modified())
2538 }))
2539 }
2540
2541 pub fn new_file(
2542 workspace: &mut Workspace,
2543 _: &workspace::NewFile,
2544 window: &mut Window,
2545 cx: &mut Context<Workspace>,
2546 ) {
2547 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2548 "Failed to create buffer",
2549 window,
2550 cx,
2551 |e, _, _| match e.error_code() {
2552 ErrorCode::RemoteUpgradeRequired => Some(format!(
2553 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2554 e.error_tag("required").unwrap_or("the latest version")
2555 )),
2556 _ => None,
2557 },
2558 );
2559 }
2560
2561 pub fn new_in_workspace(
2562 workspace: &mut Workspace,
2563 window: &mut Window,
2564 cx: &mut Context<Workspace>,
2565 ) -> Task<Result<Entity<Editor>>> {
2566 let project = workspace.project().clone();
2567 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2568
2569 cx.spawn_in(window, async move |workspace, cx| {
2570 let buffer = create.await?;
2571 workspace.update_in(cx, |workspace, window, cx| {
2572 let editor =
2573 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2574 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2575 editor
2576 })
2577 })
2578 }
2579
2580 fn new_file_vertical(
2581 workspace: &mut Workspace,
2582 _: &workspace::NewFileSplitVertical,
2583 window: &mut Window,
2584 cx: &mut Context<Workspace>,
2585 ) {
2586 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2587 }
2588
2589 fn new_file_horizontal(
2590 workspace: &mut Workspace,
2591 _: &workspace::NewFileSplitHorizontal,
2592 window: &mut Window,
2593 cx: &mut Context<Workspace>,
2594 ) {
2595 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2596 }
2597
2598 fn new_file_in_direction(
2599 workspace: &mut Workspace,
2600 direction: SplitDirection,
2601 window: &mut Window,
2602 cx: &mut Context<Workspace>,
2603 ) {
2604 let project = workspace.project().clone();
2605 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2606
2607 cx.spawn_in(window, async move |workspace, cx| {
2608 let buffer = create.await?;
2609 workspace.update_in(cx, move |workspace, window, cx| {
2610 workspace.split_item(
2611 direction,
2612 Box::new(
2613 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2614 ),
2615 window,
2616 cx,
2617 )
2618 })?;
2619 anyhow::Ok(())
2620 })
2621 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2622 match e.error_code() {
2623 ErrorCode::RemoteUpgradeRequired => Some(format!(
2624 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2625 e.error_tag("required").unwrap_or("the latest version")
2626 )),
2627 _ => None,
2628 }
2629 });
2630 }
2631
2632 pub fn leader_id(&self) -> Option<CollaboratorId> {
2633 self.leader_id
2634 }
2635
2636 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2637 &self.buffer
2638 }
2639
2640 pub fn project(&self) -> Option<&Entity<Project>> {
2641 self.project.as_ref()
2642 }
2643
2644 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2645 self.workspace.as_ref()?.0.upgrade()
2646 }
2647
2648 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2649 self.buffer().read(cx).title(cx)
2650 }
2651
2652 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2653 let git_blame_gutter_max_author_length = self
2654 .render_git_blame_gutter(cx)
2655 .then(|| {
2656 if let Some(blame) = self.blame.as_ref() {
2657 let max_author_length =
2658 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2659 Some(max_author_length)
2660 } else {
2661 None
2662 }
2663 })
2664 .flatten();
2665
2666 EditorSnapshot {
2667 mode: self.mode.clone(),
2668 show_gutter: self.show_gutter,
2669 show_line_numbers: self.show_line_numbers,
2670 show_git_diff_gutter: self.show_git_diff_gutter,
2671 show_code_actions: self.show_code_actions,
2672 show_runnables: self.show_runnables,
2673 show_breakpoints: self.show_breakpoints,
2674 git_blame_gutter_max_author_length,
2675 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2676 scroll_anchor: self.scroll_manager.anchor(),
2677 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2678 placeholder_text: self.placeholder_text.clone(),
2679 is_focused: self.focus_handle.is_focused(window),
2680 current_line_highlight: self
2681 .current_line_highlight
2682 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2683 gutter_hovered: self.gutter_hovered,
2684 }
2685 }
2686
2687 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2688 self.buffer.read(cx).language_at(point, cx)
2689 }
2690
2691 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2692 self.buffer.read(cx).read(cx).file_at(point).cloned()
2693 }
2694
2695 pub fn active_excerpt(
2696 &self,
2697 cx: &App,
2698 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2699 self.buffer
2700 .read(cx)
2701 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2702 }
2703
2704 pub fn mode(&self) -> &EditorMode {
2705 &self.mode
2706 }
2707
2708 pub fn set_mode(&mut self, mode: EditorMode) {
2709 self.mode = mode;
2710 }
2711
2712 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2713 self.collaboration_hub.as_deref()
2714 }
2715
2716 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2717 self.collaboration_hub = Some(hub);
2718 }
2719
2720 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2721 self.in_project_search = in_project_search;
2722 }
2723
2724 pub fn set_custom_context_menu(
2725 &mut self,
2726 f: impl 'static
2727 + Fn(
2728 &mut Self,
2729 DisplayPoint,
2730 &mut Window,
2731 &mut Context<Self>,
2732 ) -> Option<Entity<ui::ContextMenu>>,
2733 ) {
2734 self.custom_context_menu = Some(Box::new(f))
2735 }
2736
2737 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2738 self.completion_provider = provider;
2739 }
2740
2741 #[cfg(any(test, feature = "test-support"))]
2742 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2743 self.completion_provider.clone()
2744 }
2745
2746 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2747 self.semantics_provider.clone()
2748 }
2749
2750 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2751 self.semantics_provider = provider;
2752 }
2753
2754 pub fn set_edit_prediction_provider<T>(
2755 &mut self,
2756 provider: Option<Entity<T>>,
2757 window: &mut Window,
2758 cx: &mut Context<Self>,
2759 ) where
2760 T: EditPredictionProvider,
2761 {
2762 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2763 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2764 if this.focus_handle.is_focused(window) {
2765 this.update_visible_edit_prediction(window, cx);
2766 }
2767 }),
2768 provider: Arc::new(provider),
2769 });
2770 self.update_edit_prediction_settings(cx);
2771 self.refresh_edit_prediction(false, false, window, cx);
2772 }
2773
2774 pub fn placeholder_text(&self) -> Option<&str> {
2775 self.placeholder_text.as_deref()
2776 }
2777
2778 pub fn set_placeholder_text(
2779 &mut self,
2780 placeholder_text: impl Into<Arc<str>>,
2781 cx: &mut Context<Self>,
2782 ) {
2783 let placeholder_text = Some(placeholder_text.into());
2784 if self.placeholder_text != placeholder_text {
2785 self.placeholder_text = placeholder_text;
2786 cx.notify();
2787 }
2788 }
2789
2790 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2791 self.cursor_shape = cursor_shape;
2792
2793 // Disrupt blink for immediate user feedback that the cursor shape has changed
2794 self.blink_manager.update(cx, BlinkManager::show_cursor);
2795
2796 cx.notify();
2797 }
2798
2799 pub fn set_current_line_highlight(
2800 &mut self,
2801 current_line_highlight: Option<CurrentLineHighlight>,
2802 ) {
2803 self.current_line_highlight = current_line_highlight;
2804 }
2805
2806 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2807 self.collapse_matches = collapse_matches;
2808 }
2809
2810 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2811 let buffers = self.buffer.read(cx).all_buffers();
2812 let Some(project) = self.project.as_ref() else {
2813 return;
2814 };
2815 project.update(cx, |project, cx| {
2816 for buffer in buffers {
2817 self.registered_buffers
2818 .entry(buffer.read(cx).remote_id())
2819 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2820 }
2821 })
2822 }
2823
2824 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2825 if self.collapse_matches {
2826 return range.start..range.start;
2827 }
2828 range.clone()
2829 }
2830
2831 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2832 if self.display_map.read(cx).clip_at_line_ends != clip {
2833 self.display_map
2834 .update(cx, |map, _| map.clip_at_line_ends = clip);
2835 }
2836 }
2837
2838 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2839 self.input_enabled = input_enabled;
2840 }
2841
2842 pub fn set_edit_predictions_hidden_for_vim_mode(
2843 &mut self,
2844 hidden: bool,
2845 window: &mut Window,
2846 cx: &mut Context<Self>,
2847 ) {
2848 if hidden != self.edit_predictions_hidden_for_vim_mode {
2849 self.edit_predictions_hidden_for_vim_mode = hidden;
2850 if hidden {
2851 self.update_visible_edit_prediction(window, cx);
2852 } else {
2853 self.refresh_edit_prediction(true, false, window, cx);
2854 }
2855 }
2856 }
2857
2858 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2859 self.menu_edit_predictions_policy = value;
2860 }
2861
2862 pub fn set_autoindent(&mut self, autoindent: bool) {
2863 if autoindent {
2864 self.autoindent_mode = Some(AutoindentMode::EachLine);
2865 } else {
2866 self.autoindent_mode = None;
2867 }
2868 }
2869
2870 pub fn read_only(&self, cx: &App) -> bool {
2871 self.read_only || self.buffer.read(cx).read_only()
2872 }
2873
2874 pub fn set_read_only(&mut self, read_only: bool) {
2875 self.read_only = read_only;
2876 }
2877
2878 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2879 self.use_autoclose = autoclose;
2880 }
2881
2882 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2883 self.use_auto_surround = auto_surround;
2884 }
2885
2886 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2887 self.auto_replace_emoji_shortcode = auto_replace;
2888 }
2889
2890 pub fn toggle_edit_predictions(
2891 &mut self,
2892 _: &ToggleEditPrediction,
2893 window: &mut Window,
2894 cx: &mut Context<Self>,
2895 ) {
2896 if self.show_edit_predictions_override.is_some() {
2897 self.set_show_edit_predictions(None, window, cx);
2898 } else {
2899 let show_edit_predictions = !self.edit_predictions_enabled();
2900 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2901 }
2902 }
2903
2904 pub fn set_show_edit_predictions(
2905 &mut self,
2906 show_edit_predictions: Option<bool>,
2907 window: &mut Window,
2908 cx: &mut Context<Self>,
2909 ) {
2910 self.show_edit_predictions_override = show_edit_predictions;
2911 self.update_edit_prediction_settings(cx);
2912
2913 if let Some(false) = show_edit_predictions {
2914 self.discard_edit_prediction(false, cx);
2915 } else {
2916 self.refresh_edit_prediction(false, true, window, cx);
2917 }
2918 }
2919
2920 fn edit_predictions_disabled_in_scope(
2921 &self,
2922 buffer: &Entity<Buffer>,
2923 buffer_position: language::Anchor,
2924 cx: &App,
2925 ) -> bool {
2926 let snapshot = buffer.read(cx).snapshot();
2927 let settings = snapshot.settings_at(buffer_position, cx);
2928
2929 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2930 return false;
2931 };
2932
2933 scope.override_name().is_some_and(|scope_name| {
2934 settings
2935 .edit_predictions_disabled_in
2936 .iter()
2937 .any(|s| s == scope_name)
2938 })
2939 }
2940
2941 pub fn set_use_modal_editing(&mut self, to: bool) {
2942 self.use_modal_editing = to;
2943 }
2944
2945 pub fn use_modal_editing(&self) -> bool {
2946 self.use_modal_editing
2947 }
2948
2949 fn selections_did_change(
2950 &mut self,
2951 local: bool,
2952 old_cursor_position: &Anchor,
2953 effects: SelectionEffects,
2954 window: &mut Window,
2955 cx: &mut Context<Self>,
2956 ) {
2957 window.invalidate_character_coordinates();
2958
2959 // Copy selections to primary selection buffer
2960 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2961 if local {
2962 let selections = self.selections.all::<usize>(cx);
2963 let buffer_handle = self.buffer.read(cx).read(cx);
2964
2965 let mut text = String::new();
2966 for (index, selection) in selections.iter().enumerate() {
2967 let text_for_selection = buffer_handle
2968 .text_for_range(selection.start..selection.end)
2969 .collect::<String>();
2970
2971 text.push_str(&text_for_selection);
2972 if index != selections.len() - 1 {
2973 text.push('\n');
2974 }
2975 }
2976
2977 if !text.is_empty() {
2978 cx.write_to_primary(ClipboardItem::new_string(text));
2979 }
2980 }
2981
2982 let selection_anchors = self.selections.disjoint_anchors();
2983
2984 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2985 self.buffer.update(cx, |buffer, cx| {
2986 buffer.set_active_selections(
2987 &selection_anchors,
2988 self.selections.line_mode,
2989 self.cursor_shape,
2990 cx,
2991 )
2992 });
2993 }
2994 let display_map = self
2995 .display_map
2996 .update(cx, |display_map, cx| display_map.snapshot(cx));
2997 let buffer = &display_map.buffer_snapshot;
2998 if self.selections.count() == 1 {
2999 self.add_selections_state = None;
3000 }
3001 self.select_next_state = None;
3002 self.select_prev_state = None;
3003 self.select_syntax_node_history.try_clear();
3004 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3005 self.snippet_stack.invalidate(&selection_anchors, buffer);
3006 self.take_rename(false, window, cx);
3007
3008 let newest_selection = self.selections.newest_anchor();
3009 let new_cursor_position = newest_selection.head();
3010 let selection_start = newest_selection.start;
3011
3012 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3013 self.push_to_nav_history(
3014 *old_cursor_position,
3015 Some(new_cursor_position.to_point(buffer)),
3016 false,
3017 effects.nav_history == Some(true),
3018 cx,
3019 );
3020 }
3021
3022 if local {
3023 if let Some(buffer_id) = new_cursor_position.buffer_id
3024 && !self.registered_buffers.contains_key(&buffer_id)
3025 && let Some(project) = self.project.as_ref()
3026 {
3027 project.update(cx, |project, cx| {
3028 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3029 return;
3030 };
3031 self.registered_buffers.insert(
3032 buffer_id,
3033 project.register_buffer_with_language_servers(&buffer, cx),
3034 );
3035 })
3036 }
3037
3038 let mut context_menu = self.context_menu.borrow_mut();
3039 let completion_menu = match context_menu.as_ref() {
3040 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3041 Some(CodeContextMenu::CodeActions(_)) => {
3042 *context_menu = None;
3043 None
3044 }
3045 None => None,
3046 };
3047 let completion_position = completion_menu.map(|menu| menu.initial_position);
3048 drop(context_menu);
3049
3050 if effects.completions
3051 && let Some(completion_position) = completion_position
3052 {
3053 let start_offset = selection_start.to_offset(buffer);
3054 let position_matches = start_offset == completion_position.to_offset(buffer);
3055 let continue_showing = if position_matches {
3056 if self.snippet_stack.is_empty() {
3057 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3058 } else {
3059 // Snippet choices can be shown even when the cursor is in whitespace.
3060 // Dismissing the menu with actions like backspace is handled by
3061 // invalidation regions.
3062 true
3063 }
3064 } else {
3065 false
3066 };
3067
3068 if continue_showing {
3069 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3070 } else {
3071 self.hide_context_menu(window, cx);
3072 }
3073 }
3074
3075 hide_hover(self, cx);
3076
3077 if old_cursor_position.to_display_point(&display_map).row()
3078 != new_cursor_position.to_display_point(&display_map).row()
3079 {
3080 self.available_code_actions.take();
3081 }
3082 self.refresh_code_actions(window, cx);
3083 self.refresh_document_highlights(cx);
3084 self.refresh_selected_text_highlights(false, window, cx);
3085 refresh_matching_bracket_highlights(self, window, cx);
3086 self.update_visible_edit_prediction(window, cx);
3087 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3088 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3089 self.inline_blame_popover.take();
3090 if self.git_blame_inline_enabled {
3091 self.start_inline_blame_timer(window, cx);
3092 }
3093 }
3094
3095 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3096 cx.emit(EditorEvent::SelectionsChanged { local });
3097
3098 let selections = &self.selections.disjoint;
3099 if selections.len() == 1 {
3100 cx.emit(SearchEvent::ActiveMatchChanged)
3101 }
3102 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3103 let inmemory_selections = selections
3104 .iter()
3105 .map(|s| {
3106 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3107 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3108 })
3109 .collect();
3110 self.update_restoration_data(cx, |data| {
3111 data.selections = inmemory_selections;
3112 });
3113
3114 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3115 && let Some(workspace_id) =
3116 self.workspace.as_ref().and_then(|workspace| workspace.1)
3117 {
3118 let snapshot = self.buffer().read(cx).snapshot(cx);
3119 let selections = selections.clone();
3120 let background_executor = cx.background_executor().clone();
3121 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3122 self.serialize_selections = cx.background_spawn(async move {
3123 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3124 let db_selections = selections
3125 .iter()
3126 .map(|selection| {
3127 (
3128 selection.start.to_offset(&snapshot),
3129 selection.end.to_offset(&snapshot),
3130 )
3131 })
3132 .collect();
3133
3134 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3135 .await
3136 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3137 .log_err();
3138 });
3139 }
3140 }
3141
3142 cx.notify();
3143 }
3144
3145 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3146 use text::ToOffset as _;
3147 use text::ToPoint as _;
3148
3149 if self.mode.is_minimap()
3150 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3151 {
3152 return;
3153 }
3154
3155 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3156 return;
3157 };
3158
3159 let snapshot = singleton.read(cx).snapshot();
3160 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3161 let display_snapshot = display_map.snapshot(cx);
3162
3163 display_snapshot
3164 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3165 .map(|fold| {
3166 fold.range.start.text_anchor.to_point(&snapshot)
3167 ..fold.range.end.text_anchor.to_point(&snapshot)
3168 })
3169 .collect()
3170 });
3171 self.update_restoration_data(cx, |data| {
3172 data.folds = inmemory_folds;
3173 });
3174
3175 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3176 return;
3177 };
3178 let background_executor = cx.background_executor().clone();
3179 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3180 let db_folds = self.display_map.update(cx, |display_map, cx| {
3181 display_map
3182 .snapshot(cx)
3183 .folds_in_range(0..snapshot.len())
3184 .map(|fold| {
3185 (
3186 fold.range.start.text_anchor.to_offset(&snapshot),
3187 fold.range.end.text_anchor.to_offset(&snapshot),
3188 )
3189 })
3190 .collect()
3191 });
3192 self.serialize_folds = cx.background_spawn(async move {
3193 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3194 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3195 .await
3196 .with_context(|| {
3197 format!(
3198 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3199 )
3200 })
3201 .log_err();
3202 });
3203 }
3204
3205 pub fn sync_selections(
3206 &mut self,
3207 other: Entity<Editor>,
3208 cx: &mut Context<Self>,
3209 ) -> gpui::Subscription {
3210 let other_selections = other.read(cx).selections.disjoint.to_vec();
3211 self.selections.change_with(cx, |selections| {
3212 selections.select_anchors(other_selections);
3213 });
3214
3215 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3216 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3217 let other_selections = other.read(cx).selections.disjoint.to_vec();
3218 if other_selections.is_empty() {
3219 return;
3220 }
3221 this.selections.change_with(cx, |selections| {
3222 selections.select_anchors(other_selections);
3223 });
3224 }
3225 });
3226
3227 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3228 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3229 let these_selections = this.selections.disjoint.to_vec();
3230 if these_selections.is_empty() {
3231 return;
3232 }
3233 other.update(cx, |other_editor, cx| {
3234 other_editor.selections.change_with(cx, |selections| {
3235 selections.select_anchors(these_selections);
3236 })
3237 });
3238 }
3239 });
3240
3241 Subscription::join(other_subscription, this_subscription)
3242 }
3243
3244 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3245 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3246 /// effects of selection change occur at the end of the transaction.
3247 pub fn change_selections<R>(
3248 &mut self,
3249 effects: SelectionEffects,
3250 window: &mut Window,
3251 cx: &mut Context<Self>,
3252 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3253 ) -> R {
3254 if let Some(state) = &mut self.deferred_selection_effects_state {
3255 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3256 state.effects.completions = effects.completions;
3257 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3258 let (changed, result) = self.selections.change_with(cx, change);
3259 state.changed |= changed;
3260 return result;
3261 }
3262 let mut state = DeferredSelectionEffectsState {
3263 changed: false,
3264 effects,
3265 old_cursor_position: self.selections.newest_anchor().head(),
3266 history_entry: SelectionHistoryEntry {
3267 selections: self.selections.disjoint_anchors(),
3268 select_next_state: self.select_next_state.clone(),
3269 select_prev_state: self.select_prev_state.clone(),
3270 add_selections_state: self.add_selections_state.clone(),
3271 },
3272 };
3273 let (changed, result) = self.selections.change_with(cx, change);
3274 state.changed = state.changed || changed;
3275 if self.defer_selection_effects {
3276 self.deferred_selection_effects_state = Some(state);
3277 } else {
3278 self.apply_selection_effects(state, window, cx);
3279 }
3280 result
3281 }
3282
3283 /// Defers the effects of selection change, so that the effects of multiple calls to
3284 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3285 /// to selection history and the state of popovers based on selection position aren't
3286 /// erroneously updated.
3287 pub fn with_selection_effects_deferred<R>(
3288 &mut self,
3289 window: &mut Window,
3290 cx: &mut Context<Self>,
3291 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3292 ) -> R {
3293 let already_deferred = self.defer_selection_effects;
3294 self.defer_selection_effects = true;
3295 let result = update(self, window, cx);
3296 if !already_deferred {
3297 self.defer_selection_effects = false;
3298 if let Some(state) = self.deferred_selection_effects_state.take() {
3299 self.apply_selection_effects(state, window, cx);
3300 }
3301 }
3302 result
3303 }
3304
3305 fn apply_selection_effects(
3306 &mut self,
3307 state: DeferredSelectionEffectsState,
3308 window: &mut Window,
3309 cx: &mut Context<Self>,
3310 ) {
3311 if state.changed {
3312 self.selection_history.push(state.history_entry);
3313
3314 if let Some(autoscroll) = state.effects.scroll {
3315 self.request_autoscroll(autoscroll, cx);
3316 }
3317
3318 let old_cursor_position = &state.old_cursor_position;
3319
3320 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3321
3322 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3323 self.show_signature_help(&ShowSignatureHelp, window, cx);
3324 }
3325 }
3326 }
3327
3328 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3329 where
3330 I: IntoIterator<Item = (Range<S>, T)>,
3331 S: ToOffset,
3332 T: Into<Arc<str>>,
3333 {
3334 if self.read_only(cx) {
3335 return;
3336 }
3337
3338 self.buffer
3339 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3340 }
3341
3342 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3343 where
3344 I: IntoIterator<Item = (Range<S>, T)>,
3345 S: ToOffset,
3346 T: Into<Arc<str>>,
3347 {
3348 if self.read_only(cx) {
3349 return;
3350 }
3351
3352 self.buffer.update(cx, |buffer, cx| {
3353 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3354 });
3355 }
3356
3357 pub fn edit_with_block_indent<I, S, T>(
3358 &mut self,
3359 edits: I,
3360 original_indent_columns: Vec<Option<u32>>,
3361 cx: &mut Context<Self>,
3362 ) where
3363 I: IntoIterator<Item = (Range<S>, T)>,
3364 S: ToOffset,
3365 T: Into<Arc<str>>,
3366 {
3367 if self.read_only(cx) {
3368 return;
3369 }
3370
3371 self.buffer.update(cx, |buffer, cx| {
3372 buffer.edit(
3373 edits,
3374 Some(AutoindentMode::Block {
3375 original_indent_columns,
3376 }),
3377 cx,
3378 )
3379 });
3380 }
3381
3382 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3383 self.hide_context_menu(window, cx);
3384
3385 match phase {
3386 SelectPhase::Begin {
3387 position,
3388 add,
3389 click_count,
3390 } => self.begin_selection(position, add, click_count, window, cx),
3391 SelectPhase::BeginColumnar {
3392 position,
3393 goal_column,
3394 reset,
3395 mode,
3396 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3397 SelectPhase::Extend {
3398 position,
3399 click_count,
3400 } => self.extend_selection(position, click_count, window, cx),
3401 SelectPhase::Update {
3402 position,
3403 goal_column,
3404 scroll_delta,
3405 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3406 SelectPhase::End => self.end_selection(window, cx),
3407 }
3408 }
3409
3410 fn extend_selection(
3411 &mut self,
3412 position: DisplayPoint,
3413 click_count: usize,
3414 window: &mut Window,
3415 cx: &mut Context<Self>,
3416 ) {
3417 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3418 let tail = self.selections.newest::<usize>(cx).tail();
3419 self.begin_selection(position, false, click_count, window, cx);
3420
3421 let position = position.to_offset(&display_map, Bias::Left);
3422 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3423
3424 let mut pending_selection = self
3425 .selections
3426 .pending_anchor()
3427 .expect("extend_selection not called with pending selection");
3428 if position >= tail {
3429 pending_selection.start = tail_anchor;
3430 } else {
3431 pending_selection.end = tail_anchor;
3432 pending_selection.reversed = true;
3433 }
3434
3435 let mut pending_mode = self.selections.pending_mode().unwrap();
3436 match &mut pending_mode {
3437 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3438 _ => {}
3439 }
3440
3441 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3442 SelectionEffects::scroll(Autoscroll::fit())
3443 } else {
3444 SelectionEffects::no_scroll()
3445 };
3446
3447 self.change_selections(effects, window, cx, |s| {
3448 s.set_pending(pending_selection, pending_mode)
3449 });
3450 }
3451
3452 fn begin_selection(
3453 &mut self,
3454 position: DisplayPoint,
3455 add: bool,
3456 click_count: usize,
3457 window: &mut Window,
3458 cx: &mut Context<Self>,
3459 ) {
3460 if !self.focus_handle.is_focused(window) {
3461 self.last_focused_descendant = None;
3462 window.focus(&self.focus_handle);
3463 }
3464
3465 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3466 let buffer = &display_map.buffer_snapshot;
3467 let position = display_map.clip_point(position, Bias::Left);
3468
3469 let start;
3470 let end;
3471 let mode;
3472 let mut auto_scroll;
3473 match click_count {
3474 1 => {
3475 start = buffer.anchor_before(position.to_point(&display_map));
3476 end = start;
3477 mode = SelectMode::Character;
3478 auto_scroll = true;
3479 }
3480 2 => {
3481 let position = display_map
3482 .clip_point(position, Bias::Left)
3483 .to_offset(&display_map, Bias::Left);
3484 let (range, _) = buffer.surrounding_word(position, false);
3485 start = buffer.anchor_before(range.start);
3486 end = buffer.anchor_before(range.end);
3487 mode = SelectMode::Word(start..end);
3488 auto_scroll = true;
3489 }
3490 3 => {
3491 let position = display_map
3492 .clip_point(position, Bias::Left)
3493 .to_point(&display_map);
3494 let line_start = display_map.prev_line_boundary(position).0;
3495 let next_line_start = buffer.clip_point(
3496 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3497 Bias::Left,
3498 );
3499 start = buffer.anchor_before(line_start);
3500 end = buffer.anchor_before(next_line_start);
3501 mode = SelectMode::Line(start..end);
3502 auto_scroll = true;
3503 }
3504 _ => {
3505 start = buffer.anchor_before(0);
3506 end = buffer.anchor_before(buffer.len());
3507 mode = SelectMode::All;
3508 auto_scroll = false;
3509 }
3510 }
3511 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3512
3513 let point_to_delete: Option<usize> = {
3514 let selected_points: Vec<Selection<Point>> =
3515 self.selections.disjoint_in_range(start..end, cx);
3516
3517 if !add || click_count > 1 {
3518 None
3519 } else if !selected_points.is_empty() {
3520 Some(selected_points[0].id)
3521 } else {
3522 let clicked_point_already_selected =
3523 self.selections.disjoint.iter().find(|selection| {
3524 selection.start.to_point(buffer) == start.to_point(buffer)
3525 || selection.end.to_point(buffer) == end.to_point(buffer)
3526 });
3527
3528 clicked_point_already_selected.map(|selection| selection.id)
3529 }
3530 };
3531
3532 let selections_count = self.selections.count();
3533 let effects = if auto_scroll {
3534 SelectionEffects::default()
3535 } else {
3536 SelectionEffects::no_scroll()
3537 };
3538
3539 self.change_selections(effects, window, cx, |s| {
3540 if let Some(point_to_delete) = point_to_delete {
3541 s.delete(point_to_delete);
3542
3543 if selections_count == 1 {
3544 s.set_pending_anchor_range(start..end, mode);
3545 }
3546 } else {
3547 if !add {
3548 s.clear_disjoint();
3549 }
3550
3551 s.set_pending_anchor_range(start..end, mode);
3552 }
3553 });
3554 }
3555
3556 fn begin_columnar_selection(
3557 &mut self,
3558 position: DisplayPoint,
3559 goal_column: u32,
3560 reset: bool,
3561 mode: ColumnarMode,
3562 window: &mut Window,
3563 cx: &mut Context<Self>,
3564 ) {
3565 if !self.focus_handle.is_focused(window) {
3566 self.last_focused_descendant = None;
3567 window.focus(&self.focus_handle);
3568 }
3569
3570 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3571
3572 if reset {
3573 let pointer_position = display_map
3574 .buffer_snapshot
3575 .anchor_before(position.to_point(&display_map));
3576
3577 self.change_selections(
3578 SelectionEffects::scroll(Autoscroll::newest()),
3579 window,
3580 cx,
3581 |s| {
3582 s.clear_disjoint();
3583 s.set_pending_anchor_range(
3584 pointer_position..pointer_position,
3585 SelectMode::Character,
3586 );
3587 },
3588 );
3589 };
3590
3591 let tail = self.selections.newest::<Point>(cx).tail();
3592 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3593 self.columnar_selection_state = match mode {
3594 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3595 selection_tail: selection_anchor,
3596 display_point: if reset {
3597 if position.column() != goal_column {
3598 Some(DisplayPoint::new(position.row(), goal_column))
3599 } else {
3600 None
3601 }
3602 } else {
3603 None
3604 },
3605 }),
3606 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3607 selection_tail: selection_anchor,
3608 }),
3609 };
3610
3611 if !reset {
3612 self.select_columns(position, goal_column, &display_map, window, cx);
3613 }
3614 }
3615
3616 fn update_selection(
3617 &mut self,
3618 position: DisplayPoint,
3619 goal_column: u32,
3620 scroll_delta: gpui::Point<f32>,
3621 window: &mut Window,
3622 cx: &mut Context<Self>,
3623 ) {
3624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3625
3626 if self.columnar_selection_state.is_some() {
3627 self.select_columns(position, goal_column, &display_map, window, cx);
3628 } else if let Some(mut pending) = self.selections.pending_anchor() {
3629 let buffer = &display_map.buffer_snapshot;
3630 let head;
3631 let tail;
3632 let mode = self.selections.pending_mode().unwrap();
3633 match &mode {
3634 SelectMode::Character => {
3635 head = position.to_point(&display_map);
3636 tail = pending.tail().to_point(buffer);
3637 }
3638 SelectMode::Word(original_range) => {
3639 let offset = display_map
3640 .clip_point(position, Bias::Left)
3641 .to_offset(&display_map, Bias::Left);
3642 let original_range = original_range.to_offset(buffer);
3643
3644 let head_offset = if buffer.is_inside_word(offset, false)
3645 || original_range.contains(&offset)
3646 {
3647 let (word_range, _) = buffer.surrounding_word(offset, false);
3648 if word_range.start < original_range.start {
3649 word_range.start
3650 } else {
3651 word_range.end
3652 }
3653 } else {
3654 offset
3655 };
3656
3657 head = head_offset.to_point(buffer);
3658 if head_offset <= original_range.start {
3659 tail = original_range.end.to_point(buffer);
3660 } else {
3661 tail = original_range.start.to_point(buffer);
3662 }
3663 }
3664 SelectMode::Line(original_range) => {
3665 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3666
3667 let position = display_map
3668 .clip_point(position, Bias::Left)
3669 .to_point(&display_map);
3670 let line_start = display_map.prev_line_boundary(position).0;
3671 let next_line_start = buffer.clip_point(
3672 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3673 Bias::Left,
3674 );
3675
3676 if line_start < original_range.start {
3677 head = line_start
3678 } else {
3679 head = next_line_start
3680 }
3681
3682 if head <= original_range.start {
3683 tail = original_range.end;
3684 } else {
3685 tail = original_range.start;
3686 }
3687 }
3688 SelectMode::All => {
3689 return;
3690 }
3691 };
3692
3693 if head < tail {
3694 pending.start = buffer.anchor_before(head);
3695 pending.end = buffer.anchor_before(tail);
3696 pending.reversed = true;
3697 } else {
3698 pending.start = buffer.anchor_before(tail);
3699 pending.end = buffer.anchor_before(head);
3700 pending.reversed = false;
3701 }
3702
3703 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3704 s.set_pending(pending, mode);
3705 });
3706 } else {
3707 log::error!("update_selection dispatched with no pending selection");
3708 return;
3709 }
3710
3711 self.apply_scroll_delta(scroll_delta, window, cx);
3712 cx.notify();
3713 }
3714
3715 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3716 self.columnar_selection_state.take();
3717 if self.selections.pending_anchor().is_some() {
3718 let selections = self.selections.all::<usize>(cx);
3719 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3720 s.select(selections);
3721 s.clear_pending();
3722 });
3723 }
3724 }
3725
3726 fn select_columns(
3727 &mut self,
3728 head: DisplayPoint,
3729 goal_column: u32,
3730 display_map: &DisplaySnapshot,
3731 window: &mut Window,
3732 cx: &mut Context<Self>,
3733 ) {
3734 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3735 return;
3736 };
3737
3738 let tail = match columnar_state {
3739 ColumnarSelectionState::FromMouse {
3740 selection_tail,
3741 display_point,
3742 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3743 ColumnarSelectionState::FromSelection { selection_tail } => {
3744 selection_tail.to_display_point(display_map)
3745 }
3746 };
3747
3748 let start_row = cmp::min(tail.row(), head.row());
3749 let end_row = cmp::max(tail.row(), head.row());
3750 let start_column = cmp::min(tail.column(), goal_column);
3751 let end_column = cmp::max(tail.column(), goal_column);
3752 let reversed = start_column < tail.column();
3753
3754 let selection_ranges = (start_row.0..=end_row.0)
3755 .map(DisplayRow)
3756 .filter_map(|row| {
3757 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3758 || start_column <= display_map.line_len(row))
3759 && !display_map.is_block_line(row)
3760 {
3761 let start = display_map
3762 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3763 .to_point(display_map);
3764 let end = display_map
3765 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3766 .to_point(display_map);
3767 if reversed {
3768 Some(end..start)
3769 } else {
3770 Some(start..end)
3771 }
3772 } else {
3773 None
3774 }
3775 })
3776 .collect::<Vec<_>>();
3777
3778 let ranges = match columnar_state {
3779 ColumnarSelectionState::FromMouse { .. } => {
3780 let mut non_empty_ranges = selection_ranges
3781 .iter()
3782 .filter(|selection_range| selection_range.start != selection_range.end)
3783 .peekable();
3784 if non_empty_ranges.peek().is_some() {
3785 non_empty_ranges.cloned().collect()
3786 } else {
3787 selection_ranges
3788 }
3789 }
3790 _ => selection_ranges,
3791 };
3792
3793 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3794 s.select_ranges(ranges);
3795 });
3796 cx.notify();
3797 }
3798
3799 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3800 self.selections
3801 .all_adjusted(cx)
3802 .iter()
3803 .any(|selection| !selection.is_empty())
3804 }
3805
3806 pub fn has_pending_nonempty_selection(&self) -> bool {
3807 let pending_nonempty_selection = match self.selections.pending_anchor() {
3808 Some(Selection { start, end, .. }) => start != end,
3809 None => false,
3810 };
3811
3812 pending_nonempty_selection
3813 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3814 }
3815
3816 pub fn has_pending_selection(&self) -> bool {
3817 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3818 }
3819
3820 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3821 self.selection_mark_mode = false;
3822 self.selection_drag_state = SelectionDragState::None;
3823
3824 if self.clear_expanded_diff_hunks(cx) {
3825 cx.notify();
3826 return;
3827 }
3828 if self.dismiss_menus_and_popups(true, window, cx) {
3829 return;
3830 }
3831
3832 if self.mode.is_full()
3833 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3834 {
3835 return;
3836 }
3837
3838 cx.propagate();
3839 }
3840
3841 pub fn dismiss_menus_and_popups(
3842 &mut self,
3843 is_user_requested: bool,
3844 window: &mut Window,
3845 cx: &mut Context<Self>,
3846 ) -> bool {
3847 if self.take_rename(false, window, cx).is_some() {
3848 return true;
3849 }
3850
3851 if hide_hover(self, cx) {
3852 return true;
3853 }
3854
3855 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3856 return true;
3857 }
3858
3859 if self.hide_context_menu(window, cx).is_some() {
3860 return true;
3861 }
3862
3863 if self.mouse_context_menu.take().is_some() {
3864 return true;
3865 }
3866
3867 if is_user_requested && self.discard_edit_prediction(true, cx) {
3868 return true;
3869 }
3870
3871 if self.snippet_stack.pop().is_some() {
3872 return true;
3873 }
3874
3875 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3876 self.dismiss_diagnostics(cx);
3877 return true;
3878 }
3879
3880 false
3881 }
3882
3883 fn linked_editing_ranges_for(
3884 &self,
3885 selection: Range<text::Anchor>,
3886 cx: &App,
3887 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3888 if self.linked_edit_ranges.is_empty() {
3889 return None;
3890 }
3891 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3892 selection.end.buffer_id.and_then(|end_buffer_id| {
3893 if selection.start.buffer_id != Some(end_buffer_id) {
3894 return None;
3895 }
3896 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3897 let snapshot = buffer.read(cx).snapshot();
3898 self.linked_edit_ranges
3899 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3900 .map(|ranges| (ranges, snapshot, buffer))
3901 })?;
3902 use text::ToOffset as TO;
3903 // find offset from the start of current range to current cursor position
3904 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3905
3906 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3907 let start_difference = start_offset - start_byte_offset;
3908 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3909 let end_difference = end_offset - start_byte_offset;
3910 // Current range has associated linked ranges.
3911 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3912 for range in linked_ranges.iter() {
3913 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3914 let end_offset = start_offset + end_difference;
3915 let start_offset = start_offset + start_difference;
3916 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3917 continue;
3918 }
3919 if self.selections.disjoint_anchor_ranges().any(|s| {
3920 if s.start.buffer_id != selection.start.buffer_id
3921 || s.end.buffer_id != selection.end.buffer_id
3922 {
3923 return false;
3924 }
3925 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3926 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3927 }) {
3928 continue;
3929 }
3930 let start = buffer_snapshot.anchor_after(start_offset);
3931 let end = buffer_snapshot.anchor_after(end_offset);
3932 linked_edits
3933 .entry(buffer.clone())
3934 .or_default()
3935 .push(start..end);
3936 }
3937 Some(linked_edits)
3938 }
3939
3940 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3941 let text: Arc<str> = text.into();
3942
3943 if self.read_only(cx) {
3944 return;
3945 }
3946
3947 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3948
3949 let selections = self.selections.all_adjusted(cx);
3950 let mut bracket_inserted = false;
3951 let mut edits = Vec::new();
3952 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3953 let mut new_selections = Vec::with_capacity(selections.len());
3954 let mut new_autoclose_regions = Vec::new();
3955 let snapshot = self.buffer.read(cx).read(cx);
3956 let mut clear_linked_edit_ranges = false;
3957
3958 for (selection, autoclose_region) in
3959 self.selections_with_autoclose_regions(selections, &snapshot)
3960 {
3961 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3962 // Determine if the inserted text matches the opening or closing
3963 // bracket of any of this language's bracket pairs.
3964 let mut bracket_pair = None;
3965 let mut is_bracket_pair_start = false;
3966 let mut is_bracket_pair_end = false;
3967 if !text.is_empty() {
3968 let mut bracket_pair_matching_end = None;
3969 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3970 // and they are removing the character that triggered IME popup.
3971 for (pair, enabled) in scope.brackets() {
3972 if !pair.close && !pair.surround {
3973 continue;
3974 }
3975
3976 if enabled && pair.start.ends_with(text.as_ref()) {
3977 let prefix_len = pair.start.len() - text.len();
3978 let preceding_text_matches_prefix = prefix_len == 0
3979 || (selection.start.column >= (prefix_len as u32)
3980 && snapshot.contains_str_at(
3981 Point::new(
3982 selection.start.row,
3983 selection.start.column - (prefix_len as u32),
3984 ),
3985 &pair.start[..prefix_len],
3986 ));
3987 if preceding_text_matches_prefix {
3988 bracket_pair = Some(pair.clone());
3989 is_bracket_pair_start = true;
3990 break;
3991 }
3992 }
3993 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3994 {
3995 // take first bracket pair matching end, but don't break in case a later bracket
3996 // pair matches start
3997 bracket_pair_matching_end = Some(pair.clone());
3998 }
3999 }
4000 if let Some(end) = bracket_pair_matching_end
4001 && bracket_pair.is_none()
4002 {
4003 bracket_pair = Some(end);
4004 is_bracket_pair_end = true;
4005 }
4006 }
4007
4008 if let Some(bracket_pair) = bracket_pair {
4009 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4010 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4011 let auto_surround =
4012 self.use_auto_surround && snapshot_settings.use_auto_surround;
4013 if selection.is_empty() {
4014 if is_bracket_pair_start {
4015 // If the inserted text is a suffix of an opening bracket and the
4016 // selection is preceded by the rest of the opening bracket, then
4017 // insert the closing bracket.
4018 let following_text_allows_autoclose = snapshot
4019 .chars_at(selection.start)
4020 .next()
4021 .is_none_or(|c| scope.should_autoclose_before(c));
4022
4023 let preceding_text_allows_autoclose = selection.start.column == 0
4024 || snapshot
4025 .reversed_chars_at(selection.start)
4026 .next()
4027 .is_none_or(|c| {
4028 bracket_pair.start != bracket_pair.end
4029 || !snapshot
4030 .char_classifier_at(selection.start)
4031 .is_word(c)
4032 });
4033
4034 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4035 && bracket_pair.start.len() == 1
4036 {
4037 let target = bracket_pair.start.chars().next().unwrap();
4038 let current_line_count = snapshot
4039 .reversed_chars_at(selection.start)
4040 .take_while(|&c| c != '\n')
4041 .filter(|&c| c == target)
4042 .count();
4043 current_line_count % 2 == 1
4044 } else {
4045 false
4046 };
4047
4048 if autoclose
4049 && bracket_pair.close
4050 && following_text_allows_autoclose
4051 && preceding_text_allows_autoclose
4052 && !is_closing_quote
4053 {
4054 let anchor = snapshot.anchor_before(selection.end);
4055 new_selections.push((selection.map(|_| anchor), text.len()));
4056 new_autoclose_regions.push((
4057 anchor,
4058 text.len(),
4059 selection.id,
4060 bracket_pair.clone(),
4061 ));
4062 edits.push((
4063 selection.range(),
4064 format!("{}{}", text, bracket_pair.end).into(),
4065 ));
4066 bracket_inserted = true;
4067 continue;
4068 }
4069 }
4070
4071 if let Some(region) = autoclose_region {
4072 // If the selection is followed by an auto-inserted closing bracket,
4073 // then don't insert that closing bracket again; just move the selection
4074 // past the closing bracket.
4075 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4076 && text.as_ref() == region.pair.end.as_str()
4077 && snapshot.contains_str_at(region.range.end, text.as_ref());
4078 if should_skip {
4079 let anchor = snapshot.anchor_after(selection.end);
4080 new_selections
4081 .push((selection.map(|_| anchor), region.pair.end.len()));
4082 continue;
4083 }
4084 }
4085
4086 let always_treat_brackets_as_autoclosed = snapshot
4087 .language_settings_at(selection.start, cx)
4088 .always_treat_brackets_as_autoclosed;
4089 if always_treat_brackets_as_autoclosed
4090 && is_bracket_pair_end
4091 && snapshot.contains_str_at(selection.end, text.as_ref())
4092 {
4093 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4094 // and the inserted text is a closing bracket and the selection is followed
4095 // by the closing bracket then move the selection past the closing bracket.
4096 let anchor = snapshot.anchor_after(selection.end);
4097 new_selections.push((selection.map(|_| anchor), text.len()));
4098 continue;
4099 }
4100 }
4101 // If an opening bracket is 1 character long and is typed while
4102 // text is selected, then surround that text with the bracket pair.
4103 else if auto_surround
4104 && bracket_pair.surround
4105 && is_bracket_pair_start
4106 && bracket_pair.start.chars().count() == 1
4107 {
4108 edits.push((selection.start..selection.start, text.clone()));
4109 edits.push((
4110 selection.end..selection.end,
4111 bracket_pair.end.as_str().into(),
4112 ));
4113 bracket_inserted = true;
4114 new_selections.push((
4115 Selection {
4116 id: selection.id,
4117 start: snapshot.anchor_after(selection.start),
4118 end: snapshot.anchor_before(selection.end),
4119 reversed: selection.reversed,
4120 goal: selection.goal,
4121 },
4122 0,
4123 ));
4124 continue;
4125 }
4126 }
4127 }
4128
4129 if self.auto_replace_emoji_shortcode
4130 && selection.is_empty()
4131 && text.as_ref().ends_with(':')
4132 && let Some(possible_emoji_short_code) =
4133 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4134 && !possible_emoji_short_code.is_empty()
4135 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4136 {
4137 let emoji_shortcode_start = Point::new(
4138 selection.start.row,
4139 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4140 );
4141
4142 // Remove shortcode from buffer
4143 edits.push((
4144 emoji_shortcode_start..selection.start,
4145 "".to_string().into(),
4146 ));
4147 new_selections.push((
4148 Selection {
4149 id: selection.id,
4150 start: snapshot.anchor_after(emoji_shortcode_start),
4151 end: snapshot.anchor_before(selection.start),
4152 reversed: selection.reversed,
4153 goal: selection.goal,
4154 },
4155 0,
4156 ));
4157
4158 // Insert emoji
4159 let selection_start_anchor = snapshot.anchor_after(selection.start);
4160 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4161 edits.push((selection.start..selection.end, emoji.to_string().into()));
4162
4163 continue;
4164 }
4165
4166 // If not handling any auto-close operation, then just replace the selected
4167 // text with the given input and move the selection to the end of the
4168 // newly inserted text.
4169 let anchor = snapshot.anchor_after(selection.end);
4170 if !self.linked_edit_ranges.is_empty() {
4171 let start_anchor = snapshot.anchor_before(selection.start);
4172
4173 let is_word_char = text.chars().next().is_none_or(|char| {
4174 let classifier = snapshot
4175 .char_classifier_at(start_anchor.to_offset(&snapshot))
4176 .ignore_punctuation(true);
4177 classifier.is_word(char)
4178 });
4179
4180 if is_word_char {
4181 if let Some(ranges) = self
4182 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4183 {
4184 for (buffer, edits) in ranges {
4185 linked_edits
4186 .entry(buffer.clone())
4187 .or_default()
4188 .extend(edits.into_iter().map(|range| (range, text.clone())));
4189 }
4190 }
4191 } else {
4192 clear_linked_edit_ranges = true;
4193 }
4194 }
4195
4196 new_selections.push((selection.map(|_| anchor), 0));
4197 edits.push((selection.start..selection.end, text.clone()));
4198 }
4199
4200 drop(snapshot);
4201
4202 self.transact(window, cx, |this, window, cx| {
4203 if clear_linked_edit_ranges {
4204 this.linked_edit_ranges.clear();
4205 }
4206 let initial_buffer_versions =
4207 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4208
4209 this.buffer.update(cx, |buffer, cx| {
4210 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4211 });
4212 for (buffer, edits) in linked_edits {
4213 buffer.update(cx, |buffer, cx| {
4214 let snapshot = buffer.snapshot();
4215 let edits = edits
4216 .into_iter()
4217 .map(|(range, text)| {
4218 use text::ToPoint as TP;
4219 let end_point = TP::to_point(&range.end, &snapshot);
4220 let start_point = TP::to_point(&range.start, &snapshot);
4221 (start_point..end_point, text)
4222 })
4223 .sorted_by_key(|(range, _)| range.start);
4224 buffer.edit(edits, None, cx);
4225 })
4226 }
4227 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4228 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4229 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4230 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4231 .zip(new_selection_deltas)
4232 .map(|(selection, delta)| Selection {
4233 id: selection.id,
4234 start: selection.start + delta,
4235 end: selection.end + delta,
4236 reversed: selection.reversed,
4237 goal: SelectionGoal::None,
4238 })
4239 .collect::<Vec<_>>();
4240
4241 let mut i = 0;
4242 for (position, delta, selection_id, pair) in new_autoclose_regions {
4243 let position = position.to_offset(&map.buffer_snapshot) + delta;
4244 let start = map.buffer_snapshot.anchor_before(position);
4245 let end = map.buffer_snapshot.anchor_after(position);
4246 while let Some(existing_state) = this.autoclose_regions.get(i) {
4247 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4248 Ordering::Less => i += 1,
4249 Ordering::Greater => break,
4250 Ordering::Equal => {
4251 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4252 Ordering::Less => i += 1,
4253 Ordering::Equal => break,
4254 Ordering::Greater => break,
4255 }
4256 }
4257 }
4258 }
4259 this.autoclose_regions.insert(
4260 i,
4261 AutocloseRegion {
4262 selection_id,
4263 range: start..end,
4264 pair,
4265 },
4266 );
4267 }
4268
4269 let had_active_edit_prediction = this.has_active_edit_prediction();
4270 this.change_selections(
4271 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4272 window,
4273 cx,
4274 |s| s.select(new_selections),
4275 );
4276
4277 if !bracket_inserted
4278 && let Some(on_type_format_task) =
4279 this.trigger_on_type_formatting(text.to_string(), window, cx)
4280 {
4281 on_type_format_task.detach_and_log_err(cx);
4282 }
4283
4284 let editor_settings = EditorSettings::get_global(cx);
4285 if bracket_inserted
4286 && (editor_settings.auto_signature_help
4287 || editor_settings.show_signature_help_after_edits)
4288 {
4289 this.show_signature_help(&ShowSignatureHelp, window, cx);
4290 }
4291
4292 let trigger_in_words =
4293 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4294 if this.hard_wrap.is_some() {
4295 let latest: Range<Point> = this.selections.newest(cx).range();
4296 if latest.is_empty()
4297 && this
4298 .buffer()
4299 .read(cx)
4300 .snapshot(cx)
4301 .line_len(MultiBufferRow(latest.start.row))
4302 == latest.start.column
4303 {
4304 this.rewrap_impl(
4305 RewrapOptions {
4306 override_language_settings: true,
4307 preserve_existing_whitespace: true,
4308 },
4309 cx,
4310 )
4311 }
4312 }
4313 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4314 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4315 this.refresh_edit_prediction(true, false, window, cx);
4316 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4317 });
4318 }
4319
4320 fn find_possible_emoji_shortcode_at_position(
4321 snapshot: &MultiBufferSnapshot,
4322 position: Point,
4323 ) -> Option<String> {
4324 let mut chars = Vec::new();
4325 let mut found_colon = false;
4326 for char in snapshot.reversed_chars_at(position).take(100) {
4327 // Found a possible emoji shortcode in the middle of the buffer
4328 if found_colon {
4329 if char.is_whitespace() {
4330 chars.reverse();
4331 return Some(chars.iter().collect());
4332 }
4333 // If the previous character is not a whitespace, we are in the middle of a word
4334 // and we only want to complete the shortcode if the word is made up of other emojis
4335 let mut containing_word = String::new();
4336 for ch in snapshot
4337 .reversed_chars_at(position)
4338 .skip(chars.len() + 1)
4339 .take(100)
4340 {
4341 if ch.is_whitespace() {
4342 break;
4343 }
4344 containing_word.push(ch);
4345 }
4346 let containing_word = containing_word.chars().rev().collect::<String>();
4347 if util::word_consists_of_emojis(containing_word.as_str()) {
4348 chars.reverse();
4349 return Some(chars.iter().collect());
4350 }
4351 }
4352
4353 if char.is_whitespace() || !char.is_ascii() {
4354 return None;
4355 }
4356 if char == ':' {
4357 found_colon = true;
4358 } else {
4359 chars.push(char);
4360 }
4361 }
4362 // Found a possible emoji shortcode at the beginning of the buffer
4363 chars.reverse();
4364 Some(chars.iter().collect())
4365 }
4366
4367 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4368 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4369 self.transact(window, cx, |this, window, cx| {
4370 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4371 let selections = this.selections.all::<usize>(cx);
4372 let multi_buffer = this.buffer.read(cx);
4373 let buffer = multi_buffer.snapshot(cx);
4374 selections
4375 .iter()
4376 .map(|selection| {
4377 let start_point = selection.start.to_point(&buffer);
4378 let mut existing_indent =
4379 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4380 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4381 let start = selection.start;
4382 let end = selection.end;
4383 let selection_is_empty = start == end;
4384 let language_scope = buffer.language_scope_at(start);
4385 let (
4386 comment_delimiter,
4387 doc_delimiter,
4388 insert_extra_newline,
4389 indent_on_newline,
4390 indent_on_extra_newline,
4391 ) = if let Some(language) = &language_scope {
4392 let mut insert_extra_newline =
4393 insert_extra_newline_brackets(&buffer, start..end, language)
4394 || insert_extra_newline_tree_sitter(&buffer, start..end);
4395
4396 // Comment extension on newline is allowed only for cursor selections
4397 let comment_delimiter = maybe!({
4398 if !selection_is_empty {
4399 return None;
4400 }
4401
4402 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4403 return None;
4404 }
4405
4406 let delimiters = language.line_comment_prefixes();
4407 let max_len_of_delimiter =
4408 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4409 let (snapshot, range) =
4410 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4411
4412 let num_of_whitespaces = snapshot
4413 .chars_for_range(range.clone())
4414 .take_while(|c| c.is_whitespace())
4415 .count();
4416 let comment_candidate = snapshot
4417 .chars_for_range(range.clone())
4418 .skip(num_of_whitespaces)
4419 .take(max_len_of_delimiter)
4420 .collect::<String>();
4421 let (delimiter, trimmed_len) = delimiters
4422 .iter()
4423 .filter_map(|delimiter| {
4424 let prefix = delimiter.trim_end();
4425 if comment_candidate.starts_with(prefix) {
4426 Some((delimiter, prefix.len()))
4427 } else {
4428 None
4429 }
4430 })
4431 .max_by_key(|(_, len)| *len)?;
4432
4433 if let Some(BlockCommentConfig {
4434 start: block_start, ..
4435 }) = language.block_comment()
4436 {
4437 let block_start_trimmed = block_start.trim_end();
4438 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4439 let line_content = snapshot
4440 .chars_for_range(range)
4441 .skip(num_of_whitespaces)
4442 .take(block_start_trimmed.len())
4443 .collect::<String>();
4444
4445 if line_content.starts_with(block_start_trimmed) {
4446 return None;
4447 }
4448 }
4449 }
4450
4451 let cursor_is_placed_after_comment_marker =
4452 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4453 if cursor_is_placed_after_comment_marker {
4454 Some(delimiter.clone())
4455 } else {
4456 None
4457 }
4458 });
4459
4460 let mut indent_on_newline = IndentSize::spaces(0);
4461 let mut indent_on_extra_newline = IndentSize::spaces(0);
4462
4463 let doc_delimiter = maybe!({
4464 if !selection_is_empty {
4465 return None;
4466 }
4467
4468 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4469 return None;
4470 }
4471
4472 let BlockCommentConfig {
4473 start: start_tag,
4474 end: end_tag,
4475 prefix: delimiter,
4476 tab_size: len,
4477 } = language.documentation_comment()?;
4478 let is_within_block_comment = buffer
4479 .language_scope_at(start_point)
4480 .is_some_and(|scope| scope.override_name() == Some("comment"));
4481 if !is_within_block_comment {
4482 return None;
4483 }
4484
4485 let (snapshot, range) =
4486 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4487
4488 let num_of_whitespaces = snapshot
4489 .chars_for_range(range.clone())
4490 .take_while(|c| c.is_whitespace())
4491 .count();
4492
4493 // 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.
4494 let column = start_point.column;
4495 let cursor_is_after_start_tag = {
4496 let start_tag_len = start_tag.len();
4497 let start_tag_line = snapshot
4498 .chars_for_range(range.clone())
4499 .skip(num_of_whitespaces)
4500 .take(start_tag_len)
4501 .collect::<String>();
4502 if start_tag_line.starts_with(start_tag.as_ref()) {
4503 num_of_whitespaces + start_tag_len <= column as usize
4504 } else {
4505 false
4506 }
4507 };
4508
4509 let cursor_is_after_delimiter = {
4510 let delimiter_trim = delimiter.trim_end();
4511 let delimiter_line = snapshot
4512 .chars_for_range(range.clone())
4513 .skip(num_of_whitespaces)
4514 .take(delimiter_trim.len())
4515 .collect::<String>();
4516 if delimiter_line.starts_with(delimiter_trim) {
4517 num_of_whitespaces + delimiter_trim.len() <= column as usize
4518 } else {
4519 false
4520 }
4521 };
4522
4523 let cursor_is_before_end_tag_if_exists = {
4524 let mut char_position = 0u32;
4525 let mut end_tag_offset = None;
4526
4527 'outer: for chunk in snapshot.text_for_range(range) {
4528 if let Some(byte_pos) = chunk.find(&**end_tag) {
4529 let chars_before_match =
4530 chunk[..byte_pos].chars().count() as u32;
4531 end_tag_offset =
4532 Some(char_position + chars_before_match);
4533 break 'outer;
4534 }
4535 char_position += chunk.chars().count() as u32;
4536 }
4537
4538 if let Some(end_tag_offset) = end_tag_offset {
4539 let cursor_is_before_end_tag = column <= end_tag_offset;
4540 if cursor_is_after_start_tag {
4541 if cursor_is_before_end_tag {
4542 insert_extra_newline = true;
4543 }
4544 let cursor_is_at_start_of_end_tag =
4545 column == end_tag_offset;
4546 if cursor_is_at_start_of_end_tag {
4547 indent_on_extra_newline.len = *len;
4548 }
4549 }
4550 cursor_is_before_end_tag
4551 } else {
4552 true
4553 }
4554 };
4555
4556 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4557 && cursor_is_before_end_tag_if_exists
4558 {
4559 if cursor_is_after_start_tag {
4560 indent_on_newline.len = *len;
4561 }
4562 Some(delimiter.clone())
4563 } else {
4564 None
4565 }
4566 });
4567
4568 (
4569 comment_delimiter,
4570 doc_delimiter,
4571 insert_extra_newline,
4572 indent_on_newline,
4573 indent_on_extra_newline,
4574 )
4575 } else {
4576 (
4577 None,
4578 None,
4579 false,
4580 IndentSize::default(),
4581 IndentSize::default(),
4582 )
4583 };
4584
4585 let prevent_auto_indent = doc_delimiter.is_some();
4586 let delimiter = comment_delimiter.or(doc_delimiter);
4587
4588 let capacity_for_delimiter =
4589 delimiter.as_deref().map(str::len).unwrap_or_default();
4590 let mut new_text = String::with_capacity(
4591 1 + capacity_for_delimiter
4592 + existing_indent.len as usize
4593 + indent_on_newline.len as usize
4594 + indent_on_extra_newline.len as usize,
4595 );
4596 new_text.push('\n');
4597 new_text.extend(existing_indent.chars());
4598 new_text.extend(indent_on_newline.chars());
4599
4600 if let Some(delimiter) = &delimiter {
4601 new_text.push_str(delimiter);
4602 }
4603
4604 if insert_extra_newline {
4605 new_text.push('\n');
4606 new_text.extend(existing_indent.chars());
4607 new_text.extend(indent_on_extra_newline.chars());
4608 }
4609
4610 let anchor = buffer.anchor_after(end);
4611 let new_selection = selection.map(|_| anchor);
4612 (
4613 ((start..end, new_text), prevent_auto_indent),
4614 (insert_extra_newline, new_selection),
4615 )
4616 })
4617 .unzip()
4618 };
4619
4620 let mut auto_indent_edits = Vec::new();
4621 let mut edits = Vec::new();
4622 for (edit, prevent_auto_indent) in edits_with_flags {
4623 if prevent_auto_indent {
4624 edits.push(edit);
4625 } else {
4626 auto_indent_edits.push(edit);
4627 }
4628 }
4629 if !edits.is_empty() {
4630 this.edit(edits, cx);
4631 }
4632 if !auto_indent_edits.is_empty() {
4633 this.edit_with_autoindent(auto_indent_edits, cx);
4634 }
4635
4636 let buffer = this.buffer.read(cx).snapshot(cx);
4637 let new_selections = selection_info
4638 .into_iter()
4639 .map(|(extra_newline_inserted, new_selection)| {
4640 let mut cursor = new_selection.end.to_point(&buffer);
4641 if extra_newline_inserted {
4642 cursor.row -= 1;
4643 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4644 }
4645 new_selection.map(|_| cursor)
4646 })
4647 .collect();
4648
4649 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4650 this.refresh_edit_prediction(true, false, window, cx);
4651 });
4652 }
4653
4654 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4655 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4656
4657 let buffer = self.buffer.read(cx);
4658 let snapshot = buffer.snapshot(cx);
4659
4660 let mut edits = Vec::new();
4661 let mut rows = Vec::new();
4662
4663 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4664 let cursor = selection.head();
4665 let row = cursor.row;
4666
4667 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4668
4669 let newline = "\n".to_string();
4670 edits.push((start_of_line..start_of_line, newline));
4671
4672 rows.push(row + rows_inserted as u32);
4673 }
4674
4675 self.transact(window, cx, |editor, window, cx| {
4676 editor.edit(edits, cx);
4677
4678 editor.change_selections(Default::default(), window, cx, |s| {
4679 let mut index = 0;
4680 s.move_cursors_with(|map, _, _| {
4681 let row = rows[index];
4682 index += 1;
4683
4684 let point = Point::new(row, 0);
4685 let boundary = map.next_line_boundary(point).1;
4686 let clipped = map.clip_point(boundary, Bias::Left);
4687
4688 (clipped, SelectionGoal::None)
4689 });
4690 });
4691
4692 let mut indent_edits = Vec::new();
4693 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4694 for row in rows {
4695 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4696 for (row, indent) in indents {
4697 if indent.len == 0 {
4698 continue;
4699 }
4700
4701 let text = match indent.kind {
4702 IndentKind::Space => " ".repeat(indent.len as usize),
4703 IndentKind::Tab => "\t".repeat(indent.len as usize),
4704 };
4705 let point = Point::new(row.0, 0);
4706 indent_edits.push((point..point, text));
4707 }
4708 }
4709 editor.edit(indent_edits, cx);
4710 });
4711 }
4712
4713 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4714 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4715
4716 let buffer = self.buffer.read(cx);
4717 let snapshot = buffer.snapshot(cx);
4718
4719 let mut edits = Vec::new();
4720 let mut rows = Vec::new();
4721 let mut rows_inserted = 0;
4722
4723 for selection in self.selections.all_adjusted(cx) {
4724 let cursor = selection.head();
4725 let row = cursor.row;
4726
4727 let point = Point::new(row + 1, 0);
4728 let start_of_line = snapshot.clip_point(point, Bias::Left);
4729
4730 let newline = "\n".to_string();
4731 edits.push((start_of_line..start_of_line, newline));
4732
4733 rows_inserted += 1;
4734 rows.push(row + rows_inserted);
4735 }
4736
4737 self.transact(window, cx, |editor, window, cx| {
4738 editor.edit(edits, cx);
4739
4740 editor.change_selections(Default::default(), window, cx, |s| {
4741 let mut index = 0;
4742 s.move_cursors_with(|map, _, _| {
4743 let row = rows[index];
4744 index += 1;
4745
4746 let point = Point::new(row, 0);
4747 let boundary = map.next_line_boundary(point).1;
4748 let clipped = map.clip_point(boundary, Bias::Left);
4749
4750 (clipped, SelectionGoal::None)
4751 });
4752 });
4753
4754 let mut indent_edits = Vec::new();
4755 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4756 for row in rows {
4757 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4758 for (row, indent) in indents {
4759 if indent.len == 0 {
4760 continue;
4761 }
4762
4763 let text = match indent.kind {
4764 IndentKind::Space => " ".repeat(indent.len as usize),
4765 IndentKind::Tab => "\t".repeat(indent.len as usize),
4766 };
4767 let point = Point::new(row.0, 0);
4768 indent_edits.push((point..point, text));
4769 }
4770 }
4771 editor.edit(indent_edits, cx);
4772 });
4773 }
4774
4775 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4776 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4777 original_indent_columns: Vec::new(),
4778 });
4779 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4780 }
4781
4782 fn insert_with_autoindent_mode(
4783 &mut self,
4784 text: &str,
4785 autoindent_mode: Option<AutoindentMode>,
4786 window: &mut Window,
4787 cx: &mut Context<Self>,
4788 ) {
4789 if self.read_only(cx) {
4790 return;
4791 }
4792
4793 let text: Arc<str> = text.into();
4794 self.transact(window, cx, |this, window, cx| {
4795 let old_selections = this.selections.all_adjusted(cx);
4796 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4797 let anchors = {
4798 let snapshot = buffer.read(cx);
4799 old_selections
4800 .iter()
4801 .map(|s| {
4802 let anchor = snapshot.anchor_after(s.head());
4803 s.map(|_| anchor)
4804 })
4805 .collect::<Vec<_>>()
4806 };
4807 buffer.edit(
4808 old_selections
4809 .iter()
4810 .map(|s| (s.start..s.end, text.clone())),
4811 autoindent_mode,
4812 cx,
4813 );
4814 anchors
4815 });
4816
4817 this.change_selections(Default::default(), window, cx, |s| {
4818 s.select_anchors(selection_anchors);
4819 });
4820
4821 cx.notify();
4822 });
4823 }
4824
4825 fn trigger_completion_on_input(
4826 &mut self,
4827 text: &str,
4828 trigger_in_words: bool,
4829 window: &mut Window,
4830 cx: &mut Context<Self>,
4831 ) {
4832 let completions_source = self
4833 .context_menu
4834 .borrow()
4835 .as_ref()
4836 .and_then(|menu| match menu {
4837 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4838 CodeContextMenu::CodeActions(_) => None,
4839 });
4840
4841 match completions_source {
4842 Some(CompletionsMenuSource::Words) => {
4843 self.show_word_completions(&ShowWordCompletions, window, cx)
4844 }
4845 Some(CompletionsMenuSource::Normal)
4846 | Some(CompletionsMenuSource::SnippetChoices)
4847 | None
4848 if self.is_completion_trigger(
4849 text,
4850 trigger_in_words,
4851 completions_source.is_some(),
4852 cx,
4853 ) =>
4854 {
4855 self.show_completions(
4856 &ShowCompletions {
4857 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4858 },
4859 window,
4860 cx,
4861 )
4862 }
4863 _ => {
4864 self.hide_context_menu(window, cx);
4865 }
4866 }
4867 }
4868
4869 fn is_completion_trigger(
4870 &self,
4871 text: &str,
4872 trigger_in_words: bool,
4873 menu_is_open: bool,
4874 cx: &mut Context<Self>,
4875 ) -> bool {
4876 let position = self.selections.newest_anchor().head();
4877 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4878 return false;
4879 };
4880
4881 if let Some(completion_provider) = &self.completion_provider {
4882 completion_provider.is_completion_trigger(
4883 &buffer,
4884 position.text_anchor,
4885 text,
4886 trigger_in_words,
4887 menu_is_open,
4888 cx,
4889 )
4890 } else {
4891 false
4892 }
4893 }
4894
4895 /// If any empty selections is touching the start of its innermost containing autoclose
4896 /// region, expand it to select the brackets.
4897 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4898 let selections = self.selections.all::<usize>(cx);
4899 let buffer = self.buffer.read(cx).read(cx);
4900 let new_selections = self
4901 .selections_with_autoclose_regions(selections, &buffer)
4902 .map(|(mut selection, region)| {
4903 if !selection.is_empty() {
4904 return selection;
4905 }
4906
4907 if let Some(region) = region {
4908 let mut range = region.range.to_offset(&buffer);
4909 if selection.start == range.start && range.start >= region.pair.start.len() {
4910 range.start -= region.pair.start.len();
4911 if buffer.contains_str_at(range.start, ®ion.pair.start)
4912 && buffer.contains_str_at(range.end, ®ion.pair.end)
4913 {
4914 range.end += region.pair.end.len();
4915 selection.start = range.start;
4916 selection.end = range.end;
4917
4918 return selection;
4919 }
4920 }
4921 }
4922
4923 let always_treat_brackets_as_autoclosed = buffer
4924 .language_settings_at(selection.start, cx)
4925 .always_treat_brackets_as_autoclosed;
4926
4927 if !always_treat_brackets_as_autoclosed {
4928 return selection;
4929 }
4930
4931 if let Some(scope) = buffer.language_scope_at(selection.start) {
4932 for (pair, enabled) in scope.brackets() {
4933 if !enabled || !pair.close {
4934 continue;
4935 }
4936
4937 if buffer.contains_str_at(selection.start, &pair.end) {
4938 let pair_start_len = pair.start.len();
4939 if buffer.contains_str_at(
4940 selection.start.saturating_sub(pair_start_len),
4941 &pair.start,
4942 ) {
4943 selection.start -= pair_start_len;
4944 selection.end += pair.end.len();
4945
4946 return selection;
4947 }
4948 }
4949 }
4950 }
4951
4952 selection
4953 })
4954 .collect();
4955
4956 drop(buffer);
4957 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4958 selections.select(new_selections)
4959 });
4960 }
4961
4962 /// Iterate the given selections, and for each one, find the smallest surrounding
4963 /// autoclose region. This uses the ordering of the selections and the autoclose
4964 /// regions to avoid repeated comparisons.
4965 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4966 &'a self,
4967 selections: impl IntoIterator<Item = Selection<D>>,
4968 buffer: &'a MultiBufferSnapshot,
4969 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4970 let mut i = 0;
4971 let mut regions = self.autoclose_regions.as_slice();
4972 selections.into_iter().map(move |selection| {
4973 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4974
4975 let mut enclosing = None;
4976 while let Some(pair_state) = regions.get(i) {
4977 if pair_state.range.end.to_offset(buffer) < range.start {
4978 regions = ®ions[i + 1..];
4979 i = 0;
4980 } else if pair_state.range.start.to_offset(buffer) > range.end {
4981 break;
4982 } else {
4983 if pair_state.selection_id == selection.id {
4984 enclosing = Some(pair_state);
4985 }
4986 i += 1;
4987 }
4988 }
4989
4990 (selection, enclosing)
4991 })
4992 }
4993
4994 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4995 fn invalidate_autoclose_regions(
4996 &mut self,
4997 mut selections: &[Selection<Anchor>],
4998 buffer: &MultiBufferSnapshot,
4999 ) {
5000 self.autoclose_regions.retain(|state| {
5001 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5002 return false;
5003 }
5004
5005 let mut i = 0;
5006 while let Some(selection) = selections.get(i) {
5007 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5008 selections = &selections[1..];
5009 continue;
5010 }
5011 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5012 break;
5013 }
5014 if selection.id == state.selection_id {
5015 return true;
5016 } else {
5017 i += 1;
5018 }
5019 }
5020 false
5021 });
5022 }
5023
5024 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5025 let offset = position.to_offset(buffer);
5026 let (word_range, kind) = buffer.surrounding_word(offset, true);
5027 if offset > word_range.start && kind == Some(CharKind::Word) {
5028 Some(
5029 buffer
5030 .text_for_range(word_range.start..offset)
5031 .collect::<String>(),
5032 )
5033 } else {
5034 None
5035 }
5036 }
5037
5038 pub fn toggle_inline_values(
5039 &mut self,
5040 _: &ToggleInlineValues,
5041 _: &mut Window,
5042 cx: &mut Context<Self>,
5043 ) {
5044 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5045
5046 self.refresh_inline_values(cx);
5047 }
5048
5049 pub fn toggle_inlay_hints(
5050 &mut self,
5051 _: &ToggleInlayHints,
5052 _: &mut Window,
5053 cx: &mut Context<Self>,
5054 ) {
5055 self.refresh_inlay_hints(
5056 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5057 cx,
5058 );
5059 }
5060
5061 pub fn inlay_hints_enabled(&self) -> bool {
5062 self.inlay_hint_cache.enabled
5063 }
5064
5065 pub fn inline_values_enabled(&self) -> bool {
5066 self.inline_value_cache.enabled
5067 }
5068
5069 #[cfg(any(test, feature = "test-support"))]
5070 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5071 self.display_map
5072 .read(cx)
5073 .current_inlays()
5074 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5075 .cloned()
5076 .collect()
5077 }
5078
5079 #[cfg(any(test, feature = "test-support"))]
5080 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5081 self.display_map
5082 .read(cx)
5083 .current_inlays()
5084 .cloned()
5085 .collect()
5086 }
5087
5088 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5089 if self.semantics_provider.is_none() || !self.mode.is_full() {
5090 return;
5091 }
5092
5093 let reason_description = reason.description();
5094 let ignore_debounce = matches!(
5095 reason,
5096 InlayHintRefreshReason::SettingsChange(_)
5097 | InlayHintRefreshReason::Toggle(_)
5098 | InlayHintRefreshReason::ExcerptsRemoved(_)
5099 | InlayHintRefreshReason::ModifiersChanged(_)
5100 );
5101 let (invalidate_cache, required_languages) = match reason {
5102 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5103 match self.inlay_hint_cache.modifiers_override(enabled) {
5104 Some(enabled) => {
5105 if enabled {
5106 (InvalidationStrategy::RefreshRequested, None)
5107 } else {
5108 self.splice_inlays(
5109 &self
5110 .visible_inlay_hints(cx)
5111 .iter()
5112 .map(|inlay| inlay.id)
5113 .collect::<Vec<InlayId>>(),
5114 Vec::new(),
5115 cx,
5116 );
5117 return;
5118 }
5119 }
5120 None => return,
5121 }
5122 }
5123 InlayHintRefreshReason::Toggle(enabled) => {
5124 if self.inlay_hint_cache.toggle(enabled) {
5125 if enabled {
5126 (InvalidationStrategy::RefreshRequested, None)
5127 } else {
5128 self.splice_inlays(
5129 &self
5130 .visible_inlay_hints(cx)
5131 .iter()
5132 .map(|inlay| inlay.id)
5133 .collect::<Vec<InlayId>>(),
5134 Vec::new(),
5135 cx,
5136 );
5137 return;
5138 }
5139 } else {
5140 return;
5141 }
5142 }
5143 InlayHintRefreshReason::SettingsChange(new_settings) => {
5144 match self.inlay_hint_cache.update_settings(
5145 &self.buffer,
5146 new_settings,
5147 self.visible_inlay_hints(cx),
5148 cx,
5149 ) {
5150 ControlFlow::Break(Some(InlaySplice {
5151 to_remove,
5152 to_insert,
5153 })) => {
5154 self.splice_inlays(&to_remove, to_insert, cx);
5155 return;
5156 }
5157 ControlFlow::Break(None) => return,
5158 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5159 }
5160 }
5161 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5162 if let Some(InlaySplice {
5163 to_remove,
5164 to_insert,
5165 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5166 {
5167 self.splice_inlays(&to_remove, to_insert, cx);
5168 }
5169 self.display_map.update(cx, |display_map, _| {
5170 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5171 });
5172 return;
5173 }
5174 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5175 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5176 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5177 }
5178 InlayHintRefreshReason::RefreshRequested => {
5179 (InvalidationStrategy::RefreshRequested, None)
5180 }
5181 };
5182
5183 if let Some(InlaySplice {
5184 to_remove,
5185 to_insert,
5186 }) = self.inlay_hint_cache.spawn_hint_refresh(
5187 reason_description,
5188 self.visible_excerpts(required_languages.as_ref(), cx),
5189 invalidate_cache,
5190 ignore_debounce,
5191 cx,
5192 ) {
5193 self.splice_inlays(&to_remove, to_insert, cx);
5194 }
5195 }
5196
5197 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5198 self.display_map
5199 .read(cx)
5200 .current_inlays()
5201 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5202 .cloned()
5203 .collect()
5204 }
5205
5206 pub fn visible_excerpts(
5207 &self,
5208 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5209 cx: &mut Context<Editor>,
5210 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5211 let Some(project) = self.project() else {
5212 return HashMap::default();
5213 };
5214 let project = project.read(cx);
5215 let multi_buffer = self.buffer().read(cx);
5216 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5217 let multi_buffer_visible_start = self
5218 .scroll_manager
5219 .anchor()
5220 .anchor
5221 .to_point(&multi_buffer_snapshot);
5222 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5223 multi_buffer_visible_start
5224 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5225 Bias::Left,
5226 );
5227 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5228 multi_buffer_snapshot
5229 .range_to_buffer_ranges(multi_buffer_visible_range)
5230 .into_iter()
5231 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5232 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5233 let buffer_file = project::File::from_dyn(buffer.file())?;
5234 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5235 let worktree_entry = buffer_worktree
5236 .read(cx)
5237 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5238 if worktree_entry.is_ignored {
5239 return None;
5240 }
5241
5242 let language = buffer.language()?;
5243 if let Some(restrict_to_languages) = restrict_to_languages
5244 && !restrict_to_languages.contains(language)
5245 {
5246 return None;
5247 }
5248 Some((
5249 excerpt_id,
5250 (
5251 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5252 buffer.version().clone(),
5253 excerpt_visible_range,
5254 ),
5255 ))
5256 })
5257 .collect()
5258 }
5259
5260 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5261 TextLayoutDetails {
5262 text_system: window.text_system().clone(),
5263 editor_style: self.style.clone().unwrap(),
5264 rem_size: window.rem_size(),
5265 scroll_anchor: self.scroll_manager.anchor(),
5266 visible_rows: self.visible_line_count(),
5267 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5268 }
5269 }
5270
5271 pub fn splice_inlays(
5272 &self,
5273 to_remove: &[InlayId],
5274 to_insert: Vec<Inlay>,
5275 cx: &mut Context<Self>,
5276 ) {
5277 self.display_map.update(cx, |display_map, cx| {
5278 display_map.splice_inlays(to_remove, to_insert, cx)
5279 });
5280 cx.notify();
5281 }
5282
5283 fn trigger_on_type_formatting(
5284 &self,
5285 input: String,
5286 window: &mut Window,
5287 cx: &mut Context<Self>,
5288 ) -> Option<Task<Result<()>>> {
5289 if input.len() != 1 {
5290 return None;
5291 }
5292
5293 let project = self.project()?;
5294 let position = self.selections.newest_anchor().head();
5295 let (buffer, buffer_position) = self
5296 .buffer
5297 .read(cx)
5298 .text_anchor_for_position(position, cx)?;
5299
5300 let settings = language_settings::language_settings(
5301 buffer
5302 .read(cx)
5303 .language_at(buffer_position)
5304 .map(|l| l.name()),
5305 buffer.read(cx).file(),
5306 cx,
5307 );
5308 if !settings.use_on_type_format {
5309 return None;
5310 }
5311
5312 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5313 // hence we do LSP request & edit on host side only — add formats to host's history.
5314 let push_to_lsp_host_history = true;
5315 // If this is not the host, append its history with new edits.
5316 let push_to_client_history = project.read(cx).is_via_collab();
5317
5318 let on_type_formatting = project.update(cx, |project, cx| {
5319 project.on_type_format(
5320 buffer.clone(),
5321 buffer_position,
5322 input,
5323 push_to_lsp_host_history,
5324 cx,
5325 )
5326 });
5327 Some(cx.spawn_in(window, async move |editor, cx| {
5328 if let Some(transaction) = on_type_formatting.await? {
5329 if push_to_client_history {
5330 buffer
5331 .update(cx, |buffer, _| {
5332 buffer.push_transaction(transaction, Instant::now());
5333 buffer.finalize_last_transaction();
5334 })
5335 .ok();
5336 }
5337 editor.update(cx, |editor, cx| {
5338 editor.refresh_document_highlights(cx);
5339 })?;
5340 }
5341 Ok(())
5342 }))
5343 }
5344
5345 pub fn show_word_completions(
5346 &mut self,
5347 _: &ShowWordCompletions,
5348 window: &mut Window,
5349 cx: &mut Context<Self>,
5350 ) {
5351 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5352 }
5353
5354 pub fn show_completions(
5355 &mut self,
5356 options: &ShowCompletions,
5357 window: &mut Window,
5358 cx: &mut Context<Self>,
5359 ) {
5360 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5361 }
5362
5363 fn open_or_update_completions_menu(
5364 &mut self,
5365 requested_source: Option<CompletionsMenuSource>,
5366 trigger: Option<&str>,
5367 window: &mut Window,
5368 cx: &mut Context<Self>,
5369 ) {
5370 if self.pending_rename.is_some() {
5371 return;
5372 }
5373
5374 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5375
5376 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5377 // inserted and selected. To handle that case, the start of the selection is used so that
5378 // the menu starts with all choices.
5379 let position = self
5380 .selections
5381 .newest_anchor()
5382 .start
5383 .bias_right(&multibuffer_snapshot);
5384 if position.diff_base_anchor.is_some() {
5385 return;
5386 }
5387 let (buffer, buffer_position) =
5388 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5389 output
5390 } else {
5391 return;
5392 };
5393 let buffer_snapshot = buffer.read(cx).snapshot();
5394
5395 let query: Option<Arc<String>> =
5396 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5397
5398 drop(multibuffer_snapshot);
5399
5400 let provider = match requested_source {
5401 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5402 Some(CompletionsMenuSource::Words) => None,
5403 Some(CompletionsMenuSource::SnippetChoices) => {
5404 log::error!("bug: SnippetChoices requested_source is not handled");
5405 None
5406 }
5407 };
5408
5409 let sort_completions = provider
5410 .as_ref()
5411 .is_some_and(|provider| provider.sort_completions());
5412
5413 let filter_completions = provider
5414 .as_ref()
5415 .is_none_or(|provider| provider.filter_completions());
5416
5417 let trigger_kind = match trigger {
5418 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5419 CompletionTriggerKind::TRIGGER_CHARACTER
5420 }
5421 _ => CompletionTriggerKind::INVOKED,
5422 };
5423 let completion_context = CompletionContext {
5424 trigger_character: trigger.and_then(|trigger| {
5425 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5426 Some(String::from(trigger))
5427 } else {
5428 None
5429 }
5430 }),
5431 trigger_kind,
5432 };
5433
5434 // Hide the current completions menu when a trigger char is typed. Without this, cached
5435 // completions from before the trigger char may be reused (#32774). Snippet choices could
5436 // involve trigger chars, so this is skipped in that case.
5437 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5438 {
5439 let menu_is_open = matches!(
5440 self.context_menu.borrow().as_ref(),
5441 Some(CodeContextMenu::Completions(_))
5442 );
5443 if menu_is_open {
5444 self.hide_context_menu(window, cx);
5445 }
5446 }
5447
5448 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5449 if filter_completions {
5450 menu.filter(query.clone(), provider.clone(), window, cx);
5451 }
5452 // When `is_incomplete` is false, no need to re-query completions when the current query
5453 // is a suffix of the initial query.
5454 if !menu.is_incomplete {
5455 // If the new query is a suffix of the old query (typing more characters) and
5456 // the previous result was complete, the existing completions can be filtered.
5457 //
5458 // Note that this is always true for snippet completions.
5459 let query_matches = match (&menu.initial_query, &query) {
5460 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5461 (None, _) => true,
5462 _ => false,
5463 };
5464 if query_matches {
5465 let position_matches = if menu.initial_position == position {
5466 true
5467 } else {
5468 let snapshot = self.buffer.read(cx).read(cx);
5469 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5470 };
5471 if position_matches {
5472 return;
5473 }
5474 }
5475 }
5476 };
5477
5478 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5479 buffer_snapshot.surrounding_word(buffer_position, false)
5480 {
5481 let word_to_exclude = buffer_snapshot
5482 .text_for_range(word_range.clone())
5483 .collect::<String>();
5484 (
5485 buffer_snapshot.anchor_before(word_range.start)
5486 ..buffer_snapshot.anchor_after(buffer_position),
5487 Some(word_to_exclude),
5488 )
5489 } else {
5490 (buffer_position..buffer_position, None)
5491 };
5492
5493 let language = buffer_snapshot
5494 .language_at(buffer_position)
5495 .map(|language| language.name());
5496
5497 let completion_settings =
5498 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5499
5500 let show_completion_documentation = buffer_snapshot
5501 .settings_at(buffer_position, cx)
5502 .show_completion_documentation;
5503
5504 // The document can be large, so stay in reasonable bounds when searching for words,
5505 // otherwise completion pop-up might be slow to appear.
5506 const WORD_LOOKUP_ROWS: u32 = 5_000;
5507 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5508 let min_word_search = buffer_snapshot.clip_point(
5509 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5510 Bias::Left,
5511 );
5512 let max_word_search = buffer_snapshot.clip_point(
5513 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5514 Bias::Right,
5515 );
5516 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5517 ..buffer_snapshot.point_to_offset(max_word_search);
5518
5519 let skip_digits = query
5520 .as_ref()
5521 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5522
5523 let (mut words, provider_responses) = match &provider {
5524 Some(provider) => {
5525 let provider_responses = provider.completions(
5526 position.excerpt_id,
5527 &buffer,
5528 buffer_position,
5529 completion_context,
5530 window,
5531 cx,
5532 );
5533
5534 let words = match completion_settings.words {
5535 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5536 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5537 .background_spawn(async move {
5538 buffer_snapshot.words_in_range(WordsQuery {
5539 fuzzy_contents: None,
5540 range: word_search_range,
5541 skip_digits,
5542 })
5543 }),
5544 };
5545
5546 (words, provider_responses)
5547 }
5548 None => (
5549 cx.background_spawn(async move {
5550 buffer_snapshot.words_in_range(WordsQuery {
5551 fuzzy_contents: None,
5552 range: word_search_range,
5553 skip_digits,
5554 })
5555 }),
5556 Task::ready(Ok(Vec::new())),
5557 ),
5558 };
5559
5560 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5561
5562 let id = post_inc(&mut self.next_completion_id);
5563 let task = cx.spawn_in(window, async move |editor, cx| {
5564 let Ok(()) = editor.update(cx, |this, _| {
5565 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5566 }) else {
5567 return;
5568 };
5569
5570 // TODO: Ideally completions from different sources would be selectively re-queried, so
5571 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5572 let mut completions = Vec::new();
5573 let mut is_incomplete = false;
5574 if let Some(provider_responses) = provider_responses.await.log_err()
5575 && !provider_responses.is_empty()
5576 {
5577 for response in provider_responses {
5578 completions.extend(response.completions);
5579 is_incomplete = is_incomplete || response.is_incomplete;
5580 }
5581 if completion_settings.words == WordsCompletionMode::Fallback {
5582 words = Task::ready(BTreeMap::default());
5583 }
5584 }
5585
5586 let mut words = words.await;
5587 if let Some(word_to_exclude) = &word_to_exclude {
5588 words.remove(word_to_exclude);
5589 }
5590 for lsp_completion in &completions {
5591 words.remove(&lsp_completion.new_text);
5592 }
5593 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5594 replace_range: word_replace_range.clone(),
5595 new_text: word.clone(),
5596 label: CodeLabel::plain(word, None),
5597 icon_path: None,
5598 documentation: None,
5599 source: CompletionSource::BufferWord {
5600 word_range,
5601 resolved: false,
5602 },
5603 insert_text_mode: Some(InsertTextMode::AS_IS),
5604 confirm: None,
5605 }));
5606
5607 let menu = if completions.is_empty() {
5608 None
5609 } else {
5610 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5611 let languages = editor
5612 .workspace
5613 .as_ref()
5614 .and_then(|(workspace, _)| workspace.upgrade())
5615 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5616 let menu = CompletionsMenu::new(
5617 id,
5618 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5619 sort_completions,
5620 show_completion_documentation,
5621 position,
5622 query.clone(),
5623 is_incomplete,
5624 buffer.clone(),
5625 completions.into(),
5626 snippet_sort_order,
5627 languages,
5628 language,
5629 cx,
5630 );
5631
5632 let query = if filter_completions { query } else { None };
5633 let matches_task = if let Some(query) = query {
5634 menu.do_async_filtering(query, cx)
5635 } else {
5636 Task::ready(menu.unfiltered_matches())
5637 };
5638 (menu, matches_task)
5639 }) else {
5640 return;
5641 };
5642
5643 let matches = matches_task.await;
5644
5645 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5646 // Newer menu already set, so exit.
5647 if let Some(CodeContextMenu::Completions(prev_menu)) =
5648 editor.context_menu.borrow().as_ref()
5649 && prev_menu.id > id
5650 {
5651 return;
5652 };
5653
5654 // Only valid to take prev_menu because it the new menu is immediately set
5655 // below, or the menu is hidden.
5656 if let Some(CodeContextMenu::Completions(prev_menu)) =
5657 editor.context_menu.borrow_mut().take()
5658 {
5659 let position_matches =
5660 if prev_menu.initial_position == menu.initial_position {
5661 true
5662 } else {
5663 let snapshot = editor.buffer.read(cx).read(cx);
5664 prev_menu.initial_position.to_offset(&snapshot)
5665 == menu.initial_position.to_offset(&snapshot)
5666 };
5667 if position_matches {
5668 // Preserve markdown cache before `set_filter_results` because it will
5669 // try to populate the documentation cache.
5670 menu.preserve_markdown_cache(prev_menu);
5671 }
5672 };
5673
5674 menu.set_filter_results(matches, provider, window, cx);
5675 }) else {
5676 return;
5677 };
5678
5679 menu.visible().then_some(menu)
5680 };
5681
5682 editor
5683 .update_in(cx, |editor, window, cx| {
5684 if editor.focus_handle.is_focused(window)
5685 && let Some(menu) = menu
5686 {
5687 *editor.context_menu.borrow_mut() =
5688 Some(CodeContextMenu::Completions(menu));
5689
5690 crate::hover_popover::hide_hover(editor, cx);
5691 if editor.show_edit_predictions_in_menu() {
5692 editor.update_visible_edit_prediction(window, cx);
5693 } else {
5694 editor.discard_edit_prediction(false, cx);
5695 }
5696
5697 cx.notify();
5698 return;
5699 }
5700
5701 if editor.completion_tasks.len() <= 1 {
5702 // If there are no more completion tasks and the last menu was empty, we should hide it.
5703 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5704 // If it was already hidden and we don't show edit predictions in the menu,
5705 // we should also show the edit prediction when available.
5706 if was_hidden && editor.show_edit_predictions_in_menu() {
5707 editor.update_visible_edit_prediction(window, cx);
5708 }
5709 }
5710 })
5711 .ok();
5712 });
5713
5714 self.completion_tasks.push((id, task));
5715 }
5716
5717 #[cfg(feature = "test-support")]
5718 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5719 let menu = self.context_menu.borrow();
5720 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5721 let completions = menu.completions.borrow();
5722 Some(completions.to_vec())
5723 } else {
5724 None
5725 }
5726 }
5727
5728 pub fn with_completions_menu_matching_id<R>(
5729 &self,
5730 id: CompletionId,
5731 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5732 ) -> R {
5733 let mut context_menu = self.context_menu.borrow_mut();
5734 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5735 return f(None);
5736 };
5737 if completions_menu.id != id {
5738 return f(None);
5739 }
5740 f(Some(completions_menu))
5741 }
5742
5743 pub fn confirm_completion(
5744 &mut self,
5745 action: &ConfirmCompletion,
5746 window: &mut Window,
5747 cx: &mut Context<Self>,
5748 ) -> Option<Task<Result<()>>> {
5749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5750 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5751 }
5752
5753 pub fn confirm_completion_insert(
5754 &mut self,
5755 _: &ConfirmCompletionInsert,
5756 window: &mut Window,
5757 cx: &mut Context<Self>,
5758 ) -> Option<Task<Result<()>>> {
5759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5760 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5761 }
5762
5763 pub fn confirm_completion_replace(
5764 &mut self,
5765 _: &ConfirmCompletionReplace,
5766 window: &mut Window,
5767 cx: &mut Context<Self>,
5768 ) -> Option<Task<Result<()>>> {
5769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5770 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5771 }
5772
5773 pub fn compose_completion(
5774 &mut self,
5775 action: &ComposeCompletion,
5776 window: &mut Window,
5777 cx: &mut Context<Self>,
5778 ) -> Option<Task<Result<()>>> {
5779 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5780 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5781 }
5782
5783 fn do_completion(
5784 &mut self,
5785 item_ix: Option<usize>,
5786 intent: CompletionIntent,
5787 window: &mut Window,
5788 cx: &mut Context<Editor>,
5789 ) -> Option<Task<Result<()>>> {
5790 use language::ToOffset as _;
5791
5792 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5793 else {
5794 return None;
5795 };
5796
5797 let candidate_id = {
5798 let entries = completions_menu.entries.borrow();
5799 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5800 if self.show_edit_predictions_in_menu() {
5801 self.discard_edit_prediction(true, cx);
5802 }
5803 mat.candidate_id
5804 };
5805
5806 let completion = completions_menu
5807 .completions
5808 .borrow()
5809 .get(candidate_id)?
5810 .clone();
5811 cx.stop_propagation();
5812
5813 let buffer_handle = completions_menu.buffer.clone();
5814
5815 let CompletionEdit {
5816 new_text,
5817 snippet,
5818 replace_range,
5819 } = process_completion_for_edit(
5820 &completion,
5821 intent,
5822 &buffer_handle,
5823 &completions_menu.initial_position.text_anchor,
5824 cx,
5825 );
5826
5827 let buffer = buffer_handle.read(cx);
5828 let snapshot = self.buffer.read(cx).snapshot(cx);
5829 let newest_anchor = self.selections.newest_anchor();
5830 let replace_range_multibuffer = {
5831 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5832 let multibuffer_anchor = snapshot
5833 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5834 .unwrap()
5835 ..snapshot
5836 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5837 .unwrap();
5838 multibuffer_anchor.start.to_offset(&snapshot)
5839 ..multibuffer_anchor.end.to_offset(&snapshot)
5840 };
5841 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5842 return None;
5843 }
5844
5845 let old_text = buffer
5846 .text_for_range(replace_range.clone())
5847 .collect::<String>();
5848 let lookbehind = newest_anchor
5849 .start
5850 .text_anchor
5851 .to_offset(buffer)
5852 .saturating_sub(replace_range.start);
5853 let lookahead = replace_range
5854 .end
5855 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5856 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5857 let suffix = &old_text[lookbehind.min(old_text.len())..];
5858
5859 let selections = self.selections.all::<usize>(cx);
5860 let mut ranges = Vec::new();
5861 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5862
5863 for selection in &selections {
5864 let range = if selection.id == newest_anchor.id {
5865 replace_range_multibuffer.clone()
5866 } else {
5867 let mut range = selection.range();
5868
5869 // if prefix is present, don't duplicate it
5870 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5871 range.start = range.start.saturating_sub(lookbehind);
5872
5873 // if suffix is also present, mimic the newest cursor and replace it
5874 if selection.id != newest_anchor.id
5875 && snapshot.contains_str_at(range.end, suffix)
5876 {
5877 range.end += lookahead;
5878 }
5879 }
5880 range
5881 };
5882
5883 ranges.push(range.clone());
5884
5885 if !self.linked_edit_ranges.is_empty() {
5886 let start_anchor = snapshot.anchor_before(range.start);
5887 let end_anchor = snapshot.anchor_after(range.end);
5888 if let Some(ranges) = self
5889 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5890 {
5891 for (buffer, edits) in ranges {
5892 linked_edits
5893 .entry(buffer.clone())
5894 .or_default()
5895 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5896 }
5897 }
5898 }
5899 }
5900
5901 let common_prefix_len = old_text
5902 .chars()
5903 .zip(new_text.chars())
5904 .take_while(|(a, b)| a == b)
5905 .map(|(a, _)| a.len_utf8())
5906 .sum::<usize>();
5907
5908 cx.emit(EditorEvent::InputHandled {
5909 utf16_range_to_replace: None,
5910 text: new_text[common_prefix_len..].into(),
5911 });
5912
5913 self.transact(window, cx, |editor, window, cx| {
5914 if let Some(mut snippet) = snippet {
5915 snippet.text = new_text.to_string();
5916 editor
5917 .insert_snippet(&ranges, snippet, window, cx)
5918 .log_err();
5919 } else {
5920 editor.buffer.update(cx, |multi_buffer, cx| {
5921 let auto_indent = match completion.insert_text_mode {
5922 Some(InsertTextMode::AS_IS) => None,
5923 _ => editor.autoindent_mode.clone(),
5924 };
5925 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5926 multi_buffer.edit(edits, auto_indent, cx);
5927 });
5928 }
5929 for (buffer, edits) in linked_edits {
5930 buffer.update(cx, |buffer, cx| {
5931 let snapshot = buffer.snapshot();
5932 let edits = edits
5933 .into_iter()
5934 .map(|(range, text)| {
5935 use text::ToPoint as TP;
5936 let end_point = TP::to_point(&range.end, &snapshot);
5937 let start_point = TP::to_point(&range.start, &snapshot);
5938 (start_point..end_point, text)
5939 })
5940 .sorted_by_key(|(range, _)| range.start);
5941 buffer.edit(edits, None, cx);
5942 })
5943 }
5944
5945 editor.refresh_edit_prediction(true, false, window, cx);
5946 });
5947 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5948
5949 let show_new_completions_on_confirm = completion
5950 .confirm
5951 .as_ref()
5952 .is_some_and(|confirm| confirm(intent, window, cx));
5953 if show_new_completions_on_confirm {
5954 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5955 }
5956
5957 let provider = self.completion_provider.as_ref()?;
5958 drop(completion);
5959 let apply_edits = provider.apply_additional_edits_for_completion(
5960 buffer_handle,
5961 completions_menu.completions.clone(),
5962 candidate_id,
5963 true,
5964 cx,
5965 );
5966
5967 let editor_settings = EditorSettings::get_global(cx);
5968 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5969 // After the code completion is finished, users often want to know what signatures are needed.
5970 // so we should automatically call signature_help
5971 self.show_signature_help(&ShowSignatureHelp, window, cx);
5972 }
5973
5974 Some(cx.foreground_executor().spawn(async move {
5975 apply_edits.await?;
5976 Ok(())
5977 }))
5978 }
5979
5980 pub fn toggle_code_actions(
5981 &mut self,
5982 action: &ToggleCodeActions,
5983 window: &mut Window,
5984 cx: &mut Context<Self>,
5985 ) {
5986 let quick_launch = action.quick_launch;
5987 let mut context_menu = self.context_menu.borrow_mut();
5988 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5989 if code_actions.deployed_from == action.deployed_from {
5990 // Toggle if we're selecting the same one
5991 *context_menu = None;
5992 cx.notify();
5993 return;
5994 } else {
5995 // Otherwise, clear it and start a new one
5996 *context_menu = None;
5997 cx.notify();
5998 }
5999 }
6000 drop(context_menu);
6001 let snapshot = self.snapshot(window, cx);
6002 let deployed_from = action.deployed_from.clone();
6003 let action = action.clone();
6004 self.completion_tasks.clear();
6005 self.discard_edit_prediction(false, cx);
6006
6007 let multibuffer_point = match &action.deployed_from {
6008 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6009 DisplayPoint::new(*row, 0).to_point(&snapshot)
6010 }
6011 _ => self.selections.newest::<Point>(cx).head(),
6012 };
6013 let Some((buffer, buffer_row)) = snapshot
6014 .buffer_snapshot
6015 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6016 .and_then(|(buffer_snapshot, range)| {
6017 self.buffer()
6018 .read(cx)
6019 .buffer(buffer_snapshot.remote_id())
6020 .map(|buffer| (buffer, range.start.row))
6021 })
6022 else {
6023 return;
6024 };
6025 let buffer_id = buffer.read(cx).remote_id();
6026 let tasks = self
6027 .tasks
6028 .get(&(buffer_id, buffer_row))
6029 .map(|t| Arc::new(t.to_owned()));
6030
6031 if !self.focus_handle.is_focused(window) {
6032 return;
6033 }
6034 let project = self.project.clone();
6035
6036 let code_actions_task = match deployed_from {
6037 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6038 _ => self.code_actions(buffer_row, window, cx),
6039 };
6040
6041 let runnable_task = match deployed_from {
6042 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6043 _ => {
6044 let mut task_context_task = Task::ready(None);
6045 if let Some(tasks) = &tasks
6046 && let Some(project) = project
6047 {
6048 task_context_task =
6049 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6050 }
6051
6052 cx.spawn_in(window, {
6053 let buffer = buffer.clone();
6054 async move |editor, cx| {
6055 let task_context = task_context_task.await;
6056
6057 let resolved_tasks =
6058 tasks
6059 .zip(task_context.clone())
6060 .map(|(tasks, task_context)| ResolvedTasks {
6061 templates: tasks.resolve(&task_context).collect(),
6062 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6063 multibuffer_point.row,
6064 tasks.column,
6065 )),
6066 });
6067 let debug_scenarios = editor
6068 .update(cx, |editor, cx| {
6069 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6070 })?
6071 .await;
6072 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6073 }
6074 })
6075 }
6076 };
6077
6078 cx.spawn_in(window, async move |editor, cx| {
6079 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6080 let code_actions = code_actions_task.await;
6081 let spawn_straight_away = quick_launch
6082 && resolved_tasks
6083 .as_ref()
6084 .is_some_and(|tasks| tasks.templates.len() == 1)
6085 && code_actions
6086 .as_ref()
6087 .is_none_or(|actions| actions.is_empty())
6088 && debug_scenarios.is_empty();
6089
6090 editor.update_in(cx, |editor, window, cx| {
6091 crate::hover_popover::hide_hover(editor, cx);
6092 let actions = CodeActionContents::new(
6093 resolved_tasks,
6094 code_actions,
6095 debug_scenarios,
6096 task_context.unwrap_or_default(),
6097 );
6098
6099 // Don't show the menu if there are no actions available
6100 if actions.is_empty() {
6101 cx.notify();
6102 return Task::ready(Ok(()));
6103 }
6104
6105 *editor.context_menu.borrow_mut() =
6106 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6107 buffer,
6108 actions,
6109 selected_item: Default::default(),
6110 scroll_handle: UniformListScrollHandle::default(),
6111 deployed_from,
6112 }));
6113 cx.notify();
6114 if spawn_straight_away
6115 && let Some(task) = editor.confirm_code_action(
6116 &ConfirmCodeAction { item_ix: Some(0) },
6117 window,
6118 cx,
6119 )
6120 {
6121 return task;
6122 }
6123
6124 Task::ready(Ok(()))
6125 })
6126 })
6127 .detach_and_log_err(cx);
6128 }
6129
6130 fn debug_scenarios(
6131 &mut self,
6132 resolved_tasks: &Option<ResolvedTasks>,
6133 buffer: &Entity<Buffer>,
6134 cx: &mut App,
6135 ) -> Task<Vec<task::DebugScenario>> {
6136 maybe!({
6137 let project = self.project()?;
6138 let dap_store = project.read(cx).dap_store();
6139 let mut scenarios = vec![];
6140 let resolved_tasks = resolved_tasks.as_ref()?;
6141 let buffer = buffer.read(cx);
6142 let language = buffer.language()?;
6143 let file = buffer.file();
6144 let debug_adapter = language_settings(language.name().into(), file, cx)
6145 .debuggers
6146 .first()
6147 .map(SharedString::from)
6148 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6149
6150 dap_store.update(cx, |dap_store, cx| {
6151 for (_, task) in &resolved_tasks.templates {
6152 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6153 task.original_task().clone(),
6154 debug_adapter.clone().into(),
6155 task.display_label().to_owned().into(),
6156 cx,
6157 );
6158 scenarios.push(maybe_scenario);
6159 }
6160 });
6161 Some(cx.background_spawn(async move {
6162 futures::future::join_all(scenarios)
6163 .await
6164 .into_iter()
6165 .flatten()
6166 .collect::<Vec<_>>()
6167 }))
6168 })
6169 .unwrap_or_else(|| Task::ready(vec![]))
6170 }
6171
6172 fn code_actions(
6173 &mut self,
6174 buffer_row: u32,
6175 window: &mut Window,
6176 cx: &mut Context<Self>,
6177 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6178 let mut task = self.code_actions_task.take();
6179 cx.spawn_in(window, async move |editor, cx| {
6180 while let Some(prev_task) = task {
6181 prev_task.await.log_err();
6182 task = editor
6183 .update(cx, |this, _| this.code_actions_task.take())
6184 .ok()?;
6185 }
6186
6187 editor
6188 .update(cx, |editor, cx| {
6189 editor
6190 .available_code_actions
6191 .clone()
6192 .and_then(|(location, code_actions)| {
6193 let snapshot = location.buffer.read(cx).snapshot();
6194 let point_range = location.range.to_point(&snapshot);
6195 let point_range = point_range.start.row..=point_range.end.row;
6196 if point_range.contains(&buffer_row) {
6197 Some(code_actions)
6198 } else {
6199 None
6200 }
6201 })
6202 })
6203 .ok()
6204 .flatten()
6205 })
6206 }
6207
6208 pub fn confirm_code_action(
6209 &mut self,
6210 action: &ConfirmCodeAction,
6211 window: &mut Window,
6212 cx: &mut Context<Self>,
6213 ) -> Option<Task<Result<()>>> {
6214 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6215
6216 let actions_menu =
6217 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6218 menu
6219 } else {
6220 return None;
6221 };
6222
6223 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6224 let action = actions_menu.actions.get(action_ix)?;
6225 let title = action.label();
6226 let buffer = actions_menu.buffer;
6227 let workspace = self.workspace()?;
6228
6229 match action {
6230 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6231 workspace.update(cx, |workspace, cx| {
6232 workspace.schedule_resolved_task(
6233 task_source_kind,
6234 resolved_task,
6235 false,
6236 window,
6237 cx,
6238 );
6239
6240 Some(Task::ready(Ok(())))
6241 })
6242 }
6243 CodeActionsItem::CodeAction {
6244 excerpt_id,
6245 action,
6246 provider,
6247 } => {
6248 let apply_code_action =
6249 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6250 let workspace = workspace.downgrade();
6251 Some(cx.spawn_in(window, async move |editor, cx| {
6252 let project_transaction = apply_code_action.await?;
6253 Self::open_project_transaction(
6254 &editor,
6255 workspace,
6256 project_transaction,
6257 title,
6258 cx,
6259 )
6260 .await
6261 }))
6262 }
6263 CodeActionsItem::DebugScenario(scenario) => {
6264 let context = actions_menu.actions.context;
6265
6266 workspace.update(cx, |workspace, cx| {
6267 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6268 workspace.start_debug_session(
6269 scenario,
6270 context,
6271 Some(buffer),
6272 None,
6273 window,
6274 cx,
6275 );
6276 });
6277 Some(Task::ready(Ok(())))
6278 }
6279 }
6280 }
6281
6282 pub async fn open_project_transaction(
6283 this: &WeakEntity<Editor>,
6284 workspace: WeakEntity<Workspace>,
6285 transaction: ProjectTransaction,
6286 title: String,
6287 cx: &mut AsyncWindowContext,
6288 ) -> Result<()> {
6289 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6290 cx.update(|_, cx| {
6291 entries.sort_unstable_by_key(|(buffer, _)| {
6292 buffer.read(cx).file().map(|f| f.path().clone())
6293 });
6294 })?;
6295
6296 // If the project transaction's edits are all contained within this editor, then
6297 // avoid opening a new editor to display them.
6298
6299 if let Some((buffer, transaction)) = entries.first() {
6300 if entries.len() == 1 {
6301 let excerpt = this.update(cx, |editor, cx| {
6302 editor
6303 .buffer()
6304 .read(cx)
6305 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6306 })?;
6307 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6308 && excerpted_buffer == *buffer
6309 {
6310 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6311 let excerpt_range = excerpt_range.to_offset(buffer);
6312 buffer
6313 .edited_ranges_for_transaction::<usize>(transaction)
6314 .all(|range| {
6315 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6316 })
6317 })?;
6318
6319 if all_edits_within_excerpt {
6320 return Ok(());
6321 }
6322 }
6323 }
6324 } else {
6325 return Ok(());
6326 }
6327
6328 let mut ranges_to_highlight = Vec::new();
6329 let excerpt_buffer = cx.new(|cx| {
6330 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6331 for (buffer_handle, transaction) in &entries {
6332 let edited_ranges = buffer_handle
6333 .read(cx)
6334 .edited_ranges_for_transaction::<Point>(transaction)
6335 .collect::<Vec<_>>();
6336 let (ranges, _) = multibuffer.set_excerpts_for_path(
6337 PathKey::for_buffer(buffer_handle, cx),
6338 buffer_handle.clone(),
6339 edited_ranges,
6340 DEFAULT_MULTIBUFFER_CONTEXT,
6341 cx,
6342 );
6343
6344 ranges_to_highlight.extend(ranges);
6345 }
6346 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6347 multibuffer
6348 })?;
6349
6350 workspace.update_in(cx, |workspace, window, cx| {
6351 let project = workspace.project().clone();
6352 let editor =
6353 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6354 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6355 editor.update(cx, |editor, cx| {
6356 editor.highlight_background::<Self>(
6357 &ranges_to_highlight,
6358 |theme| theme.colors().editor_highlighted_line_background,
6359 cx,
6360 );
6361 });
6362 })?;
6363
6364 Ok(())
6365 }
6366
6367 pub fn clear_code_action_providers(&mut self) {
6368 self.code_action_providers.clear();
6369 self.available_code_actions.take();
6370 }
6371
6372 pub fn add_code_action_provider(
6373 &mut self,
6374 provider: Rc<dyn CodeActionProvider>,
6375 window: &mut Window,
6376 cx: &mut Context<Self>,
6377 ) {
6378 if self
6379 .code_action_providers
6380 .iter()
6381 .any(|existing_provider| existing_provider.id() == provider.id())
6382 {
6383 return;
6384 }
6385
6386 self.code_action_providers.push(provider);
6387 self.refresh_code_actions(window, cx);
6388 }
6389
6390 pub fn remove_code_action_provider(
6391 &mut self,
6392 id: Arc<str>,
6393 window: &mut Window,
6394 cx: &mut Context<Self>,
6395 ) {
6396 self.code_action_providers
6397 .retain(|provider| provider.id() != id);
6398 self.refresh_code_actions(window, cx);
6399 }
6400
6401 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6402 !self.code_action_providers.is_empty()
6403 && EditorSettings::get_global(cx).toolbar.code_actions
6404 }
6405
6406 pub fn has_available_code_actions(&self) -> bool {
6407 self.available_code_actions
6408 .as_ref()
6409 .is_some_and(|(_, actions)| !actions.is_empty())
6410 }
6411
6412 fn render_inline_code_actions(
6413 &self,
6414 icon_size: ui::IconSize,
6415 display_row: DisplayRow,
6416 is_active: bool,
6417 cx: &mut Context<Self>,
6418 ) -> AnyElement {
6419 let show_tooltip = !self.context_menu_visible();
6420 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6421 .icon_size(icon_size)
6422 .shape(ui::IconButtonShape::Square)
6423 .icon_color(ui::Color::Hidden)
6424 .toggle_state(is_active)
6425 .when(show_tooltip, |this| {
6426 this.tooltip({
6427 let focus_handle = self.focus_handle.clone();
6428 move |window, cx| {
6429 Tooltip::for_action_in(
6430 "Toggle Code Actions",
6431 &ToggleCodeActions {
6432 deployed_from: None,
6433 quick_launch: false,
6434 },
6435 &focus_handle,
6436 window,
6437 cx,
6438 )
6439 }
6440 })
6441 })
6442 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6443 window.focus(&editor.focus_handle(cx));
6444 editor.toggle_code_actions(
6445 &crate::actions::ToggleCodeActions {
6446 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6447 display_row,
6448 )),
6449 quick_launch: false,
6450 },
6451 window,
6452 cx,
6453 );
6454 }))
6455 .into_any_element()
6456 }
6457
6458 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6459 &self.context_menu
6460 }
6461
6462 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6463 let newest_selection = self.selections.newest_anchor().clone();
6464 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6465 let buffer = self.buffer.read(cx);
6466 if newest_selection.head().diff_base_anchor.is_some() {
6467 return None;
6468 }
6469 let (start_buffer, start) =
6470 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6471 let (end_buffer, end) =
6472 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6473 if start_buffer != end_buffer {
6474 return None;
6475 }
6476
6477 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6478 cx.background_executor()
6479 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6480 .await;
6481
6482 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6483 let providers = this.code_action_providers.clone();
6484 let tasks = this
6485 .code_action_providers
6486 .iter()
6487 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6488 .collect::<Vec<_>>();
6489 (providers, tasks)
6490 })?;
6491
6492 let mut actions = Vec::new();
6493 for (provider, provider_actions) in
6494 providers.into_iter().zip(future::join_all(tasks).await)
6495 {
6496 if let Some(provider_actions) = provider_actions.log_err() {
6497 actions.extend(provider_actions.into_iter().map(|action| {
6498 AvailableCodeAction {
6499 excerpt_id: newest_selection.start.excerpt_id,
6500 action,
6501 provider: provider.clone(),
6502 }
6503 }));
6504 }
6505 }
6506
6507 this.update(cx, |this, cx| {
6508 this.available_code_actions = if actions.is_empty() {
6509 None
6510 } else {
6511 Some((
6512 Location {
6513 buffer: start_buffer,
6514 range: start..end,
6515 },
6516 actions.into(),
6517 ))
6518 };
6519 cx.notify();
6520 })
6521 }));
6522 None
6523 }
6524
6525 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6526 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6527 self.show_git_blame_inline = false;
6528
6529 self.show_git_blame_inline_delay_task =
6530 Some(cx.spawn_in(window, async move |this, cx| {
6531 cx.background_executor().timer(delay).await;
6532
6533 this.update(cx, |this, cx| {
6534 this.show_git_blame_inline = true;
6535 cx.notify();
6536 })
6537 .log_err();
6538 }));
6539 }
6540 }
6541
6542 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6543 let snapshot = self.snapshot(window, cx);
6544 let cursor = self.selections.newest::<Point>(cx).head();
6545 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6546 else {
6547 return;
6548 };
6549
6550 let Some(blame) = self.blame.as_ref() else {
6551 return;
6552 };
6553
6554 let row_info = RowInfo {
6555 buffer_id: Some(buffer.remote_id()),
6556 buffer_row: Some(point.row),
6557 ..Default::default()
6558 };
6559 let Some(blame_entry) = blame
6560 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6561 .flatten()
6562 else {
6563 return;
6564 };
6565
6566 let anchor = self.selections.newest_anchor().head();
6567 let position = self.to_pixel_point(anchor, &snapshot, window);
6568 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6569 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6570 };
6571 }
6572
6573 fn show_blame_popover(
6574 &mut self,
6575 blame_entry: &BlameEntry,
6576 position: gpui::Point<Pixels>,
6577 ignore_timeout: bool,
6578 cx: &mut Context<Self>,
6579 ) {
6580 if let Some(state) = &mut self.inline_blame_popover {
6581 state.hide_task.take();
6582 } else {
6583 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6584 let blame_entry = blame_entry.clone();
6585 let show_task = cx.spawn(async move |editor, cx| {
6586 if !ignore_timeout {
6587 cx.background_executor()
6588 .timer(std::time::Duration::from_millis(blame_popover_delay))
6589 .await;
6590 }
6591 editor
6592 .update(cx, |editor, cx| {
6593 editor.inline_blame_popover_show_task.take();
6594 let Some(blame) = editor.blame.as_ref() else {
6595 return;
6596 };
6597 let blame = blame.read(cx);
6598 let details = blame.details_for_entry(&blame_entry);
6599 let markdown = cx.new(|cx| {
6600 Markdown::new(
6601 details
6602 .as_ref()
6603 .map(|message| message.message.clone())
6604 .unwrap_or_default(),
6605 None,
6606 None,
6607 cx,
6608 )
6609 });
6610 editor.inline_blame_popover = Some(InlineBlamePopover {
6611 position,
6612 hide_task: None,
6613 popover_bounds: None,
6614 popover_state: InlineBlamePopoverState {
6615 scroll_handle: ScrollHandle::new(),
6616 commit_message: details,
6617 markdown,
6618 },
6619 keyboard_grace: ignore_timeout,
6620 });
6621 cx.notify();
6622 })
6623 .ok();
6624 });
6625 self.inline_blame_popover_show_task = Some(show_task);
6626 }
6627 }
6628
6629 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6630 self.inline_blame_popover_show_task.take();
6631 if let Some(state) = &mut self.inline_blame_popover {
6632 let hide_task = cx.spawn(async move |editor, cx| {
6633 cx.background_executor()
6634 .timer(std::time::Duration::from_millis(100))
6635 .await;
6636 editor
6637 .update(cx, |editor, cx| {
6638 editor.inline_blame_popover.take();
6639 cx.notify();
6640 })
6641 .ok();
6642 });
6643 state.hide_task = Some(hide_task);
6644 }
6645 }
6646
6647 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6648 if self.pending_rename.is_some() {
6649 return None;
6650 }
6651
6652 let provider = self.semantics_provider.clone()?;
6653 let buffer = self.buffer.read(cx);
6654 let newest_selection = self.selections.newest_anchor().clone();
6655 let cursor_position = newest_selection.head();
6656 let (cursor_buffer, cursor_buffer_position) =
6657 buffer.text_anchor_for_position(cursor_position, cx)?;
6658 let (tail_buffer, tail_buffer_position) =
6659 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6660 if cursor_buffer != tail_buffer {
6661 return None;
6662 }
6663
6664 let snapshot = cursor_buffer.read(cx).snapshot();
6665 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6666 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6667 if start_word_range != end_word_range {
6668 self.document_highlights_task.take();
6669 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6670 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6671 return None;
6672 }
6673
6674 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6675 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6676 cx.background_executor()
6677 .timer(Duration::from_millis(debounce))
6678 .await;
6679
6680 let highlights = if let Some(highlights) = cx
6681 .update(|cx| {
6682 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6683 })
6684 .ok()
6685 .flatten()
6686 {
6687 highlights.await.log_err()
6688 } else {
6689 None
6690 };
6691
6692 if let Some(highlights) = highlights {
6693 this.update(cx, |this, cx| {
6694 if this.pending_rename.is_some() {
6695 return;
6696 }
6697
6698 let buffer = this.buffer.read(cx);
6699 if buffer
6700 .text_anchor_for_position(cursor_position, cx)
6701 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6702 {
6703 return;
6704 }
6705
6706 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6707 let mut write_ranges = Vec::new();
6708 let mut read_ranges = Vec::new();
6709 for highlight in highlights {
6710 let buffer_id = cursor_buffer.read(cx).remote_id();
6711 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6712 {
6713 let start = highlight
6714 .range
6715 .start
6716 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6717 let end = highlight
6718 .range
6719 .end
6720 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6721 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6722 continue;
6723 }
6724
6725 let range = Anchor {
6726 buffer_id: Some(buffer_id),
6727 excerpt_id,
6728 text_anchor: start,
6729 diff_base_anchor: None,
6730 }..Anchor {
6731 buffer_id: Some(buffer_id),
6732 excerpt_id,
6733 text_anchor: end,
6734 diff_base_anchor: None,
6735 };
6736 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6737 write_ranges.push(range);
6738 } else {
6739 read_ranges.push(range);
6740 }
6741 }
6742 }
6743
6744 this.highlight_background::<DocumentHighlightRead>(
6745 &read_ranges,
6746 |theme| theme.colors().editor_document_highlight_read_background,
6747 cx,
6748 );
6749 this.highlight_background::<DocumentHighlightWrite>(
6750 &write_ranges,
6751 |theme| theme.colors().editor_document_highlight_write_background,
6752 cx,
6753 );
6754 cx.notify();
6755 })
6756 .log_err();
6757 }
6758 }));
6759 None
6760 }
6761
6762 fn prepare_highlight_query_from_selection(
6763 &mut self,
6764 cx: &mut Context<Editor>,
6765 ) -> Option<(String, Range<Anchor>)> {
6766 if matches!(self.mode, EditorMode::SingleLine) {
6767 return None;
6768 }
6769 if !EditorSettings::get_global(cx).selection_highlight {
6770 return None;
6771 }
6772 if self.selections.count() != 1 || self.selections.line_mode {
6773 return None;
6774 }
6775 let selection = self.selections.newest::<Point>(cx);
6776 if selection.is_empty() || selection.start.row != selection.end.row {
6777 return None;
6778 }
6779 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6780 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6781 let query = multi_buffer_snapshot
6782 .text_for_range(selection_anchor_range.clone())
6783 .collect::<String>();
6784 if query.trim().is_empty() {
6785 return None;
6786 }
6787 Some((query, selection_anchor_range))
6788 }
6789
6790 fn update_selection_occurrence_highlights(
6791 &mut self,
6792 query_text: String,
6793 query_range: Range<Anchor>,
6794 multi_buffer_range_to_query: Range<Point>,
6795 use_debounce: bool,
6796 window: &mut Window,
6797 cx: &mut Context<Editor>,
6798 ) -> Task<()> {
6799 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6800 cx.spawn_in(window, async move |editor, cx| {
6801 if use_debounce {
6802 cx.background_executor()
6803 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6804 .await;
6805 }
6806 let match_task = cx.background_spawn(async move {
6807 let buffer_ranges = multi_buffer_snapshot
6808 .range_to_buffer_ranges(multi_buffer_range_to_query)
6809 .into_iter()
6810 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6811 let mut match_ranges = Vec::new();
6812 let Ok(regex) = project::search::SearchQuery::text(
6813 query_text.clone(),
6814 false,
6815 false,
6816 false,
6817 Default::default(),
6818 Default::default(),
6819 false,
6820 None,
6821 ) else {
6822 return Vec::default();
6823 };
6824 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6825 match_ranges.extend(
6826 regex
6827 .search(buffer_snapshot, Some(search_range.clone()))
6828 .await
6829 .into_iter()
6830 .filter_map(|match_range| {
6831 let match_start = buffer_snapshot
6832 .anchor_after(search_range.start + match_range.start);
6833 let match_end = buffer_snapshot
6834 .anchor_before(search_range.start + match_range.end);
6835 let match_anchor_range = Anchor::range_in_buffer(
6836 excerpt_id,
6837 buffer_snapshot.remote_id(),
6838 match_start..match_end,
6839 );
6840 (match_anchor_range != query_range).then_some(match_anchor_range)
6841 }),
6842 );
6843 }
6844 match_ranges
6845 });
6846 let match_ranges = match_task.await;
6847 editor
6848 .update_in(cx, |editor, _, cx| {
6849 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6850 if !match_ranges.is_empty() {
6851 editor.highlight_background::<SelectedTextHighlight>(
6852 &match_ranges,
6853 |theme| theme.colors().editor_document_highlight_bracket_background,
6854 cx,
6855 )
6856 }
6857 })
6858 .log_err();
6859 })
6860 }
6861
6862 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6863 struct NewlineFold;
6864 let type_id = std::any::TypeId::of::<NewlineFold>();
6865 if !self.mode.is_single_line() {
6866 return;
6867 }
6868 let snapshot = self.snapshot(window, cx);
6869 if snapshot.buffer_snapshot.max_point().row == 0 {
6870 return;
6871 }
6872 let task = cx.background_spawn(async move {
6873 let new_newlines = snapshot
6874 .buffer_chars_at(0)
6875 .filter_map(|(c, i)| {
6876 if c == '\n' {
6877 Some(
6878 snapshot.buffer_snapshot.anchor_after(i)
6879 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6880 )
6881 } else {
6882 None
6883 }
6884 })
6885 .collect::<Vec<_>>();
6886 let existing_newlines = snapshot
6887 .folds_in_range(0..snapshot.buffer_snapshot.len())
6888 .filter_map(|fold| {
6889 if fold.placeholder.type_tag == Some(type_id) {
6890 Some(fold.range.start..fold.range.end)
6891 } else {
6892 None
6893 }
6894 })
6895 .collect::<Vec<_>>();
6896
6897 (new_newlines, existing_newlines)
6898 });
6899 self.folding_newlines = cx.spawn(async move |this, cx| {
6900 let (new_newlines, existing_newlines) = task.await;
6901 if new_newlines == existing_newlines {
6902 return;
6903 }
6904 let placeholder = FoldPlaceholder {
6905 render: Arc::new(move |_, _, cx| {
6906 div()
6907 .bg(cx.theme().status().hint_background)
6908 .border_b_1()
6909 .size_full()
6910 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6911 .border_color(cx.theme().status().hint)
6912 .child("\\n")
6913 .into_any()
6914 }),
6915 constrain_width: false,
6916 merge_adjacent: false,
6917 type_tag: Some(type_id),
6918 };
6919 let creases = new_newlines
6920 .into_iter()
6921 .map(|range| Crease::simple(range, placeholder.clone()))
6922 .collect();
6923 this.update(cx, |this, cx| {
6924 this.display_map.update(cx, |display_map, cx| {
6925 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6926 display_map.fold(creases, cx);
6927 });
6928 })
6929 .ok();
6930 });
6931 }
6932
6933 fn refresh_selected_text_highlights(
6934 &mut self,
6935 on_buffer_edit: bool,
6936 window: &mut Window,
6937 cx: &mut Context<Editor>,
6938 ) {
6939 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6940 else {
6941 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6942 self.quick_selection_highlight_task.take();
6943 self.debounced_selection_highlight_task.take();
6944 return;
6945 };
6946 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6947 if on_buffer_edit
6948 || self
6949 .quick_selection_highlight_task
6950 .as_ref()
6951 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6952 {
6953 let multi_buffer_visible_start = self
6954 .scroll_manager
6955 .anchor()
6956 .anchor
6957 .to_point(&multi_buffer_snapshot);
6958 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6959 multi_buffer_visible_start
6960 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6961 Bias::Left,
6962 );
6963 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6964 self.quick_selection_highlight_task = Some((
6965 query_range.clone(),
6966 self.update_selection_occurrence_highlights(
6967 query_text.clone(),
6968 query_range.clone(),
6969 multi_buffer_visible_range,
6970 false,
6971 window,
6972 cx,
6973 ),
6974 ));
6975 }
6976 if on_buffer_edit
6977 || self
6978 .debounced_selection_highlight_task
6979 .as_ref()
6980 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6981 {
6982 let multi_buffer_start = multi_buffer_snapshot
6983 .anchor_before(0)
6984 .to_point(&multi_buffer_snapshot);
6985 let multi_buffer_end = multi_buffer_snapshot
6986 .anchor_after(multi_buffer_snapshot.len())
6987 .to_point(&multi_buffer_snapshot);
6988 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6989 self.debounced_selection_highlight_task = Some((
6990 query_range.clone(),
6991 self.update_selection_occurrence_highlights(
6992 query_text,
6993 query_range,
6994 multi_buffer_full_range,
6995 true,
6996 window,
6997 cx,
6998 ),
6999 ));
7000 }
7001 }
7002
7003 pub fn refresh_edit_prediction(
7004 &mut self,
7005 debounce: bool,
7006 user_requested: bool,
7007 window: &mut Window,
7008 cx: &mut Context<Self>,
7009 ) -> Option<()> {
7010 if DisableAiSettings::get_global(cx).disable_ai {
7011 return None;
7012 }
7013
7014 let provider = self.edit_prediction_provider()?;
7015 let cursor = self.selections.newest_anchor().head();
7016 let (buffer, cursor_buffer_position) =
7017 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7018
7019 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7020 self.discard_edit_prediction(false, cx);
7021 return None;
7022 }
7023
7024 if !user_requested
7025 && (!self.should_show_edit_predictions()
7026 || !self.is_focused(window)
7027 || buffer.read(cx).is_empty())
7028 {
7029 self.discard_edit_prediction(false, cx);
7030 return None;
7031 }
7032
7033 self.update_visible_edit_prediction(window, cx);
7034 provider.refresh(
7035 self.project.clone(),
7036 buffer,
7037 cursor_buffer_position,
7038 debounce,
7039 cx,
7040 );
7041 Some(())
7042 }
7043
7044 fn show_edit_predictions_in_menu(&self) -> bool {
7045 match self.edit_prediction_settings {
7046 EditPredictionSettings::Disabled => false,
7047 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7048 }
7049 }
7050
7051 pub fn edit_predictions_enabled(&self) -> bool {
7052 match self.edit_prediction_settings {
7053 EditPredictionSettings::Disabled => false,
7054 EditPredictionSettings::Enabled { .. } => true,
7055 }
7056 }
7057
7058 fn edit_prediction_requires_modifier(&self) -> bool {
7059 match self.edit_prediction_settings {
7060 EditPredictionSettings::Disabled => false,
7061 EditPredictionSettings::Enabled {
7062 preview_requires_modifier,
7063 ..
7064 } => preview_requires_modifier,
7065 }
7066 }
7067
7068 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7069 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7070 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7071 self.discard_edit_prediction(false, cx);
7072 } else {
7073 let selection = self.selections.newest_anchor();
7074 let cursor = selection.head();
7075
7076 if let Some((buffer, cursor_buffer_position)) =
7077 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7078 {
7079 self.edit_prediction_settings =
7080 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7081 }
7082 }
7083 }
7084
7085 fn edit_prediction_settings_at_position(
7086 &self,
7087 buffer: &Entity<Buffer>,
7088 buffer_position: language::Anchor,
7089 cx: &App,
7090 ) -> EditPredictionSettings {
7091 if !self.mode.is_full()
7092 || !self.show_edit_predictions_override.unwrap_or(true)
7093 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7094 {
7095 return EditPredictionSettings::Disabled;
7096 }
7097
7098 let buffer = buffer.read(cx);
7099
7100 let file = buffer.file();
7101
7102 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7103 return EditPredictionSettings::Disabled;
7104 };
7105
7106 let by_provider = matches!(
7107 self.menu_edit_predictions_policy,
7108 MenuEditPredictionsPolicy::ByProvider
7109 );
7110
7111 let show_in_menu = by_provider
7112 && self
7113 .edit_prediction_provider
7114 .as_ref()
7115 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7116
7117 let preview_requires_modifier =
7118 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7119
7120 EditPredictionSettings::Enabled {
7121 show_in_menu,
7122 preview_requires_modifier,
7123 }
7124 }
7125
7126 fn should_show_edit_predictions(&self) -> bool {
7127 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7128 }
7129
7130 pub fn edit_prediction_preview_is_active(&self) -> bool {
7131 matches!(
7132 self.edit_prediction_preview,
7133 EditPredictionPreview::Active { .. }
7134 )
7135 }
7136
7137 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7138 let cursor = self.selections.newest_anchor().head();
7139 if let Some((buffer, cursor_position)) =
7140 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7141 {
7142 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7143 } else {
7144 false
7145 }
7146 }
7147
7148 pub fn supports_minimap(&self, cx: &App) -> bool {
7149 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7150 }
7151
7152 fn edit_predictions_enabled_in_buffer(
7153 &self,
7154 buffer: &Entity<Buffer>,
7155 buffer_position: language::Anchor,
7156 cx: &App,
7157 ) -> bool {
7158 maybe!({
7159 if self.read_only(cx) {
7160 return Some(false);
7161 }
7162 let provider = self.edit_prediction_provider()?;
7163 if !provider.is_enabled(buffer, buffer_position, cx) {
7164 return Some(false);
7165 }
7166 let buffer = buffer.read(cx);
7167 let Some(file) = buffer.file() else {
7168 return Some(true);
7169 };
7170 let settings = all_language_settings(Some(file), cx);
7171 Some(settings.edit_predictions_enabled_for_file(file, cx))
7172 })
7173 .unwrap_or(false)
7174 }
7175
7176 fn cycle_edit_prediction(
7177 &mut self,
7178 direction: Direction,
7179 window: &mut Window,
7180 cx: &mut Context<Self>,
7181 ) -> Option<()> {
7182 let provider = self.edit_prediction_provider()?;
7183 let cursor = self.selections.newest_anchor().head();
7184 let (buffer, cursor_buffer_position) =
7185 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7186 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7187 return None;
7188 }
7189
7190 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7191 self.update_visible_edit_prediction(window, cx);
7192
7193 Some(())
7194 }
7195
7196 pub fn show_edit_prediction(
7197 &mut self,
7198 _: &ShowEditPrediction,
7199 window: &mut Window,
7200 cx: &mut Context<Self>,
7201 ) {
7202 if !self.has_active_edit_prediction() {
7203 self.refresh_edit_prediction(false, true, window, cx);
7204 return;
7205 }
7206
7207 self.update_visible_edit_prediction(window, cx);
7208 }
7209
7210 pub fn display_cursor_names(
7211 &mut self,
7212 _: &DisplayCursorNames,
7213 window: &mut Window,
7214 cx: &mut Context<Self>,
7215 ) {
7216 self.show_cursor_names(window, cx);
7217 }
7218
7219 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7220 self.show_cursor_names = true;
7221 cx.notify();
7222 cx.spawn_in(window, async move |this, cx| {
7223 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7224 this.update(cx, |this, cx| {
7225 this.show_cursor_names = false;
7226 cx.notify()
7227 })
7228 .ok()
7229 })
7230 .detach();
7231 }
7232
7233 pub fn next_edit_prediction(
7234 &mut self,
7235 _: &NextEditPrediction,
7236 window: &mut Window,
7237 cx: &mut Context<Self>,
7238 ) {
7239 if self.has_active_edit_prediction() {
7240 self.cycle_edit_prediction(Direction::Next, window, cx);
7241 } else {
7242 let is_copilot_disabled = self
7243 .refresh_edit_prediction(false, true, window, cx)
7244 .is_none();
7245 if is_copilot_disabled {
7246 cx.propagate();
7247 }
7248 }
7249 }
7250
7251 pub fn previous_edit_prediction(
7252 &mut self,
7253 _: &PreviousEditPrediction,
7254 window: &mut Window,
7255 cx: &mut Context<Self>,
7256 ) {
7257 if self.has_active_edit_prediction() {
7258 self.cycle_edit_prediction(Direction::Prev, window, cx);
7259 } else {
7260 let is_copilot_disabled = self
7261 .refresh_edit_prediction(false, true, window, cx)
7262 .is_none();
7263 if is_copilot_disabled {
7264 cx.propagate();
7265 }
7266 }
7267 }
7268
7269 pub fn accept_edit_prediction(
7270 &mut self,
7271 _: &AcceptEditPrediction,
7272 window: &mut Window,
7273 cx: &mut Context<Self>,
7274 ) {
7275 if self.show_edit_predictions_in_menu() {
7276 self.hide_context_menu(window, cx);
7277 }
7278
7279 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7280 return;
7281 };
7282
7283 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7284
7285 match &active_edit_prediction.completion {
7286 EditPrediction::Move { target, .. } => {
7287 let target = *target;
7288
7289 if let Some(position_map) = &self.last_position_map {
7290 if position_map
7291 .visible_row_range
7292 .contains(&target.to_display_point(&position_map.snapshot).row())
7293 || !self.edit_prediction_requires_modifier()
7294 {
7295 self.unfold_ranges(&[target..target], true, false, cx);
7296 // Note that this is also done in vim's handler of the Tab action.
7297 self.change_selections(
7298 SelectionEffects::scroll(Autoscroll::newest()),
7299 window,
7300 cx,
7301 |selections| {
7302 selections.select_anchor_ranges([target..target]);
7303 },
7304 );
7305 self.clear_row_highlights::<EditPredictionPreview>();
7306
7307 self.edit_prediction_preview
7308 .set_previous_scroll_position(None);
7309 } else {
7310 self.edit_prediction_preview
7311 .set_previous_scroll_position(Some(
7312 position_map.snapshot.scroll_anchor,
7313 ));
7314
7315 self.highlight_rows::<EditPredictionPreview>(
7316 target..target,
7317 cx.theme().colors().editor_highlighted_line_background,
7318 RowHighlightOptions {
7319 autoscroll: true,
7320 ..Default::default()
7321 },
7322 cx,
7323 );
7324 self.request_autoscroll(Autoscroll::fit(), cx);
7325 }
7326 }
7327 }
7328 EditPrediction::Edit { edits, .. } => {
7329 if let Some(provider) = self.edit_prediction_provider() {
7330 provider.accept(cx);
7331 }
7332
7333 // Store the transaction ID and selections before applying the edit
7334 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7335
7336 let snapshot = self.buffer.read(cx).snapshot(cx);
7337 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7338
7339 self.buffer.update(cx, |buffer, cx| {
7340 buffer.edit(edits.iter().cloned(), None, cx)
7341 });
7342
7343 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7344 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7345 });
7346
7347 let selections = self.selections.disjoint_anchors();
7348 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7349 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7350 if has_new_transaction {
7351 self.selection_history
7352 .insert_transaction(transaction_id_now, selections);
7353 }
7354 }
7355
7356 self.update_visible_edit_prediction(window, cx);
7357 if self.active_edit_prediction.is_none() {
7358 self.refresh_edit_prediction(true, true, window, cx);
7359 }
7360
7361 cx.notify();
7362 }
7363 }
7364
7365 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7366 }
7367
7368 pub fn accept_partial_edit_prediction(
7369 &mut self,
7370 _: &AcceptPartialEditPrediction,
7371 window: &mut Window,
7372 cx: &mut Context<Self>,
7373 ) {
7374 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7375 return;
7376 };
7377 if self.selections.count() != 1 {
7378 return;
7379 }
7380
7381 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7382
7383 match &active_edit_prediction.completion {
7384 EditPrediction::Move { target, .. } => {
7385 let target = *target;
7386 self.change_selections(
7387 SelectionEffects::scroll(Autoscroll::newest()),
7388 window,
7389 cx,
7390 |selections| {
7391 selections.select_anchor_ranges([target..target]);
7392 },
7393 );
7394 }
7395 EditPrediction::Edit { edits, .. } => {
7396 // Find an insertion that starts at the cursor position.
7397 let snapshot = self.buffer.read(cx).snapshot(cx);
7398 let cursor_offset = self.selections.newest::<usize>(cx).head();
7399 let insertion = edits.iter().find_map(|(range, text)| {
7400 let range = range.to_offset(&snapshot);
7401 if range.is_empty() && range.start == cursor_offset {
7402 Some(text)
7403 } else {
7404 None
7405 }
7406 });
7407
7408 if let Some(text) = insertion {
7409 let mut partial_completion = text
7410 .chars()
7411 .by_ref()
7412 .take_while(|c| c.is_alphabetic())
7413 .collect::<String>();
7414 if partial_completion.is_empty() {
7415 partial_completion = text
7416 .chars()
7417 .by_ref()
7418 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7419 .collect::<String>();
7420 }
7421
7422 cx.emit(EditorEvent::InputHandled {
7423 utf16_range_to_replace: None,
7424 text: partial_completion.clone().into(),
7425 });
7426
7427 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7428
7429 self.refresh_edit_prediction(true, true, window, cx);
7430 cx.notify();
7431 } else {
7432 self.accept_edit_prediction(&Default::default(), window, cx);
7433 }
7434 }
7435 }
7436 }
7437
7438 fn discard_edit_prediction(
7439 &mut self,
7440 should_report_edit_prediction_event: bool,
7441 cx: &mut Context<Self>,
7442 ) -> bool {
7443 if should_report_edit_prediction_event {
7444 let completion_id = self
7445 .active_edit_prediction
7446 .as_ref()
7447 .and_then(|active_completion| active_completion.completion_id.clone());
7448
7449 self.report_edit_prediction_event(completion_id, false, cx);
7450 }
7451
7452 if let Some(provider) = self.edit_prediction_provider() {
7453 provider.discard(cx);
7454 }
7455
7456 self.take_active_edit_prediction(cx)
7457 }
7458
7459 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7460 let Some(provider) = self.edit_prediction_provider() else {
7461 return;
7462 };
7463
7464 let Some((_, buffer, _)) = self
7465 .buffer
7466 .read(cx)
7467 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7468 else {
7469 return;
7470 };
7471
7472 let extension = buffer
7473 .read(cx)
7474 .file()
7475 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7476
7477 let event_type = match accepted {
7478 true => "Edit Prediction Accepted",
7479 false => "Edit Prediction Discarded",
7480 };
7481 telemetry::event!(
7482 event_type,
7483 provider = provider.name(),
7484 prediction_id = id,
7485 suggestion_accepted = accepted,
7486 file_extension = extension,
7487 );
7488 }
7489
7490 pub fn has_active_edit_prediction(&self) -> bool {
7491 self.active_edit_prediction.is_some()
7492 }
7493
7494 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7495 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7496 return false;
7497 };
7498
7499 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7500 self.clear_highlights::<EditPredictionHighlight>(cx);
7501 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7502 true
7503 }
7504
7505 /// Returns true when we're displaying the edit prediction popover below the cursor
7506 /// like we are not previewing and the LSP autocomplete menu is visible
7507 /// or we are in `when_holding_modifier` mode.
7508 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7509 if self.edit_prediction_preview_is_active()
7510 || !self.show_edit_predictions_in_menu()
7511 || !self.edit_predictions_enabled()
7512 {
7513 return false;
7514 }
7515
7516 if self.has_visible_completions_menu() {
7517 return true;
7518 }
7519
7520 has_completion && self.edit_prediction_requires_modifier()
7521 }
7522
7523 fn handle_modifiers_changed(
7524 &mut self,
7525 modifiers: Modifiers,
7526 position_map: &PositionMap,
7527 window: &mut Window,
7528 cx: &mut Context<Self>,
7529 ) {
7530 if self.show_edit_predictions_in_menu() {
7531 self.update_edit_prediction_preview(&modifiers, window, cx);
7532 }
7533
7534 self.update_selection_mode(&modifiers, position_map, window, cx);
7535
7536 let mouse_position = window.mouse_position();
7537 if !position_map.text_hitbox.is_hovered(window) {
7538 return;
7539 }
7540
7541 self.update_hovered_link(
7542 position_map.point_for_position(mouse_position),
7543 &position_map.snapshot,
7544 modifiers,
7545 window,
7546 cx,
7547 )
7548 }
7549
7550 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7551 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7552 if invert {
7553 match multi_cursor_setting {
7554 MultiCursorModifier::Alt => modifiers.alt,
7555 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7556 }
7557 } else {
7558 match multi_cursor_setting {
7559 MultiCursorModifier::Alt => modifiers.secondary(),
7560 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7561 }
7562 }
7563 }
7564
7565 fn columnar_selection_mode(
7566 modifiers: &Modifiers,
7567 cx: &mut Context<Self>,
7568 ) -> Option<ColumnarMode> {
7569 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7570 if Self::multi_cursor_modifier(false, modifiers, cx) {
7571 Some(ColumnarMode::FromMouse)
7572 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7573 Some(ColumnarMode::FromSelection)
7574 } else {
7575 None
7576 }
7577 } else {
7578 None
7579 }
7580 }
7581
7582 fn update_selection_mode(
7583 &mut self,
7584 modifiers: &Modifiers,
7585 position_map: &PositionMap,
7586 window: &mut Window,
7587 cx: &mut Context<Self>,
7588 ) {
7589 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7590 return;
7591 };
7592 if self.selections.pending.is_none() {
7593 return;
7594 }
7595
7596 let mouse_position = window.mouse_position();
7597 let point_for_position = position_map.point_for_position(mouse_position);
7598 let position = point_for_position.previous_valid;
7599
7600 self.select(
7601 SelectPhase::BeginColumnar {
7602 position,
7603 reset: false,
7604 mode,
7605 goal_column: point_for_position.exact_unclipped.column(),
7606 },
7607 window,
7608 cx,
7609 );
7610 }
7611
7612 fn update_edit_prediction_preview(
7613 &mut self,
7614 modifiers: &Modifiers,
7615 window: &mut Window,
7616 cx: &mut Context<Self>,
7617 ) {
7618 let mut modifiers_held = false;
7619 if let Some(accept_keystroke) = self
7620 .accept_edit_prediction_keybind(false, window, cx)
7621 .keystroke()
7622 {
7623 modifiers_held = modifiers_held
7624 || (&accept_keystroke.modifiers == modifiers
7625 && accept_keystroke.modifiers.modified());
7626 };
7627 if let Some(accept_partial_keystroke) = self
7628 .accept_edit_prediction_keybind(true, window, cx)
7629 .keystroke()
7630 {
7631 modifiers_held = modifiers_held
7632 || (&accept_partial_keystroke.modifiers == modifiers
7633 && accept_partial_keystroke.modifiers.modified());
7634 }
7635
7636 if modifiers_held {
7637 if matches!(
7638 self.edit_prediction_preview,
7639 EditPredictionPreview::Inactive { .. }
7640 ) {
7641 self.edit_prediction_preview = EditPredictionPreview::Active {
7642 previous_scroll_position: None,
7643 since: Instant::now(),
7644 };
7645
7646 self.update_visible_edit_prediction(window, cx);
7647 cx.notify();
7648 }
7649 } else if let EditPredictionPreview::Active {
7650 previous_scroll_position,
7651 since,
7652 } = self.edit_prediction_preview
7653 {
7654 if let (Some(previous_scroll_position), Some(position_map)) =
7655 (previous_scroll_position, self.last_position_map.as_ref())
7656 {
7657 self.set_scroll_position(
7658 previous_scroll_position
7659 .scroll_position(&position_map.snapshot.display_snapshot),
7660 window,
7661 cx,
7662 );
7663 }
7664
7665 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7666 released_too_fast: since.elapsed() < Duration::from_millis(200),
7667 };
7668 self.clear_row_highlights::<EditPredictionPreview>();
7669 self.update_visible_edit_prediction(window, cx);
7670 cx.notify();
7671 }
7672 }
7673
7674 fn update_visible_edit_prediction(
7675 &mut self,
7676 _window: &mut Window,
7677 cx: &mut Context<Self>,
7678 ) -> Option<()> {
7679 if DisableAiSettings::get_global(cx).disable_ai {
7680 return None;
7681 }
7682
7683 let selection = self.selections.newest_anchor();
7684 let cursor = selection.head();
7685 let multibuffer = self.buffer.read(cx).snapshot(cx);
7686 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7687 let excerpt_id = cursor.excerpt_id;
7688
7689 let show_in_menu = self.show_edit_predictions_in_menu();
7690 let completions_menu_has_precedence = !show_in_menu
7691 && (self.context_menu.borrow().is_some()
7692 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7693
7694 if completions_menu_has_precedence
7695 || !offset_selection.is_empty()
7696 || self
7697 .active_edit_prediction
7698 .as_ref()
7699 .is_some_and(|completion| {
7700 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7701 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7702 !invalidation_range.contains(&offset_selection.head())
7703 })
7704 {
7705 self.discard_edit_prediction(false, cx);
7706 return None;
7707 }
7708
7709 self.take_active_edit_prediction(cx);
7710 let Some(provider) = self.edit_prediction_provider() else {
7711 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7712 return None;
7713 };
7714
7715 let (buffer, cursor_buffer_position) =
7716 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7717
7718 self.edit_prediction_settings =
7719 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7720
7721 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7722 self.discard_edit_prediction(false, cx);
7723 return None;
7724 };
7725
7726 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7727
7728 if self.edit_prediction_indent_conflict {
7729 let cursor_point = cursor.to_point(&multibuffer);
7730
7731 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7732
7733 if let Some((_, indent)) = indents.iter().next()
7734 && indent.len == cursor_point.column
7735 {
7736 self.edit_prediction_indent_conflict = false;
7737 }
7738 }
7739
7740 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7741 let edits = edit_prediction
7742 .edits
7743 .into_iter()
7744 .flat_map(|(range, new_text)| {
7745 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7746 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7747 Some((start..end, new_text))
7748 })
7749 .collect::<Vec<_>>();
7750 if edits.is_empty() {
7751 return None;
7752 }
7753
7754 let first_edit_start = edits.first().unwrap().0.start;
7755 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7756 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7757
7758 let last_edit_end = edits.last().unwrap().0.end;
7759 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7760 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7761
7762 let cursor_row = cursor.to_point(&multibuffer).row;
7763
7764 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7765
7766 let mut inlay_ids = Vec::new();
7767 let invalidation_row_range;
7768 let move_invalidation_row_range = if cursor_row < edit_start_row {
7769 Some(cursor_row..edit_end_row)
7770 } else if cursor_row > edit_end_row {
7771 Some(edit_start_row..cursor_row)
7772 } else {
7773 None
7774 };
7775 let supports_jump = self
7776 .edit_prediction_provider
7777 .as_ref()
7778 .map(|provider| provider.provider.supports_jump_to_edit())
7779 .unwrap_or(true);
7780
7781 let is_move = supports_jump
7782 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7783 let completion = if is_move {
7784 invalidation_row_range =
7785 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7786 let target = first_edit_start;
7787 EditPrediction::Move { target, snapshot }
7788 } else {
7789 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7790 && !self.edit_predictions_hidden_for_vim_mode;
7791
7792 if show_completions_in_buffer {
7793 if edits
7794 .iter()
7795 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7796 {
7797 let mut inlays = Vec::new();
7798 for (range, new_text) in &edits {
7799 let inlay = Inlay::edit_prediction(
7800 post_inc(&mut self.next_inlay_id),
7801 range.start,
7802 new_text.as_str(),
7803 );
7804 inlay_ids.push(inlay.id);
7805 inlays.push(inlay);
7806 }
7807
7808 self.splice_inlays(&[], inlays, cx);
7809 } else {
7810 let background_color = cx.theme().status().deleted_background;
7811 self.highlight_text::<EditPredictionHighlight>(
7812 edits.iter().map(|(range, _)| range.clone()).collect(),
7813 HighlightStyle {
7814 background_color: Some(background_color),
7815 ..Default::default()
7816 },
7817 cx,
7818 );
7819 }
7820 }
7821
7822 invalidation_row_range = edit_start_row..edit_end_row;
7823
7824 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7825 if provider.show_tab_accept_marker() {
7826 EditDisplayMode::TabAccept
7827 } else {
7828 EditDisplayMode::Inline
7829 }
7830 } else {
7831 EditDisplayMode::DiffPopover
7832 };
7833
7834 EditPrediction::Edit {
7835 edits,
7836 edit_preview: edit_prediction.edit_preview,
7837 display_mode,
7838 snapshot,
7839 }
7840 };
7841
7842 let invalidation_range = multibuffer
7843 .anchor_before(Point::new(invalidation_row_range.start, 0))
7844 ..multibuffer.anchor_after(Point::new(
7845 invalidation_row_range.end,
7846 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7847 ));
7848
7849 self.stale_edit_prediction_in_menu = None;
7850 self.active_edit_prediction = Some(EditPredictionState {
7851 inlay_ids,
7852 completion,
7853 completion_id: edit_prediction.id,
7854 invalidation_range,
7855 });
7856
7857 cx.notify();
7858
7859 Some(())
7860 }
7861
7862 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7863 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7864 }
7865
7866 fn clear_tasks(&mut self) {
7867 self.tasks.clear()
7868 }
7869
7870 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7871 if self.tasks.insert(key, value).is_some() {
7872 // This case should hopefully be rare, but just in case...
7873 log::error!(
7874 "multiple different run targets found on a single line, only the last target will be rendered"
7875 )
7876 }
7877 }
7878
7879 /// Get all display points of breakpoints that will be rendered within editor
7880 ///
7881 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7882 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7883 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7884 fn active_breakpoints(
7885 &self,
7886 range: Range<DisplayRow>,
7887 window: &mut Window,
7888 cx: &mut Context<Self>,
7889 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7890 let mut breakpoint_display_points = HashMap::default();
7891
7892 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7893 return breakpoint_display_points;
7894 };
7895
7896 let snapshot = self.snapshot(window, cx);
7897
7898 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7899 let Some(project) = self.project() else {
7900 return breakpoint_display_points;
7901 };
7902
7903 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7904 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7905
7906 for (buffer_snapshot, range, excerpt_id) in
7907 multi_buffer_snapshot.range_to_buffer_ranges(range)
7908 {
7909 let Some(buffer) = project
7910 .read(cx)
7911 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7912 else {
7913 continue;
7914 };
7915 let breakpoints = breakpoint_store.read(cx).breakpoints(
7916 &buffer,
7917 Some(
7918 buffer_snapshot.anchor_before(range.start)
7919 ..buffer_snapshot.anchor_after(range.end),
7920 ),
7921 buffer_snapshot,
7922 cx,
7923 );
7924 for (breakpoint, state) in breakpoints {
7925 let multi_buffer_anchor =
7926 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7927 let position = multi_buffer_anchor
7928 .to_point(multi_buffer_snapshot)
7929 .to_display_point(&snapshot);
7930
7931 breakpoint_display_points.insert(
7932 position.row(),
7933 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7934 );
7935 }
7936 }
7937
7938 breakpoint_display_points
7939 }
7940
7941 fn breakpoint_context_menu(
7942 &self,
7943 anchor: Anchor,
7944 window: &mut Window,
7945 cx: &mut Context<Self>,
7946 ) -> Entity<ui::ContextMenu> {
7947 let weak_editor = cx.weak_entity();
7948 let focus_handle = self.focus_handle(cx);
7949
7950 let row = self
7951 .buffer
7952 .read(cx)
7953 .snapshot(cx)
7954 .summary_for_anchor::<Point>(&anchor)
7955 .row;
7956
7957 let breakpoint = self
7958 .breakpoint_at_row(row, window, cx)
7959 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7960
7961 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7962 "Edit Log Breakpoint"
7963 } else {
7964 "Set Log Breakpoint"
7965 };
7966
7967 let condition_breakpoint_msg = if breakpoint
7968 .as_ref()
7969 .is_some_and(|bp| bp.1.condition.is_some())
7970 {
7971 "Edit Condition Breakpoint"
7972 } else {
7973 "Set Condition Breakpoint"
7974 };
7975
7976 let hit_condition_breakpoint_msg = if breakpoint
7977 .as_ref()
7978 .is_some_and(|bp| bp.1.hit_condition.is_some())
7979 {
7980 "Edit Hit Condition Breakpoint"
7981 } else {
7982 "Set Hit Condition Breakpoint"
7983 };
7984
7985 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7986 "Unset Breakpoint"
7987 } else {
7988 "Set Breakpoint"
7989 };
7990
7991 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7992
7993 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7994 BreakpointState::Enabled => Some("Disable"),
7995 BreakpointState::Disabled => Some("Enable"),
7996 });
7997
7998 let (anchor, breakpoint) =
7999 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8000
8001 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8002 menu.on_blur_subscription(Subscription::new(|| {}))
8003 .context(focus_handle)
8004 .when(run_to_cursor, |this| {
8005 let weak_editor = weak_editor.clone();
8006 this.entry("Run to cursor", None, move |window, cx| {
8007 weak_editor
8008 .update(cx, |editor, cx| {
8009 editor.change_selections(
8010 SelectionEffects::no_scroll(),
8011 window,
8012 cx,
8013 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8014 );
8015 })
8016 .ok();
8017
8018 window.dispatch_action(Box::new(RunToCursor), cx);
8019 })
8020 .separator()
8021 })
8022 .when_some(toggle_state_msg, |this, msg| {
8023 this.entry(msg, None, {
8024 let weak_editor = weak_editor.clone();
8025 let breakpoint = breakpoint.clone();
8026 move |_window, cx| {
8027 weak_editor
8028 .update(cx, |this, cx| {
8029 this.edit_breakpoint_at_anchor(
8030 anchor,
8031 breakpoint.as_ref().clone(),
8032 BreakpointEditAction::InvertState,
8033 cx,
8034 );
8035 })
8036 .log_err();
8037 }
8038 })
8039 })
8040 .entry(set_breakpoint_msg, None, {
8041 let weak_editor = weak_editor.clone();
8042 let breakpoint = breakpoint.clone();
8043 move |_window, cx| {
8044 weak_editor
8045 .update(cx, |this, cx| {
8046 this.edit_breakpoint_at_anchor(
8047 anchor,
8048 breakpoint.as_ref().clone(),
8049 BreakpointEditAction::Toggle,
8050 cx,
8051 );
8052 })
8053 .log_err();
8054 }
8055 })
8056 .entry(log_breakpoint_msg, None, {
8057 let breakpoint = breakpoint.clone();
8058 let weak_editor = weak_editor.clone();
8059 move |window, cx| {
8060 weak_editor
8061 .update(cx, |this, cx| {
8062 this.add_edit_breakpoint_block(
8063 anchor,
8064 breakpoint.as_ref(),
8065 BreakpointPromptEditAction::Log,
8066 window,
8067 cx,
8068 );
8069 })
8070 .log_err();
8071 }
8072 })
8073 .entry(condition_breakpoint_msg, None, {
8074 let breakpoint = breakpoint.clone();
8075 let weak_editor = weak_editor.clone();
8076 move |window, cx| {
8077 weak_editor
8078 .update(cx, |this, cx| {
8079 this.add_edit_breakpoint_block(
8080 anchor,
8081 breakpoint.as_ref(),
8082 BreakpointPromptEditAction::Condition,
8083 window,
8084 cx,
8085 );
8086 })
8087 .log_err();
8088 }
8089 })
8090 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8091 weak_editor
8092 .update(cx, |this, cx| {
8093 this.add_edit_breakpoint_block(
8094 anchor,
8095 breakpoint.as_ref(),
8096 BreakpointPromptEditAction::HitCondition,
8097 window,
8098 cx,
8099 );
8100 })
8101 .log_err();
8102 })
8103 })
8104 }
8105
8106 fn render_breakpoint(
8107 &self,
8108 position: Anchor,
8109 row: DisplayRow,
8110 breakpoint: &Breakpoint,
8111 state: Option<BreakpointSessionState>,
8112 cx: &mut Context<Self>,
8113 ) -> IconButton {
8114 let is_rejected = state.is_some_and(|s| !s.verified);
8115 // Is it a breakpoint that shows up when hovering over gutter?
8116 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8117 (false, false),
8118 |PhantomBreakpointIndicator {
8119 is_active,
8120 display_row,
8121 collides_with_existing_breakpoint,
8122 }| {
8123 (
8124 is_active && display_row == row,
8125 collides_with_existing_breakpoint,
8126 )
8127 },
8128 );
8129
8130 let (color, icon) = {
8131 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8132 (false, false) => ui::IconName::DebugBreakpoint,
8133 (true, false) => ui::IconName::DebugLogBreakpoint,
8134 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8135 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8136 };
8137
8138 let color = if is_phantom {
8139 Color::Hint
8140 } else if is_rejected {
8141 Color::Disabled
8142 } else {
8143 Color::Debugger
8144 };
8145
8146 (color, icon)
8147 };
8148
8149 let breakpoint = Arc::from(breakpoint.clone());
8150
8151 let alt_as_text = gpui::Keystroke {
8152 modifiers: Modifiers::secondary_key(),
8153 ..Default::default()
8154 };
8155 let primary_action_text = if breakpoint.is_disabled() {
8156 "Enable breakpoint"
8157 } else if is_phantom && !collides_with_existing {
8158 "Set breakpoint"
8159 } else {
8160 "Unset breakpoint"
8161 };
8162 let focus_handle = self.focus_handle.clone();
8163
8164 let meta = if is_rejected {
8165 SharedString::from("No executable code is associated with this line.")
8166 } else if collides_with_existing && !breakpoint.is_disabled() {
8167 SharedString::from(format!(
8168 "{alt_as_text}-click to disable,\nright-click for more options."
8169 ))
8170 } else {
8171 SharedString::from("Right-click for more options.")
8172 };
8173 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8174 .icon_size(IconSize::XSmall)
8175 .size(ui::ButtonSize::None)
8176 .when(is_rejected, |this| {
8177 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8178 })
8179 .icon_color(color)
8180 .style(ButtonStyle::Transparent)
8181 .on_click(cx.listener({
8182 move |editor, event: &ClickEvent, window, cx| {
8183 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8184 BreakpointEditAction::InvertState
8185 } else {
8186 BreakpointEditAction::Toggle
8187 };
8188
8189 window.focus(&editor.focus_handle(cx));
8190 editor.edit_breakpoint_at_anchor(
8191 position,
8192 breakpoint.as_ref().clone(),
8193 edit_action,
8194 cx,
8195 );
8196 }
8197 }))
8198 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8199 editor.set_breakpoint_context_menu(
8200 row,
8201 Some(position),
8202 event.position(),
8203 window,
8204 cx,
8205 );
8206 }))
8207 .tooltip(move |window, cx| {
8208 Tooltip::with_meta_in(
8209 primary_action_text,
8210 Some(&ToggleBreakpoint),
8211 meta.clone(),
8212 &focus_handle,
8213 window,
8214 cx,
8215 )
8216 })
8217 }
8218
8219 fn build_tasks_context(
8220 project: &Entity<Project>,
8221 buffer: &Entity<Buffer>,
8222 buffer_row: u32,
8223 tasks: &Arc<RunnableTasks>,
8224 cx: &mut Context<Self>,
8225 ) -> Task<Option<task::TaskContext>> {
8226 let position = Point::new(buffer_row, tasks.column);
8227 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8228 let location = Location {
8229 buffer: buffer.clone(),
8230 range: range_start..range_start,
8231 };
8232 // Fill in the environmental variables from the tree-sitter captures
8233 let mut captured_task_variables = TaskVariables::default();
8234 for (capture_name, value) in tasks.extra_variables.clone() {
8235 captured_task_variables.insert(
8236 task::VariableName::Custom(capture_name.into()),
8237 value.clone(),
8238 );
8239 }
8240 project.update(cx, |project, cx| {
8241 project.task_store().update(cx, |task_store, cx| {
8242 task_store.task_context_for_location(captured_task_variables, location, cx)
8243 })
8244 })
8245 }
8246
8247 pub fn spawn_nearest_task(
8248 &mut self,
8249 action: &SpawnNearestTask,
8250 window: &mut Window,
8251 cx: &mut Context<Self>,
8252 ) {
8253 let Some((workspace, _)) = self.workspace.clone() else {
8254 return;
8255 };
8256 let Some(project) = self.project.clone() else {
8257 return;
8258 };
8259
8260 // Try to find a closest, enclosing node using tree-sitter that has a task
8261 let Some((buffer, buffer_row, tasks)) = self
8262 .find_enclosing_node_task(cx)
8263 // Or find the task that's closest in row-distance.
8264 .or_else(|| self.find_closest_task(cx))
8265 else {
8266 return;
8267 };
8268
8269 let reveal_strategy = action.reveal;
8270 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8271 cx.spawn_in(window, async move |_, cx| {
8272 let context = task_context.await?;
8273 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8274
8275 let resolved = &mut resolved_task.resolved;
8276 resolved.reveal = reveal_strategy;
8277
8278 workspace
8279 .update_in(cx, |workspace, window, cx| {
8280 workspace.schedule_resolved_task(
8281 task_source_kind,
8282 resolved_task,
8283 false,
8284 window,
8285 cx,
8286 );
8287 })
8288 .ok()
8289 })
8290 .detach();
8291 }
8292
8293 fn find_closest_task(
8294 &mut self,
8295 cx: &mut Context<Self>,
8296 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8297 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8298
8299 let ((buffer_id, row), tasks) = self
8300 .tasks
8301 .iter()
8302 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8303
8304 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8305 let tasks = Arc::new(tasks.to_owned());
8306 Some((buffer, *row, tasks))
8307 }
8308
8309 fn find_enclosing_node_task(
8310 &mut self,
8311 cx: &mut Context<Self>,
8312 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8313 let snapshot = self.buffer.read(cx).snapshot(cx);
8314 let offset = self.selections.newest::<usize>(cx).head();
8315 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8316 let buffer_id = excerpt.buffer().remote_id();
8317
8318 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8319 let mut cursor = layer.node().walk();
8320
8321 while cursor.goto_first_child_for_byte(offset).is_some() {
8322 if cursor.node().end_byte() == offset {
8323 cursor.goto_next_sibling();
8324 }
8325 }
8326
8327 // Ascend to the smallest ancestor that contains the range and has a task.
8328 loop {
8329 let node = cursor.node();
8330 let node_range = node.byte_range();
8331 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8332
8333 // Check if this node contains our offset
8334 if node_range.start <= offset && node_range.end >= offset {
8335 // If it contains offset, check for task
8336 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8337 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8338 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8339 }
8340 }
8341
8342 if !cursor.goto_parent() {
8343 break;
8344 }
8345 }
8346 None
8347 }
8348
8349 fn render_run_indicator(
8350 &self,
8351 _style: &EditorStyle,
8352 is_active: bool,
8353 row: DisplayRow,
8354 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8355 cx: &mut Context<Self>,
8356 ) -> IconButton {
8357 let color = Color::Muted;
8358 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8359
8360 IconButton::new(
8361 ("run_indicator", row.0 as usize),
8362 ui::IconName::PlayOutlined,
8363 )
8364 .shape(ui::IconButtonShape::Square)
8365 .icon_size(IconSize::XSmall)
8366 .icon_color(color)
8367 .toggle_state(is_active)
8368 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8369 let quick_launch = match e {
8370 ClickEvent::Keyboard(_) => true,
8371 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8372 };
8373
8374 window.focus(&editor.focus_handle(cx));
8375 editor.toggle_code_actions(
8376 &ToggleCodeActions {
8377 deployed_from: Some(CodeActionSource::RunMenu(row)),
8378 quick_launch,
8379 },
8380 window,
8381 cx,
8382 );
8383 }))
8384 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8385 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8386 }))
8387 }
8388
8389 pub fn context_menu_visible(&self) -> bool {
8390 !self.edit_prediction_preview_is_active()
8391 && self
8392 .context_menu
8393 .borrow()
8394 .as_ref()
8395 .is_some_and(|menu| menu.visible())
8396 }
8397
8398 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8399 self.context_menu
8400 .borrow()
8401 .as_ref()
8402 .map(|menu| menu.origin())
8403 }
8404
8405 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8406 self.context_menu_options = Some(options);
8407 }
8408
8409 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8410 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8411
8412 fn render_edit_prediction_popover(
8413 &mut self,
8414 text_bounds: &Bounds<Pixels>,
8415 content_origin: gpui::Point<Pixels>,
8416 right_margin: Pixels,
8417 editor_snapshot: &EditorSnapshot,
8418 visible_row_range: Range<DisplayRow>,
8419 scroll_top: f32,
8420 scroll_bottom: f32,
8421 line_layouts: &[LineWithInvisibles],
8422 line_height: Pixels,
8423 scroll_pixel_position: gpui::Point<Pixels>,
8424 newest_selection_head: Option<DisplayPoint>,
8425 editor_width: Pixels,
8426 style: &EditorStyle,
8427 window: &mut Window,
8428 cx: &mut App,
8429 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8430 if self.mode().is_minimap() {
8431 return None;
8432 }
8433 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8434
8435 if self.edit_prediction_visible_in_cursor_popover(true) {
8436 return None;
8437 }
8438
8439 match &active_edit_prediction.completion {
8440 EditPrediction::Move { target, .. } => {
8441 let target_display_point = target.to_display_point(editor_snapshot);
8442
8443 if self.edit_prediction_requires_modifier() {
8444 if !self.edit_prediction_preview_is_active() {
8445 return None;
8446 }
8447
8448 self.render_edit_prediction_modifier_jump_popover(
8449 text_bounds,
8450 content_origin,
8451 visible_row_range,
8452 line_layouts,
8453 line_height,
8454 scroll_pixel_position,
8455 newest_selection_head,
8456 target_display_point,
8457 window,
8458 cx,
8459 )
8460 } else {
8461 self.render_edit_prediction_eager_jump_popover(
8462 text_bounds,
8463 content_origin,
8464 editor_snapshot,
8465 visible_row_range,
8466 scroll_top,
8467 scroll_bottom,
8468 line_height,
8469 scroll_pixel_position,
8470 target_display_point,
8471 editor_width,
8472 window,
8473 cx,
8474 )
8475 }
8476 }
8477 EditPrediction::Edit {
8478 display_mode: EditDisplayMode::Inline,
8479 ..
8480 } => None,
8481 EditPrediction::Edit {
8482 display_mode: EditDisplayMode::TabAccept,
8483 edits,
8484 ..
8485 } => {
8486 let range = &edits.first()?.0;
8487 let target_display_point = range.end.to_display_point(editor_snapshot);
8488
8489 self.render_edit_prediction_end_of_line_popover(
8490 "Accept",
8491 editor_snapshot,
8492 visible_row_range,
8493 target_display_point,
8494 line_height,
8495 scroll_pixel_position,
8496 content_origin,
8497 editor_width,
8498 window,
8499 cx,
8500 )
8501 }
8502 EditPrediction::Edit {
8503 edits,
8504 edit_preview,
8505 display_mode: EditDisplayMode::DiffPopover,
8506 snapshot,
8507 } => self.render_edit_prediction_diff_popover(
8508 text_bounds,
8509 content_origin,
8510 right_margin,
8511 editor_snapshot,
8512 visible_row_range,
8513 line_layouts,
8514 line_height,
8515 scroll_pixel_position,
8516 newest_selection_head,
8517 editor_width,
8518 style,
8519 edits,
8520 edit_preview,
8521 snapshot,
8522 window,
8523 cx,
8524 ),
8525 }
8526 }
8527
8528 fn render_edit_prediction_modifier_jump_popover(
8529 &mut self,
8530 text_bounds: &Bounds<Pixels>,
8531 content_origin: gpui::Point<Pixels>,
8532 visible_row_range: Range<DisplayRow>,
8533 line_layouts: &[LineWithInvisibles],
8534 line_height: Pixels,
8535 scroll_pixel_position: gpui::Point<Pixels>,
8536 newest_selection_head: Option<DisplayPoint>,
8537 target_display_point: DisplayPoint,
8538 window: &mut Window,
8539 cx: &mut App,
8540 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8541 let scrolled_content_origin =
8542 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8543
8544 const SCROLL_PADDING_Y: Pixels = px(12.);
8545
8546 if target_display_point.row() < visible_row_range.start {
8547 return self.render_edit_prediction_scroll_popover(
8548 |_| SCROLL_PADDING_Y,
8549 IconName::ArrowUp,
8550 visible_row_range,
8551 line_layouts,
8552 newest_selection_head,
8553 scrolled_content_origin,
8554 window,
8555 cx,
8556 );
8557 } else if target_display_point.row() >= visible_row_range.end {
8558 return self.render_edit_prediction_scroll_popover(
8559 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8560 IconName::ArrowDown,
8561 visible_row_range,
8562 line_layouts,
8563 newest_selection_head,
8564 scrolled_content_origin,
8565 window,
8566 cx,
8567 );
8568 }
8569
8570 const POLE_WIDTH: Pixels = px(2.);
8571
8572 let line_layout =
8573 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8574 let target_column = target_display_point.column() as usize;
8575
8576 let target_x = line_layout.x_for_index(target_column);
8577 let target_y =
8578 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8579
8580 let flag_on_right = target_x < text_bounds.size.width / 2.;
8581
8582 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8583 border_color.l += 0.001;
8584
8585 let mut element = v_flex()
8586 .items_end()
8587 .when(flag_on_right, |el| el.items_start())
8588 .child(if flag_on_right {
8589 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8590 .rounded_bl(px(0.))
8591 .rounded_tl(px(0.))
8592 .border_l_2()
8593 .border_color(border_color)
8594 } else {
8595 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8596 .rounded_br(px(0.))
8597 .rounded_tr(px(0.))
8598 .border_r_2()
8599 .border_color(border_color)
8600 })
8601 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8602 .into_any();
8603
8604 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8605
8606 let mut origin = scrolled_content_origin + point(target_x, target_y)
8607 - point(
8608 if flag_on_right {
8609 POLE_WIDTH
8610 } else {
8611 size.width - POLE_WIDTH
8612 },
8613 size.height - line_height,
8614 );
8615
8616 origin.x = origin.x.max(content_origin.x);
8617
8618 element.prepaint_at(origin, window, cx);
8619
8620 Some((element, origin))
8621 }
8622
8623 fn render_edit_prediction_scroll_popover(
8624 &mut self,
8625 to_y: impl Fn(Size<Pixels>) -> Pixels,
8626 scroll_icon: IconName,
8627 visible_row_range: Range<DisplayRow>,
8628 line_layouts: &[LineWithInvisibles],
8629 newest_selection_head: Option<DisplayPoint>,
8630 scrolled_content_origin: gpui::Point<Pixels>,
8631 window: &mut Window,
8632 cx: &mut App,
8633 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8634 let mut element = self
8635 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8636 .into_any();
8637
8638 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8639
8640 let cursor = newest_selection_head?;
8641 let cursor_row_layout =
8642 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8643 let cursor_column = cursor.column() as usize;
8644
8645 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8646
8647 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8648
8649 element.prepaint_at(origin, window, cx);
8650 Some((element, origin))
8651 }
8652
8653 fn render_edit_prediction_eager_jump_popover(
8654 &mut self,
8655 text_bounds: &Bounds<Pixels>,
8656 content_origin: gpui::Point<Pixels>,
8657 editor_snapshot: &EditorSnapshot,
8658 visible_row_range: Range<DisplayRow>,
8659 scroll_top: f32,
8660 scroll_bottom: f32,
8661 line_height: Pixels,
8662 scroll_pixel_position: gpui::Point<Pixels>,
8663 target_display_point: DisplayPoint,
8664 editor_width: Pixels,
8665 window: &mut Window,
8666 cx: &mut App,
8667 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8668 if target_display_point.row().as_f32() < scroll_top {
8669 let mut element = self
8670 .render_edit_prediction_line_popover(
8671 "Jump to Edit",
8672 Some(IconName::ArrowUp),
8673 window,
8674 cx,
8675 )?
8676 .into_any();
8677
8678 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8679 let offset = point(
8680 (text_bounds.size.width - size.width) / 2.,
8681 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8682 );
8683
8684 let origin = text_bounds.origin + offset;
8685 element.prepaint_at(origin, window, cx);
8686 Some((element, origin))
8687 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8688 let mut element = self
8689 .render_edit_prediction_line_popover(
8690 "Jump to Edit",
8691 Some(IconName::ArrowDown),
8692 window,
8693 cx,
8694 )?
8695 .into_any();
8696
8697 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8698 let offset = point(
8699 (text_bounds.size.width - size.width) / 2.,
8700 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8701 );
8702
8703 let origin = text_bounds.origin + offset;
8704 element.prepaint_at(origin, window, cx);
8705 Some((element, origin))
8706 } else {
8707 self.render_edit_prediction_end_of_line_popover(
8708 "Jump to Edit",
8709 editor_snapshot,
8710 visible_row_range,
8711 target_display_point,
8712 line_height,
8713 scroll_pixel_position,
8714 content_origin,
8715 editor_width,
8716 window,
8717 cx,
8718 )
8719 }
8720 }
8721
8722 fn render_edit_prediction_end_of_line_popover(
8723 self: &mut Editor,
8724 label: &'static str,
8725 editor_snapshot: &EditorSnapshot,
8726 visible_row_range: Range<DisplayRow>,
8727 target_display_point: DisplayPoint,
8728 line_height: Pixels,
8729 scroll_pixel_position: gpui::Point<Pixels>,
8730 content_origin: gpui::Point<Pixels>,
8731 editor_width: Pixels,
8732 window: &mut Window,
8733 cx: &mut App,
8734 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8735 let target_line_end = DisplayPoint::new(
8736 target_display_point.row(),
8737 editor_snapshot.line_len(target_display_point.row()),
8738 );
8739
8740 let mut element = self
8741 .render_edit_prediction_line_popover(label, None, window, cx)?
8742 .into_any();
8743
8744 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8745
8746 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8747
8748 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8749 let mut origin = start_point
8750 + line_origin
8751 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8752 origin.x = origin.x.max(content_origin.x);
8753
8754 let max_x = content_origin.x + editor_width - size.width;
8755
8756 if origin.x > max_x {
8757 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8758
8759 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8760 origin.y += offset;
8761 IconName::ArrowUp
8762 } else {
8763 origin.y -= offset;
8764 IconName::ArrowDown
8765 };
8766
8767 element = self
8768 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8769 .into_any();
8770
8771 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8772
8773 origin.x = content_origin.x + editor_width - size.width - px(2.);
8774 }
8775
8776 element.prepaint_at(origin, window, cx);
8777 Some((element, origin))
8778 }
8779
8780 fn render_edit_prediction_diff_popover(
8781 self: &Editor,
8782 text_bounds: &Bounds<Pixels>,
8783 content_origin: gpui::Point<Pixels>,
8784 right_margin: Pixels,
8785 editor_snapshot: &EditorSnapshot,
8786 visible_row_range: Range<DisplayRow>,
8787 line_layouts: &[LineWithInvisibles],
8788 line_height: Pixels,
8789 scroll_pixel_position: gpui::Point<Pixels>,
8790 newest_selection_head: Option<DisplayPoint>,
8791 editor_width: Pixels,
8792 style: &EditorStyle,
8793 edits: &Vec<(Range<Anchor>, String)>,
8794 edit_preview: &Option<language::EditPreview>,
8795 snapshot: &language::BufferSnapshot,
8796 window: &mut Window,
8797 cx: &mut App,
8798 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8799 let edit_start = edits
8800 .first()
8801 .unwrap()
8802 .0
8803 .start
8804 .to_display_point(editor_snapshot);
8805 let edit_end = edits
8806 .last()
8807 .unwrap()
8808 .0
8809 .end
8810 .to_display_point(editor_snapshot);
8811
8812 let is_visible = visible_row_range.contains(&edit_start.row())
8813 || visible_row_range.contains(&edit_end.row());
8814 if !is_visible {
8815 return None;
8816 }
8817
8818 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8819 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8820 } else {
8821 // Fallback for providers without edit_preview
8822 crate::edit_prediction_fallback_text(edits, cx)
8823 };
8824
8825 let styled_text = highlighted_edits.to_styled_text(&style.text);
8826 let line_count = highlighted_edits.text.lines().count();
8827
8828 const BORDER_WIDTH: Pixels = px(1.);
8829
8830 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8831 let has_keybind = keybind.is_some();
8832
8833 let mut element = h_flex()
8834 .items_start()
8835 .child(
8836 h_flex()
8837 .bg(cx.theme().colors().editor_background)
8838 .border(BORDER_WIDTH)
8839 .shadow_xs()
8840 .border_color(cx.theme().colors().border)
8841 .rounded_l_lg()
8842 .when(line_count > 1, |el| el.rounded_br_lg())
8843 .pr_1()
8844 .child(styled_text),
8845 )
8846 .child(
8847 h_flex()
8848 .h(line_height + BORDER_WIDTH * 2.)
8849 .px_1p5()
8850 .gap_1()
8851 // Workaround: For some reason, there's a gap if we don't do this
8852 .ml(-BORDER_WIDTH)
8853 .shadow(vec![gpui::BoxShadow {
8854 color: gpui::black().opacity(0.05),
8855 offset: point(px(1.), px(1.)),
8856 blur_radius: px(2.),
8857 spread_radius: px(0.),
8858 }])
8859 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8860 .border(BORDER_WIDTH)
8861 .border_color(cx.theme().colors().border)
8862 .rounded_r_lg()
8863 .id("edit_prediction_diff_popover_keybind")
8864 .when(!has_keybind, |el| {
8865 let status_colors = cx.theme().status();
8866
8867 el.bg(status_colors.error_background)
8868 .border_color(status_colors.error.opacity(0.6))
8869 .child(Icon::new(IconName::Info).color(Color::Error))
8870 .cursor_default()
8871 .hoverable_tooltip(move |_window, cx| {
8872 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8873 })
8874 })
8875 .children(keybind),
8876 )
8877 .into_any();
8878
8879 let longest_row =
8880 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8881 let longest_line_width = if visible_row_range.contains(&longest_row) {
8882 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8883 } else {
8884 layout_line(
8885 longest_row,
8886 editor_snapshot,
8887 style,
8888 editor_width,
8889 |_| false,
8890 window,
8891 cx,
8892 )
8893 .width
8894 };
8895
8896 let viewport_bounds =
8897 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8898 right: -right_margin,
8899 ..Default::default()
8900 });
8901
8902 let x_after_longest =
8903 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8904 - scroll_pixel_position.x;
8905
8906 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8907
8908 // Fully visible if it can be displayed within the window (allow overlapping other
8909 // panes). However, this is only allowed if the popover starts within text_bounds.
8910 let can_position_to_the_right = x_after_longest < text_bounds.right()
8911 && x_after_longest + element_bounds.width < viewport_bounds.right();
8912
8913 let mut origin = if can_position_to_the_right {
8914 point(
8915 x_after_longest,
8916 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8917 - scroll_pixel_position.y,
8918 )
8919 } else {
8920 let cursor_row = newest_selection_head.map(|head| head.row());
8921 let above_edit = edit_start
8922 .row()
8923 .0
8924 .checked_sub(line_count as u32)
8925 .map(DisplayRow);
8926 let below_edit = Some(edit_end.row() + 1);
8927 let above_cursor =
8928 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8929 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8930
8931 // Place the edit popover adjacent to the edit if there is a location
8932 // available that is onscreen and does not obscure the cursor. Otherwise,
8933 // place it adjacent to the cursor.
8934 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8935 .into_iter()
8936 .flatten()
8937 .find(|&start_row| {
8938 let end_row = start_row + line_count as u32;
8939 visible_row_range.contains(&start_row)
8940 && visible_row_range.contains(&end_row)
8941 && cursor_row
8942 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
8943 })?;
8944
8945 content_origin
8946 + point(
8947 -scroll_pixel_position.x,
8948 row_target.as_f32() * line_height - scroll_pixel_position.y,
8949 )
8950 };
8951
8952 origin.x -= BORDER_WIDTH;
8953
8954 window.defer_draw(element, origin, 1);
8955
8956 // Do not return an element, since it will already be drawn due to defer_draw.
8957 None
8958 }
8959
8960 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8961 px(30.)
8962 }
8963
8964 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8965 if self.read_only(cx) {
8966 cx.theme().players().read_only()
8967 } else {
8968 self.style.as_ref().unwrap().local_player
8969 }
8970 }
8971
8972 fn render_edit_prediction_accept_keybind(
8973 &self,
8974 window: &mut Window,
8975 cx: &App,
8976 ) -> Option<AnyElement> {
8977 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8978 let accept_keystroke = accept_binding.keystroke()?;
8979
8980 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8981
8982 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8983 Color::Accent
8984 } else {
8985 Color::Muted
8986 };
8987
8988 h_flex()
8989 .px_0p5()
8990 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8991 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8992 .text_size(TextSize::XSmall.rems(cx))
8993 .child(h_flex().children(ui::render_modifiers(
8994 &accept_keystroke.modifiers,
8995 PlatformStyle::platform(),
8996 Some(modifiers_color),
8997 Some(IconSize::XSmall.rems().into()),
8998 true,
8999 )))
9000 .when(is_platform_style_mac, |parent| {
9001 parent.child(accept_keystroke.key.clone())
9002 })
9003 .when(!is_platform_style_mac, |parent| {
9004 parent.child(
9005 Key::new(
9006 util::capitalize(&accept_keystroke.key),
9007 Some(Color::Default),
9008 )
9009 .size(Some(IconSize::XSmall.rems().into())),
9010 )
9011 })
9012 .into_any()
9013 .into()
9014 }
9015
9016 fn render_edit_prediction_line_popover(
9017 &self,
9018 label: impl Into<SharedString>,
9019 icon: Option<IconName>,
9020 window: &mut Window,
9021 cx: &App,
9022 ) -> Option<Stateful<Div>> {
9023 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9024
9025 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9026 let has_keybind = keybind.is_some();
9027
9028 let result = h_flex()
9029 .id("ep-line-popover")
9030 .py_0p5()
9031 .pl_1()
9032 .pr(padding_right)
9033 .gap_1()
9034 .rounded_md()
9035 .border_1()
9036 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9037 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9038 .shadow_xs()
9039 .when(!has_keybind, |el| {
9040 let status_colors = cx.theme().status();
9041
9042 el.bg(status_colors.error_background)
9043 .border_color(status_colors.error.opacity(0.6))
9044 .pl_2()
9045 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9046 .cursor_default()
9047 .hoverable_tooltip(move |_window, cx| {
9048 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9049 })
9050 })
9051 .children(keybind)
9052 .child(
9053 Label::new(label)
9054 .size(LabelSize::Small)
9055 .when(!has_keybind, |el| {
9056 el.color(cx.theme().status().error.into()).strikethrough()
9057 }),
9058 )
9059 .when(!has_keybind, |el| {
9060 el.child(
9061 h_flex().ml_1().child(
9062 Icon::new(IconName::Info)
9063 .size(IconSize::Small)
9064 .color(cx.theme().status().error.into()),
9065 ),
9066 )
9067 })
9068 .when_some(icon, |element, icon| {
9069 element.child(
9070 div()
9071 .mt(px(1.5))
9072 .child(Icon::new(icon).size(IconSize::Small)),
9073 )
9074 });
9075
9076 Some(result)
9077 }
9078
9079 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9080 let accent_color = cx.theme().colors().text_accent;
9081 let editor_bg_color = cx.theme().colors().editor_background;
9082 editor_bg_color.blend(accent_color.opacity(0.1))
9083 }
9084
9085 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9086 let accent_color = cx.theme().colors().text_accent;
9087 let editor_bg_color = cx.theme().colors().editor_background;
9088 editor_bg_color.blend(accent_color.opacity(0.6))
9089 }
9090 fn get_prediction_provider_icon_name(
9091 provider: &Option<RegisteredEditPredictionProvider>,
9092 ) -> IconName {
9093 match provider {
9094 Some(provider) => match provider.provider.name() {
9095 "copilot" => IconName::Copilot,
9096 "supermaven" => IconName::Supermaven,
9097 _ => IconName::ZedPredict,
9098 },
9099 None => IconName::ZedPredict,
9100 }
9101 }
9102
9103 fn render_edit_prediction_cursor_popover(
9104 &self,
9105 min_width: Pixels,
9106 max_width: Pixels,
9107 cursor_point: Point,
9108 style: &EditorStyle,
9109 accept_keystroke: Option<&gpui::Keystroke>,
9110 _window: &Window,
9111 cx: &mut Context<Editor>,
9112 ) -> Option<AnyElement> {
9113 let provider = self.edit_prediction_provider.as_ref()?;
9114 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9115
9116 let is_refreshing = provider.provider.is_refreshing(cx);
9117
9118 fn pending_completion_container(icon: IconName) -> Div {
9119 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9120 }
9121
9122 let completion = match &self.active_edit_prediction {
9123 Some(prediction) => {
9124 if !self.has_visible_completions_menu() {
9125 const RADIUS: Pixels = px(6.);
9126 const BORDER_WIDTH: Pixels = px(1.);
9127
9128 return Some(
9129 h_flex()
9130 .elevation_2(cx)
9131 .border(BORDER_WIDTH)
9132 .border_color(cx.theme().colors().border)
9133 .when(accept_keystroke.is_none(), |el| {
9134 el.border_color(cx.theme().status().error)
9135 })
9136 .rounded(RADIUS)
9137 .rounded_tl(px(0.))
9138 .overflow_hidden()
9139 .child(div().px_1p5().child(match &prediction.completion {
9140 EditPrediction::Move { target, snapshot } => {
9141 use text::ToPoint as _;
9142 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9143 {
9144 Icon::new(IconName::ZedPredictDown)
9145 } else {
9146 Icon::new(IconName::ZedPredictUp)
9147 }
9148 }
9149 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9150 }))
9151 .child(
9152 h_flex()
9153 .gap_1()
9154 .py_1()
9155 .px_2()
9156 .rounded_r(RADIUS - BORDER_WIDTH)
9157 .border_l_1()
9158 .border_color(cx.theme().colors().border)
9159 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9160 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9161 el.child(
9162 Label::new("Hold")
9163 .size(LabelSize::Small)
9164 .when(accept_keystroke.is_none(), |el| {
9165 el.strikethrough()
9166 })
9167 .line_height_style(LineHeightStyle::UiLabel),
9168 )
9169 })
9170 .id("edit_prediction_cursor_popover_keybind")
9171 .when(accept_keystroke.is_none(), |el| {
9172 let status_colors = cx.theme().status();
9173
9174 el.bg(status_colors.error_background)
9175 .border_color(status_colors.error.opacity(0.6))
9176 .child(Icon::new(IconName::Info).color(Color::Error))
9177 .cursor_default()
9178 .hoverable_tooltip(move |_window, cx| {
9179 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9180 .into()
9181 })
9182 })
9183 .when_some(
9184 accept_keystroke.as_ref(),
9185 |el, accept_keystroke| {
9186 el.child(h_flex().children(ui::render_modifiers(
9187 &accept_keystroke.modifiers,
9188 PlatformStyle::platform(),
9189 Some(Color::Default),
9190 Some(IconSize::XSmall.rems().into()),
9191 false,
9192 )))
9193 },
9194 ),
9195 )
9196 .into_any(),
9197 );
9198 }
9199
9200 self.render_edit_prediction_cursor_popover_preview(
9201 prediction,
9202 cursor_point,
9203 style,
9204 cx,
9205 )?
9206 }
9207
9208 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9209 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9210 stale_completion,
9211 cursor_point,
9212 style,
9213 cx,
9214 )?,
9215
9216 None => pending_completion_container(provider_icon)
9217 .child(Label::new("...").size(LabelSize::Small)),
9218 },
9219
9220 None => pending_completion_container(provider_icon)
9221 .child(Label::new("...").size(LabelSize::Small)),
9222 };
9223
9224 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9225 completion
9226 .with_animation(
9227 "loading-completion",
9228 Animation::new(Duration::from_secs(2))
9229 .repeat()
9230 .with_easing(pulsating_between(0.4, 0.8)),
9231 |label, delta| label.opacity(delta),
9232 )
9233 .into_any_element()
9234 } else {
9235 completion.into_any_element()
9236 };
9237
9238 let has_completion = self.active_edit_prediction.is_some();
9239
9240 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9241 Some(
9242 h_flex()
9243 .min_w(min_width)
9244 .max_w(max_width)
9245 .flex_1()
9246 .elevation_2(cx)
9247 .border_color(cx.theme().colors().border)
9248 .child(
9249 div()
9250 .flex_1()
9251 .py_1()
9252 .px_2()
9253 .overflow_hidden()
9254 .child(completion),
9255 )
9256 .when_some(accept_keystroke, |el, accept_keystroke| {
9257 if !accept_keystroke.modifiers.modified() {
9258 return el;
9259 }
9260
9261 el.child(
9262 h_flex()
9263 .h_full()
9264 .border_l_1()
9265 .rounded_r_lg()
9266 .border_color(cx.theme().colors().border)
9267 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9268 .gap_1()
9269 .py_1()
9270 .px_2()
9271 .child(
9272 h_flex()
9273 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9274 .when(is_platform_style_mac, |parent| parent.gap_1())
9275 .child(h_flex().children(ui::render_modifiers(
9276 &accept_keystroke.modifiers,
9277 PlatformStyle::platform(),
9278 Some(if !has_completion {
9279 Color::Muted
9280 } else {
9281 Color::Default
9282 }),
9283 None,
9284 false,
9285 ))),
9286 )
9287 .child(Label::new("Preview").into_any_element())
9288 .opacity(if has_completion { 1.0 } else { 0.4 }),
9289 )
9290 })
9291 .into_any(),
9292 )
9293 }
9294
9295 fn render_edit_prediction_cursor_popover_preview(
9296 &self,
9297 completion: &EditPredictionState,
9298 cursor_point: Point,
9299 style: &EditorStyle,
9300 cx: &mut Context<Editor>,
9301 ) -> Option<Div> {
9302 use text::ToPoint as _;
9303
9304 fn render_relative_row_jump(
9305 prefix: impl Into<String>,
9306 current_row: u32,
9307 target_row: u32,
9308 ) -> Div {
9309 let (row_diff, arrow) = if target_row < current_row {
9310 (current_row - target_row, IconName::ArrowUp)
9311 } else {
9312 (target_row - current_row, IconName::ArrowDown)
9313 };
9314
9315 h_flex()
9316 .child(
9317 Label::new(format!("{}{}", prefix.into(), row_diff))
9318 .color(Color::Muted)
9319 .size(LabelSize::Small),
9320 )
9321 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9322 }
9323
9324 let supports_jump = self
9325 .edit_prediction_provider
9326 .as_ref()
9327 .map(|provider| provider.provider.supports_jump_to_edit())
9328 .unwrap_or(true);
9329
9330 match &completion.completion {
9331 EditPrediction::Move {
9332 target, snapshot, ..
9333 } => {
9334 if !supports_jump {
9335 return None;
9336 }
9337
9338 Some(
9339 h_flex()
9340 .px_2()
9341 .gap_2()
9342 .flex_1()
9343 .child(
9344 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9345 Icon::new(IconName::ZedPredictDown)
9346 } else {
9347 Icon::new(IconName::ZedPredictUp)
9348 },
9349 )
9350 .child(Label::new("Jump to Edit")),
9351 )
9352 }
9353
9354 EditPrediction::Edit {
9355 edits,
9356 edit_preview,
9357 snapshot,
9358 display_mode: _,
9359 } => {
9360 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9361
9362 let (highlighted_edits, has_more_lines) =
9363 if let Some(edit_preview) = edit_preview.as_ref() {
9364 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9365 .first_line_preview()
9366 } else {
9367 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9368 };
9369
9370 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9371 .with_default_highlights(&style.text, highlighted_edits.highlights);
9372
9373 let preview = h_flex()
9374 .gap_1()
9375 .min_w_16()
9376 .child(styled_text)
9377 .when(has_more_lines, |parent| parent.child("…"));
9378
9379 let left = if supports_jump && first_edit_row != cursor_point.row {
9380 render_relative_row_jump("", cursor_point.row, first_edit_row)
9381 .into_any_element()
9382 } else {
9383 let icon_name =
9384 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9385 Icon::new(icon_name).into_any_element()
9386 };
9387
9388 Some(
9389 h_flex()
9390 .h_full()
9391 .flex_1()
9392 .gap_2()
9393 .pr_1()
9394 .overflow_x_hidden()
9395 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9396 .child(left)
9397 .child(preview),
9398 )
9399 }
9400 }
9401 }
9402
9403 pub fn render_context_menu(
9404 &self,
9405 style: &EditorStyle,
9406 max_height_in_lines: u32,
9407 window: &mut Window,
9408 cx: &mut Context<Editor>,
9409 ) -> Option<AnyElement> {
9410 let menu = self.context_menu.borrow();
9411 let menu = menu.as_ref()?;
9412 if !menu.visible() {
9413 return None;
9414 };
9415 Some(menu.render(style, max_height_in_lines, window, cx))
9416 }
9417
9418 fn render_context_menu_aside(
9419 &mut self,
9420 max_size: Size<Pixels>,
9421 window: &mut Window,
9422 cx: &mut Context<Editor>,
9423 ) -> Option<AnyElement> {
9424 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9425 if menu.visible() {
9426 menu.render_aside(max_size, window, cx)
9427 } else {
9428 None
9429 }
9430 })
9431 }
9432
9433 fn hide_context_menu(
9434 &mut self,
9435 window: &mut Window,
9436 cx: &mut Context<Self>,
9437 ) -> Option<CodeContextMenu> {
9438 cx.notify();
9439 self.completion_tasks.clear();
9440 let context_menu = self.context_menu.borrow_mut().take();
9441 self.stale_edit_prediction_in_menu.take();
9442 self.update_visible_edit_prediction(window, cx);
9443 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9444 && let Some(completion_provider) = &self.completion_provider
9445 {
9446 completion_provider.selection_changed(None, window, cx);
9447 }
9448 context_menu
9449 }
9450
9451 fn show_snippet_choices(
9452 &mut self,
9453 choices: &Vec<String>,
9454 selection: Range<Anchor>,
9455 cx: &mut Context<Self>,
9456 ) {
9457 let Some((_, buffer, _)) = self
9458 .buffer()
9459 .read(cx)
9460 .excerpt_containing(selection.start, cx)
9461 else {
9462 return;
9463 };
9464 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9465 else {
9466 return;
9467 };
9468 if buffer != end_buffer {
9469 log::error!("expected anchor range to have matching buffer IDs");
9470 return;
9471 }
9472
9473 let id = post_inc(&mut self.next_completion_id);
9474 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9475 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9476 CompletionsMenu::new_snippet_choices(
9477 id,
9478 true,
9479 choices,
9480 selection,
9481 buffer,
9482 snippet_sort_order,
9483 ),
9484 ));
9485 }
9486
9487 pub fn insert_snippet(
9488 &mut self,
9489 insertion_ranges: &[Range<usize>],
9490 snippet: Snippet,
9491 window: &mut Window,
9492 cx: &mut Context<Self>,
9493 ) -> Result<()> {
9494 struct Tabstop<T> {
9495 is_end_tabstop: bool,
9496 ranges: Vec<Range<T>>,
9497 choices: Option<Vec<String>>,
9498 }
9499
9500 let tabstops = self.buffer.update(cx, |buffer, cx| {
9501 let snippet_text: Arc<str> = snippet.text.clone().into();
9502 let edits = insertion_ranges
9503 .iter()
9504 .cloned()
9505 .map(|range| (range, snippet_text.clone()));
9506 let autoindent_mode = AutoindentMode::Block {
9507 original_indent_columns: Vec::new(),
9508 };
9509 buffer.edit(edits, Some(autoindent_mode), cx);
9510
9511 let snapshot = &*buffer.read(cx);
9512 let snippet = &snippet;
9513 snippet
9514 .tabstops
9515 .iter()
9516 .map(|tabstop| {
9517 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9518 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9519 });
9520 let mut tabstop_ranges = tabstop
9521 .ranges
9522 .iter()
9523 .flat_map(|tabstop_range| {
9524 let mut delta = 0_isize;
9525 insertion_ranges.iter().map(move |insertion_range| {
9526 let insertion_start = insertion_range.start as isize + delta;
9527 delta +=
9528 snippet.text.len() as isize - insertion_range.len() as isize;
9529
9530 let start = ((insertion_start + tabstop_range.start) as usize)
9531 .min(snapshot.len());
9532 let end = ((insertion_start + tabstop_range.end) as usize)
9533 .min(snapshot.len());
9534 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9535 })
9536 })
9537 .collect::<Vec<_>>();
9538 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9539
9540 Tabstop {
9541 is_end_tabstop,
9542 ranges: tabstop_ranges,
9543 choices: tabstop.choices.clone(),
9544 }
9545 })
9546 .collect::<Vec<_>>()
9547 });
9548 if let Some(tabstop) = tabstops.first() {
9549 self.change_selections(Default::default(), window, cx, |s| {
9550 // Reverse order so that the first range is the newest created selection.
9551 // Completions will use it and autoscroll will prioritize it.
9552 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9553 });
9554
9555 if let Some(choices) = &tabstop.choices
9556 && let Some(selection) = tabstop.ranges.first()
9557 {
9558 self.show_snippet_choices(choices, selection.clone(), cx)
9559 }
9560
9561 // If we're already at the last tabstop and it's at the end of the snippet,
9562 // we're done, we don't need to keep the state around.
9563 if !tabstop.is_end_tabstop {
9564 let choices = tabstops
9565 .iter()
9566 .map(|tabstop| tabstop.choices.clone())
9567 .collect();
9568
9569 let ranges = tabstops
9570 .into_iter()
9571 .map(|tabstop| tabstop.ranges)
9572 .collect::<Vec<_>>();
9573
9574 self.snippet_stack.push(SnippetState {
9575 active_index: 0,
9576 ranges,
9577 choices,
9578 });
9579 }
9580
9581 // Check whether the just-entered snippet ends with an auto-closable bracket.
9582 if self.autoclose_regions.is_empty() {
9583 let snapshot = self.buffer.read(cx).snapshot(cx);
9584 let mut all_selections = self.selections.all::<Point>(cx);
9585 for selection in &mut all_selections {
9586 let selection_head = selection.head();
9587 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9588 continue;
9589 };
9590
9591 let mut bracket_pair = None;
9592 let max_lookup_length = scope
9593 .brackets()
9594 .map(|(pair, _)| {
9595 pair.start
9596 .as_str()
9597 .chars()
9598 .count()
9599 .max(pair.end.as_str().chars().count())
9600 })
9601 .max();
9602 if let Some(max_lookup_length) = max_lookup_length {
9603 let next_text = snapshot
9604 .chars_at(selection_head)
9605 .take(max_lookup_length)
9606 .collect::<String>();
9607 let prev_text = snapshot
9608 .reversed_chars_at(selection_head)
9609 .take(max_lookup_length)
9610 .collect::<String>();
9611
9612 for (pair, enabled) in scope.brackets() {
9613 if enabled
9614 && pair.close
9615 && prev_text.starts_with(pair.start.as_str())
9616 && next_text.starts_with(pair.end.as_str())
9617 {
9618 bracket_pair = Some(pair.clone());
9619 break;
9620 }
9621 }
9622 }
9623
9624 if let Some(pair) = bracket_pair {
9625 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9626 let autoclose_enabled =
9627 self.use_autoclose && snapshot_settings.use_autoclose;
9628 if autoclose_enabled {
9629 let start = snapshot.anchor_after(selection_head);
9630 let end = snapshot.anchor_after(selection_head);
9631 self.autoclose_regions.push(AutocloseRegion {
9632 selection_id: selection.id,
9633 range: start..end,
9634 pair,
9635 });
9636 }
9637 }
9638 }
9639 }
9640 }
9641 Ok(())
9642 }
9643
9644 pub fn move_to_next_snippet_tabstop(
9645 &mut self,
9646 window: &mut Window,
9647 cx: &mut Context<Self>,
9648 ) -> bool {
9649 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9650 }
9651
9652 pub fn move_to_prev_snippet_tabstop(
9653 &mut self,
9654 window: &mut Window,
9655 cx: &mut Context<Self>,
9656 ) -> bool {
9657 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9658 }
9659
9660 pub fn move_to_snippet_tabstop(
9661 &mut self,
9662 bias: Bias,
9663 window: &mut Window,
9664 cx: &mut Context<Self>,
9665 ) -> bool {
9666 if let Some(mut snippet) = self.snippet_stack.pop() {
9667 match bias {
9668 Bias::Left => {
9669 if snippet.active_index > 0 {
9670 snippet.active_index -= 1;
9671 } else {
9672 self.snippet_stack.push(snippet);
9673 return false;
9674 }
9675 }
9676 Bias::Right => {
9677 if snippet.active_index + 1 < snippet.ranges.len() {
9678 snippet.active_index += 1;
9679 } else {
9680 self.snippet_stack.push(snippet);
9681 return false;
9682 }
9683 }
9684 }
9685 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9686 self.change_selections(Default::default(), window, cx, |s| {
9687 // Reverse order so that the first range is the newest created selection.
9688 // Completions will use it and autoscroll will prioritize it.
9689 s.select_ranges(current_ranges.iter().rev().cloned())
9690 });
9691
9692 if let Some(choices) = &snippet.choices[snippet.active_index]
9693 && let Some(selection) = current_ranges.first()
9694 {
9695 self.show_snippet_choices(choices, selection.clone(), cx);
9696 }
9697
9698 // If snippet state is not at the last tabstop, push it back on the stack
9699 if snippet.active_index + 1 < snippet.ranges.len() {
9700 self.snippet_stack.push(snippet);
9701 }
9702 return true;
9703 }
9704 }
9705
9706 false
9707 }
9708
9709 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9710 self.transact(window, cx, |this, window, cx| {
9711 this.select_all(&SelectAll, window, cx);
9712 this.insert("", window, cx);
9713 });
9714 }
9715
9716 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9717 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9718 self.transact(window, cx, |this, window, cx| {
9719 this.select_autoclose_pair(window, cx);
9720 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9721 if !this.linked_edit_ranges.is_empty() {
9722 let selections = this.selections.all::<MultiBufferPoint>(cx);
9723 let snapshot = this.buffer.read(cx).snapshot(cx);
9724
9725 for selection in selections.iter() {
9726 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9727 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9728 if selection_start.buffer_id != selection_end.buffer_id {
9729 continue;
9730 }
9731 if let Some(ranges) =
9732 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9733 {
9734 for (buffer, entries) in ranges {
9735 linked_ranges.entry(buffer).or_default().extend(entries);
9736 }
9737 }
9738 }
9739 }
9740
9741 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9742 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9743 for selection in &mut selections {
9744 if selection.is_empty() {
9745 let old_head = selection.head();
9746 let mut new_head =
9747 movement::left(&display_map, old_head.to_display_point(&display_map))
9748 .to_point(&display_map);
9749 if let Some((buffer, line_buffer_range)) = display_map
9750 .buffer_snapshot
9751 .buffer_line_for_row(MultiBufferRow(old_head.row))
9752 {
9753 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9754 let indent_len = match indent_size.kind {
9755 IndentKind::Space => {
9756 buffer.settings_at(line_buffer_range.start, cx).tab_size
9757 }
9758 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9759 };
9760 if old_head.column <= indent_size.len && old_head.column > 0 {
9761 let indent_len = indent_len.get();
9762 new_head = cmp::min(
9763 new_head,
9764 MultiBufferPoint::new(
9765 old_head.row,
9766 ((old_head.column - 1) / indent_len) * indent_len,
9767 ),
9768 );
9769 }
9770 }
9771
9772 selection.set_head(new_head, SelectionGoal::None);
9773 }
9774 }
9775
9776 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9777 this.insert("", window, cx);
9778 let empty_str: Arc<str> = Arc::from("");
9779 for (buffer, edits) in linked_ranges {
9780 let snapshot = buffer.read(cx).snapshot();
9781 use text::ToPoint as TP;
9782
9783 let edits = edits
9784 .into_iter()
9785 .map(|range| {
9786 let end_point = TP::to_point(&range.end, &snapshot);
9787 let mut start_point = TP::to_point(&range.start, &snapshot);
9788
9789 if end_point == start_point {
9790 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9791 .saturating_sub(1);
9792 start_point =
9793 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9794 };
9795
9796 (start_point..end_point, empty_str.clone())
9797 })
9798 .sorted_by_key(|(range, _)| range.start)
9799 .collect::<Vec<_>>();
9800 buffer.update(cx, |this, cx| {
9801 this.edit(edits, None, cx);
9802 })
9803 }
9804 this.refresh_edit_prediction(true, false, window, cx);
9805 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9806 });
9807 }
9808
9809 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9810 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9811 self.transact(window, cx, |this, window, cx| {
9812 this.change_selections(Default::default(), window, cx, |s| {
9813 s.move_with(|map, selection| {
9814 if selection.is_empty() {
9815 let cursor = movement::right(map, selection.head());
9816 selection.end = cursor;
9817 selection.reversed = true;
9818 selection.goal = SelectionGoal::None;
9819 }
9820 })
9821 });
9822 this.insert("", window, cx);
9823 this.refresh_edit_prediction(true, false, window, cx);
9824 });
9825 }
9826
9827 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9828 if self.mode.is_single_line() {
9829 cx.propagate();
9830 return;
9831 }
9832
9833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9834 if self.move_to_prev_snippet_tabstop(window, cx) {
9835 return;
9836 }
9837 self.outdent(&Outdent, window, cx);
9838 }
9839
9840 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9841 if self.mode.is_single_line() {
9842 cx.propagate();
9843 return;
9844 }
9845
9846 if self.move_to_next_snippet_tabstop(window, cx) {
9847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9848 return;
9849 }
9850 if self.read_only(cx) {
9851 return;
9852 }
9853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9854 let mut selections = self.selections.all_adjusted(cx);
9855 let buffer = self.buffer.read(cx);
9856 let snapshot = buffer.snapshot(cx);
9857 let rows_iter = selections.iter().map(|s| s.head().row);
9858 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9859
9860 let has_some_cursor_in_whitespace = selections
9861 .iter()
9862 .filter(|selection| selection.is_empty())
9863 .any(|selection| {
9864 let cursor = selection.head();
9865 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9866 cursor.column < current_indent.len
9867 });
9868
9869 let mut edits = Vec::new();
9870 let mut prev_edited_row = 0;
9871 let mut row_delta = 0;
9872 for selection in &mut selections {
9873 if selection.start.row != prev_edited_row {
9874 row_delta = 0;
9875 }
9876 prev_edited_row = selection.end.row;
9877
9878 // If the selection is non-empty, then increase the indentation of the selected lines.
9879 if !selection.is_empty() {
9880 row_delta =
9881 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9882 continue;
9883 }
9884
9885 let cursor = selection.head();
9886 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9887 if let Some(suggested_indent) =
9888 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9889 {
9890 // Don't do anything if already at suggested indent
9891 // and there is any other cursor which is not
9892 if has_some_cursor_in_whitespace
9893 && cursor.column == current_indent.len
9894 && current_indent.len == suggested_indent.len
9895 {
9896 continue;
9897 }
9898
9899 // Adjust line and move cursor to suggested indent
9900 // if cursor is not at suggested indent
9901 if cursor.column < suggested_indent.len
9902 && cursor.column <= current_indent.len
9903 && current_indent.len <= suggested_indent.len
9904 {
9905 selection.start = Point::new(cursor.row, suggested_indent.len);
9906 selection.end = selection.start;
9907 if row_delta == 0 {
9908 edits.extend(Buffer::edit_for_indent_size_adjustment(
9909 cursor.row,
9910 current_indent,
9911 suggested_indent,
9912 ));
9913 row_delta = suggested_indent.len - current_indent.len;
9914 }
9915 continue;
9916 }
9917
9918 // If current indent is more than suggested indent
9919 // only move cursor to current indent and skip indent
9920 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9921 selection.start = Point::new(cursor.row, current_indent.len);
9922 selection.end = selection.start;
9923 continue;
9924 }
9925 }
9926
9927 // Otherwise, insert a hard or soft tab.
9928 let settings = buffer.language_settings_at(cursor, cx);
9929 let tab_size = if settings.hard_tabs {
9930 IndentSize::tab()
9931 } else {
9932 let tab_size = settings.tab_size.get();
9933 let indent_remainder = snapshot
9934 .text_for_range(Point::new(cursor.row, 0)..cursor)
9935 .flat_map(str::chars)
9936 .fold(row_delta % tab_size, |counter: u32, c| {
9937 if c == '\t' {
9938 0
9939 } else {
9940 (counter + 1) % tab_size
9941 }
9942 });
9943
9944 let chars_to_next_tab_stop = tab_size - indent_remainder;
9945 IndentSize::spaces(chars_to_next_tab_stop)
9946 };
9947 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9948 selection.end = selection.start;
9949 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9950 row_delta += tab_size.len;
9951 }
9952
9953 self.transact(window, cx, |this, window, cx| {
9954 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9955 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9956 this.refresh_edit_prediction(true, false, window, cx);
9957 });
9958 }
9959
9960 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9961 if self.read_only(cx) {
9962 return;
9963 }
9964 if self.mode.is_single_line() {
9965 cx.propagate();
9966 return;
9967 }
9968
9969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9970 let mut selections = self.selections.all::<Point>(cx);
9971 let mut prev_edited_row = 0;
9972 let mut row_delta = 0;
9973 let mut edits = Vec::new();
9974 let buffer = self.buffer.read(cx);
9975 let snapshot = buffer.snapshot(cx);
9976 for selection in &mut selections {
9977 if selection.start.row != prev_edited_row {
9978 row_delta = 0;
9979 }
9980 prev_edited_row = selection.end.row;
9981
9982 row_delta =
9983 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9984 }
9985
9986 self.transact(window, cx, |this, window, cx| {
9987 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9988 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9989 });
9990 }
9991
9992 fn indent_selection(
9993 buffer: &MultiBuffer,
9994 snapshot: &MultiBufferSnapshot,
9995 selection: &mut Selection<Point>,
9996 edits: &mut Vec<(Range<Point>, String)>,
9997 delta_for_start_row: u32,
9998 cx: &App,
9999 ) -> u32 {
10000 let settings = buffer.language_settings_at(selection.start, cx);
10001 let tab_size = settings.tab_size.get();
10002 let indent_kind = if settings.hard_tabs {
10003 IndentKind::Tab
10004 } else {
10005 IndentKind::Space
10006 };
10007 let mut start_row = selection.start.row;
10008 let mut end_row = selection.end.row + 1;
10009
10010 // If a selection ends at the beginning of a line, don't indent
10011 // that last line.
10012 if selection.end.column == 0 && selection.end.row > selection.start.row {
10013 end_row -= 1;
10014 }
10015
10016 // Avoid re-indenting a row that has already been indented by a
10017 // previous selection, but still update this selection's column
10018 // to reflect that indentation.
10019 if delta_for_start_row > 0 {
10020 start_row += 1;
10021 selection.start.column += delta_for_start_row;
10022 if selection.end.row == selection.start.row {
10023 selection.end.column += delta_for_start_row;
10024 }
10025 }
10026
10027 let mut delta_for_end_row = 0;
10028 let has_multiple_rows = start_row + 1 != end_row;
10029 for row in start_row..end_row {
10030 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10031 let indent_delta = match (current_indent.kind, indent_kind) {
10032 (IndentKind::Space, IndentKind::Space) => {
10033 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10034 IndentSize::spaces(columns_to_next_tab_stop)
10035 }
10036 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10037 (_, IndentKind::Tab) => IndentSize::tab(),
10038 };
10039
10040 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10041 0
10042 } else {
10043 selection.start.column
10044 };
10045 let row_start = Point::new(row, start);
10046 edits.push((
10047 row_start..row_start,
10048 indent_delta.chars().collect::<String>(),
10049 ));
10050
10051 // Update this selection's endpoints to reflect the indentation.
10052 if row == selection.start.row {
10053 selection.start.column += indent_delta.len;
10054 }
10055 if row == selection.end.row {
10056 selection.end.column += indent_delta.len;
10057 delta_for_end_row = indent_delta.len;
10058 }
10059 }
10060
10061 if selection.start.row == selection.end.row {
10062 delta_for_start_row + delta_for_end_row
10063 } else {
10064 delta_for_end_row
10065 }
10066 }
10067
10068 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10069 if self.read_only(cx) {
10070 return;
10071 }
10072 if self.mode.is_single_line() {
10073 cx.propagate();
10074 return;
10075 }
10076
10077 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10078 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10079 let selections = self.selections.all::<Point>(cx);
10080 let mut deletion_ranges = Vec::new();
10081 let mut last_outdent = None;
10082 {
10083 let buffer = self.buffer.read(cx);
10084 let snapshot = buffer.snapshot(cx);
10085 for selection in &selections {
10086 let settings = buffer.language_settings_at(selection.start, cx);
10087 let tab_size = settings.tab_size.get();
10088 let mut rows = selection.spanned_rows(false, &display_map);
10089
10090 // Avoid re-outdenting a row that has already been outdented by a
10091 // previous selection.
10092 if let Some(last_row) = last_outdent
10093 && last_row == rows.start
10094 {
10095 rows.start = rows.start.next_row();
10096 }
10097 let has_multiple_rows = rows.len() > 1;
10098 for row in rows.iter_rows() {
10099 let indent_size = snapshot.indent_size_for_line(row);
10100 if indent_size.len > 0 {
10101 let deletion_len = match indent_size.kind {
10102 IndentKind::Space => {
10103 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10104 if columns_to_prev_tab_stop == 0 {
10105 tab_size
10106 } else {
10107 columns_to_prev_tab_stop
10108 }
10109 }
10110 IndentKind::Tab => 1,
10111 };
10112 let start = if has_multiple_rows
10113 || deletion_len > selection.start.column
10114 || indent_size.len < selection.start.column
10115 {
10116 0
10117 } else {
10118 selection.start.column - deletion_len
10119 };
10120 deletion_ranges.push(
10121 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10122 );
10123 last_outdent = Some(row);
10124 }
10125 }
10126 }
10127 }
10128
10129 self.transact(window, cx, |this, window, cx| {
10130 this.buffer.update(cx, |buffer, cx| {
10131 let empty_str: Arc<str> = Arc::default();
10132 buffer.edit(
10133 deletion_ranges
10134 .into_iter()
10135 .map(|range| (range, empty_str.clone())),
10136 None,
10137 cx,
10138 );
10139 });
10140 let selections = this.selections.all::<usize>(cx);
10141 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10142 });
10143 }
10144
10145 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10146 if self.read_only(cx) {
10147 return;
10148 }
10149 if self.mode.is_single_line() {
10150 cx.propagate();
10151 return;
10152 }
10153
10154 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10155 let selections = self
10156 .selections
10157 .all::<usize>(cx)
10158 .into_iter()
10159 .map(|s| s.range());
10160
10161 self.transact(window, cx, |this, window, cx| {
10162 this.buffer.update(cx, |buffer, cx| {
10163 buffer.autoindent_ranges(selections, cx);
10164 });
10165 let selections = this.selections.all::<usize>(cx);
10166 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10167 });
10168 }
10169
10170 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10171 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10172 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10173 let selections = self.selections.all::<Point>(cx);
10174
10175 let mut new_cursors = Vec::new();
10176 let mut edit_ranges = Vec::new();
10177 let mut selections = selections.iter().peekable();
10178 while let Some(selection) = selections.next() {
10179 let mut rows = selection.spanned_rows(false, &display_map);
10180 let goal_display_column = selection.head().to_display_point(&display_map).column();
10181
10182 // Accumulate contiguous regions of rows that we want to delete.
10183 while let Some(next_selection) = selections.peek() {
10184 let next_rows = next_selection.spanned_rows(false, &display_map);
10185 if next_rows.start <= rows.end {
10186 rows.end = next_rows.end;
10187 selections.next().unwrap();
10188 } else {
10189 break;
10190 }
10191 }
10192
10193 let buffer = &display_map.buffer_snapshot;
10194 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10195 let edit_end;
10196 let cursor_buffer_row;
10197 if buffer.max_point().row >= rows.end.0 {
10198 // If there's a line after the range, delete the \n from the end of the row range
10199 // and position the cursor on the next line.
10200 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10201 cursor_buffer_row = rows.end;
10202 } else {
10203 // If there isn't a line after the range, delete the \n from the line before the
10204 // start of the row range and position the cursor there.
10205 edit_start = edit_start.saturating_sub(1);
10206 edit_end = buffer.len();
10207 cursor_buffer_row = rows.start.previous_row();
10208 }
10209
10210 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10211 *cursor.column_mut() =
10212 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10213
10214 new_cursors.push((
10215 selection.id,
10216 buffer.anchor_after(cursor.to_point(&display_map)),
10217 ));
10218 edit_ranges.push(edit_start..edit_end);
10219 }
10220
10221 self.transact(window, cx, |this, window, cx| {
10222 let buffer = this.buffer.update(cx, |buffer, cx| {
10223 let empty_str: Arc<str> = Arc::default();
10224 buffer.edit(
10225 edit_ranges
10226 .into_iter()
10227 .map(|range| (range, empty_str.clone())),
10228 None,
10229 cx,
10230 );
10231 buffer.snapshot(cx)
10232 });
10233 let new_selections = new_cursors
10234 .into_iter()
10235 .map(|(id, cursor)| {
10236 let cursor = cursor.to_point(&buffer);
10237 Selection {
10238 id,
10239 start: cursor,
10240 end: cursor,
10241 reversed: false,
10242 goal: SelectionGoal::None,
10243 }
10244 })
10245 .collect();
10246
10247 this.change_selections(Default::default(), window, cx, |s| {
10248 s.select(new_selections);
10249 });
10250 });
10251 }
10252
10253 pub fn join_lines_impl(
10254 &mut self,
10255 insert_whitespace: bool,
10256 window: &mut Window,
10257 cx: &mut Context<Self>,
10258 ) {
10259 if self.read_only(cx) {
10260 return;
10261 }
10262 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10263 for selection in self.selections.all::<Point>(cx) {
10264 let start = MultiBufferRow(selection.start.row);
10265 // Treat single line selections as if they include the next line. Otherwise this action
10266 // would do nothing for single line selections individual cursors.
10267 let end = if selection.start.row == selection.end.row {
10268 MultiBufferRow(selection.start.row + 1)
10269 } else {
10270 MultiBufferRow(selection.end.row)
10271 };
10272
10273 if let Some(last_row_range) = row_ranges.last_mut()
10274 && start <= last_row_range.end
10275 {
10276 last_row_range.end = end;
10277 continue;
10278 }
10279 row_ranges.push(start..end);
10280 }
10281
10282 let snapshot = self.buffer.read(cx).snapshot(cx);
10283 let mut cursor_positions = Vec::new();
10284 for row_range in &row_ranges {
10285 let anchor = snapshot.anchor_before(Point::new(
10286 row_range.end.previous_row().0,
10287 snapshot.line_len(row_range.end.previous_row()),
10288 ));
10289 cursor_positions.push(anchor..anchor);
10290 }
10291
10292 self.transact(window, cx, |this, window, cx| {
10293 for row_range in row_ranges.into_iter().rev() {
10294 for row in row_range.iter_rows().rev() {
10295 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10296 let next_line_row = row.next_row();
10297 let indent = snapshot.indent_size_for_line(next_line_row);
10298 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10299
10300 let replace =
10301 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10302 " "
10303 } else {
10304 ""
10305 };
10306
10307 this.buffer.update(cx, |buffer, cx| {
10308 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10309 });
10310 }
10311 }
10312
10313 this.change_selections(Default::default(), window, cx, |s| {
10314 s.select_anchor_ranges(cursor_positions)
10315 });
10316 });
10317 }
10318
10319 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10321 self.join_lines_impl(true, window, cx);
10322 }
10323
10324 pub fn sort_lines_case_sensitive(
10325 &mut self,
10326 _: &SortLinesCaseSensitive,
10327 window: &mut Window,
10328 cx: &mut Context<Self>,
10329 ) {
10330 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10331 }
10332
10333 pub fn sort_lines_by_length(
10334 &mut self,
10335 _: &SortLinesByLength,
10336 window: &mut Window,
10337 cx: &mut Context<Self>,
10338 ) {
10339 self.manipulate_immutable_lines(window, cx, |lines| {
10340 lines.sort_by_key(|&line| line.chars().count())
10341 })
10342 }
10343
10344 pub fn sort_lines_case_insensitive(
10345 &mut self,
10346 _: &SortLinesCaseInsensitive,
10347 window: &mut Window,
10348 cx: &mut Context<Self>,
10349 ) {
10350 self.manipulate_immutable_lines(window, cx, |lines| {
10351 lines.sort_by_key(|line| line.to_lowercase())
10352 })
10353 }
10354
10355 pub fn unique_lines_case_insensitive(
10356 &mut self,
10357 _: &UniqueLinesCaseInsensitive,
10358 window: &mut Window,
10359 cx: &mut Context<Self>,
10360 ) {
10361 self.manipulate_immutable_lines(window, cx, |lines| {
10362 let mut seen = HashSet::default();
10363 lines.retain(|line| seen.insert(line.to_lowercase()));
10364 })
10365 }
10366
10367 pub fn unique_lines_case_sensitive(
10368 &mut self,
10369 _: &UniqueLinesCaseSensitive,
10370 window: &mut Window,
10371 cx: &mut Context<Self>,
10372 ) {
10373 self.manipulate_immutable_lines(window, cx, |lines| {
10374 let mut seen = HashSet::default();
10375 lines.retain(|line| seen.insert(*line));
10376 })
10377 }
10378
10379 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10380 let Some(project) = self.project.clone() else {
10381 return;
10382 };
10383 self.reload(project, window, cx)
10384 .detach_and_notify_err(window, cx);
10385 }
10386
10387 pub fn restore_file(
10388 &mut self,
10389 _: &::git::RestoreFile,
10390 window: &mut Window,
10391 cx: &mut Context<Self>,
10392 ) {
10393 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10394 let mut buffer_ids = HashSet::default();
10395 let snapshot = self.buffer().read(cx).snapshot(cx);
10396 for selection in self.selections.all::<usize>(cx) {
10397 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10398 }
10399
10400 let buffer = self.buffer().read(cx);
10401 let ranges = buffer_ids
10402 .into_iter()
10403 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10404 .collect::<Vec<_>>();
10405
10406 self.restore_hunks_in_ranges(ranges, window, cx);
10407 }
10408
10409 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10411 let selections = self
10412 .selections
10413 .all(cx)
10414 .into_iter()
10415 .map(|s| s.range())
10416 .collect();
10417 self.restore_hunks_in_ranges(selections, window, cx);
10418 }
10419
10420 pub fn restore_hunks_in_ranges(
10421 &mut self,
10422 ranges: Vec<Range<Point>>,
10423 window: &mut Window,
10424 cx: &mut Context<Editor>,
10425 ) {
10426 let mut revert_changes = HashMap::default();
10427 let chunk_by = self
10428 .snapshot(window, cx)
10429 .hunks_for_ranges(ranges)
10430 .into_iter()
10431 .chunk_by(|hunk| hunk.buffer_id);
10432 for (buffer_id, hunks) in &chunk_by {
10433 let hunks = hunks.collect::<Vec<_>>();
10434 for hunk in &hunks {
10435 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10436 }
10437 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10438 }
10439 drop(chunk_by);
10440 if !revert_changes.is_empty() {
10441 self.transact(window, cx, |editor, window, cx| {
10442 editor.restore(revert_changes, window, cx);
10443 });
10444 }
10445 }
10446
10447 pub fn open_active_item_in_terminal(
10448 &mut self,
10449 _: &OpenInTerminal,
10450 window: &mut Window,
10451 cx: &mut Context<Self>,
10452 ) {
10453 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10454 let project_path = buffer.read(cx).project_path(cx)?;
10455 let project = self.project()?.read(cx);
10456 let entry = project.entry_for_path(&project_path, cx)?;
10457 let parent = match &entry.canonical_path {
10458 Some(canonical_path) => canonical_path.to_path_buf(),
10459 None => project.absolute_path(&project_path, cx)?,
10460 }
10461 .parent()?
10462 .to_path_buf();
10463 Some(parent)
10464 }) {
10465 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10466 }
10467 }
10468
10469 fn set_breakpoint_context_menu(
10470 &mut self,
10471 display_row: DisplayRow,
10472 position: Option<Anchor>,
10473 clicked_point: gpui::Point<Pixels>,
10474 window: &mut Window,
10475 cx: &mut Context<Self>,
10476 ) {
10477 let source = self
10478 .buffer
10479 .read(cx)
10480 .snapshot(cx)
10481 .anchor_before(Point::new(display_row.0, 0u32));
10482
10483 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10484
10485 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10486 self,
10487 source,
10488 clicked_point,
10489 context_menu,
10490 window,
10491 cx,
10492 );
10493 }
10494
10495 fn add_edit_breakpoint_block(
10496 &mut self,
10497 anchor: Anchor,
10498 breakpoint: &Breakpoint,
10499 edit_action: BreakpointPromptEditAction,
10500 window: &mut Window,
10501 cx: &mut Context<Self>,
10502 ) {
10503 let weak_editor = cx.weak_entity();
10504 let bp_prompt = cx.new(|cx| {
10505 BreakpointPromptEditor::new(
10506 weak_editor,
10507 anchor,
10508 breakpoint.clone(),
10509 edit_action,
10510 window,
10511 cx,
10512 )
10513 });
10514
10515 let height = bp_prompt.update(cx, |this, cx| {
10516 this.prompt
10517 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10518 });
10519 let cloned_prompt = bp_prompt.clone();
10520 let blocks = vec![BlockProperties {
10521 style: BlockStyle::Sticky,
10522 placement: BlockPlacement::Above(anchor),
10523 height: Some(height),
10524 render: Arc::new(move |cx| {
10525 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10526 cloned_prompt.clone().into_any_element()
10527 }),
10528 priority: 0,
10529 }];
10530
10531 let focus_handle = bp_prompt.focus_handle(cx);
10532 window.focus(&focus_handle);
10533
10534 let block_ids = self.insert_blocks(blocks, None, cx);
10535 bp_prompt.update(cx, |prompt, _| {
10536 prompt.add_block_ids(block_ids);
10537 });
10538 }
10539
10540 pub(crate) fn breakpoint_at_row(
10541 &self,
10542 row: u32,
10543 window: &mut Window,
10544 cx: &mut Context<Self>,
10545 ) -> Option<(Anchor, Breakpoint)> {
10546 let snapshot = self.snapshot(window, cx);
10547 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10548
10549 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10550 }
10551
10552 pub(crate) fn breakpoint_at_anchor(
10553 &self,
10554 breakpoint_position: Anchor,
10555 snapshot: &EditorSnapshot,
10556 cx: &mut Context<Self>,
10557 ) -> Option<(Anchor, Breakpoint)> {
10558 let buffer = self
10559 .buffer
10560 .read(cx)
10561 .buffer_for_anchor(breakpoint_position, cx)?;
10562
10563 let enclosing_excerpt = breakpoint_position.excerpt_id;
10564 let buffer_snapshot = buffer.read(cx).snapshot();
10565
10566 let row = buffer_snapshot
10567 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10568 .row;
10569
10570 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10571 let anchor_end = snapshot
10572 .buffer_snapshot
10573 .anchor_after(Point::new(row, line_len));
10574
10575 self.breakpoint_store
10576 .as_ref()?
10577 .read_with(cx, |breakpoint_store, cx| {
10578 breakpoint_store
10579 .breakpoints(
10580 &buffer,
10581 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10582 &buffer_snapshot,
10583 cx,
10584 )
10585 .next()
10586 .and_then(|(bp, _)| {
10587 let breakpoint_row = buffer_snapshot
10588 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10589 .row;
10590
10591 if breakpoint_row == row {
10592 snapshot
10593 .buffer_snapshot
10594 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10595 .map(|position| (position, bp.bp.clone()))
10596 } else {
10597 None
10598 }
10599 })
10600 })
10601 }
10602
10603 pub fn edit_log_breakpoint(
10604 &mut self,
10605 _: &EditLogBreakpoint,
10606 window: &mut Window,
10607 cx: &mut Context<Self>,
10608 ) {
10609 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10610 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10611 message: None,
10612 state: BreakpointState::Enabled,
10613 condition: None,
10614 hit_condition: None,
10615 });
10616
10617 self.add_edit_breakpoint_block(
10618 anchor,
10619 &breakpoint,
10620 BreakpointPromptEditAction::Log,
10621 window,
10622 cx,
10623 );
10624 }
10625 }
10626
10627 fn breakpoints_at_cursors(
10628 &self,
10629 window: &mut Window,
10630 cx: &mut Context<Self>,
10631 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10632 let snapshot = self.snapshot(window, cx);
10633 let cursors = self
10634 .selections
10635 .disjoint_anchors()
10636 .iter()
10637 .map(|selection| {
10638 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10639
10640 let breakpoint_position = self
10641 .breakpoint_at_row(cursor_position.row, window, cx)
10642 .map(|bp| bp.0)
10643 .unwrap_or_else(|| {
10644 snapshot
10645 .display_snapshot
10646 .buffer_snapshot
10647 .anchor_after(Point::new(cursor_position.row, 0))
10648 });
10649
10650 let breakpoint = self
10651 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10652 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10653
10654 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10655 })
10656 // 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.
10657 .collect::<HashMap<Anchor, _>>();
10658
10659 cursors.into_iter().collect()
10660 }
10661
10662 pub fn enable_breakpoint(
10663 &mut self,
10664 _: &crate::actions::EnableBreakpoint,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10669 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10670 continue;
10671 };
10672 self.edit_breakpoint_at_anchor(
10673 anchor,
10674 breakpoint,
10675 BreakpointEditAction::InvertState,
10676 cx,
10677 );
10678 }
10679 }
10680
10681 pub fn disable_breakpoint(
10682 &mut self,
10683 _: &crate::actions::DisableBreakpoint,
10684 window: &mut Window,
10685 cx: &mut Context<Self>,
10686 ) {
10687 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10688 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10689 continue;
10690 };
10691 self.edit_breakpoint_at_anchor(
10692 anchor,
10693 breakpoint,
10694 BreakpointEditAction::InvertState,
10695 cx,
10696 );
10697 }
10698 }
10699
10700 pub fn toggle_breakpoint(
10701 &mut self,
10702 _: &crate::actions::ToggleBreakpoint,
10703 window: &mut Window,
10704 cx: &mut Context<Self>,
10705 ) {
10706 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10707 if let Some(breakpoint) = breakpoint {
10708 self.edit_breakpoint_at_anchor(
10709 anchor,
10710 breakpoint,
10711 BreakpointEditAction::Toggle,
10712 cx,
10713 );
10714 } else {
10715 self.edit_breakpoint_at_anchor(
10716 anchor,
10717 Breakpoint::new_standard(),
10718 BreakpointEditAction::Toggle,
10719 cx,
10720 );
10721 }
10722 }
10723 }
10724
10725 pub fn edit_breakpoint_at_anchor(
10726 &mut self,
10727 breakpoint_position: Anchor,
10728 breakpoint: Breakpoint,
10729 edit_action: BreakpointEditAction,
10730 cx: &mut Context<Self>,
10731 ) {
10732 let Some(breakpoint_store) = &self.breakpoint_store else {
10733 return;
10734 };
10735
10736 let Some(buffer) = self
10737 .buffer
10738 .read(cx)
10739 .buffer_for_anchor(breakpoint_position, cx)
10740 else {
10741 return;
10742 };
10743
10744 breakpoint_store.update(cx, |breakpoint_store, cx| {
10745 breakpoint_store.toggle_breakpoint(
10746 buffer,
10747 BreakpointWithPosition {
10748 position: breakpoint_position.text_anchor,
10749 bp: breakpoint,
10750 },
10751 edit_action,
10752 cx,
10753 );
10754 });
10755
10756 cx.notify();
10757 }
10758
10759 #[cfg(any(test, feature = "test-support"))]
10760 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10761 self.breakpoint_store.clone()
10762 }
10763
10764 pub fn prepare_restore_change(
10765 &self,
10766 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10767 hunk: &MultiBufferDiffHunk,
10768 cx: &mut App,
10769 ) -> Option<()> {
10770 if hunk.is_created_file() {
10771 return None;
10772 }
10773 let buffer = self.buffer.read(cx);
10774 let diff = buffer.diff_for(hunk.buffer_id)?;
10775 let buffer = buffer.buffer(hunk.buffer_id)?;
10776 let buffer = buffer.read(cx);
10777 let original_text = diff
10778 .read(cx)
10779 .base_text()
10780 .as_rope()
10781 .slice(hunk.diff_base_byte_range.clone());
10782 let buffer_snapshot = buffer.snapshot();
10783 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10784 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10785 probe
10786 .0
10787 .start
10788 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10789 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10790 }) {
10791 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10792 Some(())
10793 } else {
10794 None
10795 }
10796 }
10797
10798 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10799 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10800 }
10801
10802 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10803 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10804 }
10805
10806 fn manipulate_lines<M>(
10807 &mut self,
10808 window: &mut Window,
10809 cx: &mut Context<Self>,
10810 mut manipulate: M,
10811 ) where
10812 M: FnMut(&str) -> LineManipulationResult,
10813 {
10814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10815
10816 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10817 let buffer = self.buffer.read(cx).snapshot(cx);
10818
10819 let mut edits = Vec::new();
10820
10821 let selections = self.selections.all::<Point>(cx);
10822 let mut selections = selections.iter().peekable();
10823 let mut contiguous_row_selections = Vec::new();
10824 let mut new_selections = Vec::new();
10825 let mut added_lines = 0;
10826 let mut removed_lines = 0;
10827
10828 while let Some(selection) = selections.next() {
10829 let (start_row, end_row) = consume_contiguous_rows(
10830 &mut contiguous_row_selections,
10831 selection,
10832 &display_map,
10833 &mut selections,
10834 );
10835
10836 let start_point = Point::new(start_row.0, 0);
10837 let end_point = Point::new(
10838 end_row.previous_row().0,
10839 buffer.line_len(end_row.previous_row()),
10840 );
10841 let text = buffer
10842 .text_for_range(start_point..end_point)
10843 .collect::<String>();
10844
10845 let LineManipulationResult {
10846 new_text,
10847 line_count_before,
10848 line_count_after,
10849 } = manipulate(&text);
10850
10851 edits.push((start_point..end_point, new_text));
10852
10853 // Selections must change based on added and removed line count
10854 let start_row =
10855 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10856 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10857 new_selections.push(Selection {
10858 id: selection.id,
10859 start: start_row,
10860 end: end_row,
10861 goal: SelectionGoal::None,
10862 reversed: selection.reversed,
10863 });
10864
10865 if line_count_after > line_count_before {
10866 added_lines += line_count_after - line_count_before;
10867 } else if line_count_before > line_count_after {
10868 removed_lines += line_count_before - line_count_after;
10869 }
10870 }
10871
10872 self.transact(window, cx, |this, window, cx| {
10873 let buffer = this.buffer.update(cx, |buffer, cx| {
10874 buffer.edit(edits, None, cx);
10875 buffer.snapshot(cx)
10876 });
10877
10878 // Recalculate offsets on newly edited buffer
10879 let new_selections = new_selections
10880 .iter()
10881 .map(|s| {
10882 let start_point = Point::new(s.start.0, 0);
10883 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10884 Selection {
10885 id: s.id,
10886 start: buffer.point_to_offset(start_point),
10887 end: buffer.point_to_offset(end_point),
10888 goal: s.goal,
10889 reversed: s.reversed,
10890 }
10891 })
10892 .collect();
10893
10894 this.change_selections(Default::default(), window, cx, |s| {
10895 s.select(new_selections);
10896 });
10897
10898 this.request_autoscroll(Autoscroll::fit(), cx);
10899 });
10900 }
10901
10902 fn manipulate_immutable_lines<Fn>(
10903 &mut self,
10904 window: &mut Window,
10905 cx: &mut Context<Self>,
10906 mut callback: Fn,
10907 ) where
10908 Fn: FnMut(&mut Vec<&str>),
10909 {
10910 self.manipulate_lines(window, cx, |text| {
10911 let mut lines: Vec<&str> = text.split('\n').collect();
10912 let line_count_before = lines.len();
10913
10914 callback(&mut lines);
10915
10916 LineManipulationResult {
10917 new_text: lines.join("\n"),
10918 line_count_before,
10919 line_count_after: lines.len(),
10920 }
10921 });
10922 }
10923
10924 fn manipulate_mutable_lines<Fn>(
10925 &mut self,
10926 window: &mut Window,
10927 cx: &mut Context<Self>,
10928 mut callback: Fn,
10929 ) where
10930 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10931 {
10932 self.manipulate_lines(window, cx, |text| {
10933 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10934 let line_count_before = lines.len();
10935
10936 callback(&mut lines);
10937
10938 LineManipulationResult {
10939 new_text: lines.join("\n"),
10940 line_count_before,
10941 line_count_after: lines.len(),
10942 }
10943 });
10944 }
10945
10946 pub fn convert_indentation_to_spaces(
10947 &mut self,
10948 _: &ConvertIndentationToSpaces,
10949 window: &mut Window,
10950 cx: &mut Context<Self>,
10951 ) {
10952 let settings = self.buffer.read(cx).language_settings(cx);
10953 let tab_size = settings.tab_size.get() as usize;
10954
10955 self.manipulate_mutable_lines(window, cx, |lines| {
10956 // Allocates a reasonably sized scratch buffer once for the whole loop
10957 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10958 // Avoids recomputing spaces that could be inserted many times
10959 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10960 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10961 .collect();
10962
10963 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10964 let mut chars = line.as_ref().chars();
10965 let mut col = 0;
10966 let mut changed = false;
10967
10968 for ch in chars.by_ref() {
10969 match ch {
10970 ' ' => {
10971 reindented_line.push(' ');
10972 col += 1;
10973 }
10974 '\t' => {
10975 // \t are converted to spaces depending on the current column
10976 let spaces_len = tab_size - (col % tab_size);
10977 reindented_line.extend(&space_cache[spaces_len - 1]);
10978 col += spaces_len;
10979 changed = true;
10980 }
10981 _ => {
10982 // If we dont append before break, the character is consumed
10983 reindented_line.push(ch);
10984 break;
10985 }
10986 }
10987 }
10988
10989 if !changed {
10990 reindented_line.clear();
10991 continue;
10992 }
10993 // Append the rest of the line and replace old reference with new one
10994 reindented_line.extend(chars);
10995 *line = Cow::Owned(reindented_line.clone());
10996 reindented_line.clear();
10997 }
10998 });
10999 }
11000
11001 pub fn convert_indentation_to_tabs(
11002 &mut self,
11003 _: &ConvertIndentationToTabs,
11004 window: &mut Window,
11005 cx: &mut Context<Self>,
11006 ) {
11007 let settings = self.buffer.read(cx).language_settings(cx);
11008 let tab_size = settings.tab_size.get() as usize;
11009
11010 self.manipulate_mutable_lines(window, cx, |lines| {
11011 // Allocates a reasonably sized buffer once for the whole loop
11012 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11013 // Avoids recomputing spaces that could be inserted many times
11014 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11015 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11016 .collect();
11017
11018 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11019 let mut chars = line.chars();
11020 let mut spaces_count = 0;
11021 let mut first_non_indent_char = None;
11022 let mut changed = false;
11023
11024 for ch in chars.by_ref() {
11025 match ch {
11026 ' ' => {
11027 // Keep track of spaces. Append \t when we reach tab_size
11028 spaces_count += 1;
11029 changed = true;
11030 if spaces_count == tab_size {
11031 reindented_line.push('\t');
11032 spaces_count = 0;
11033 }
11034 }
11035 '\t' => {
11036 reindented_line.push('\t');
11037 spaces_count = 0;
11038 }
11039 _ => {
11040 // Dont append it yet, we might have remaining spaces
11041 first_non_indent_char = Some(ch);
11042 break;
11043 }
11044 }
11045 }
11046
11047 if !changed {
11048 reindented_line.clear();
11049 continue;
11050 }
11051 // Remaining spaces that didn't make a full tab stop
11052 if spaces_count > 0 {
11053 reindented_line.extend(&space_cache[spaces_count - 1]);
11054 }
11055 // If we consume an extra character that was not indentation, add it back
11056 if let Some(extra_char) = first_non_indent_char {
11057 reindented_line.push(extra_char);
11058 }
11059 // Append the rest of the line and replace old reference with new one
11060 reindented_line.extend(chars);
11061 *line = Cow::Owned(reindented_line.clone());
11062 reindented_line.clear();
11063 }
11064 });
11065 }
11066
11067 pub fn convert_to_upper_case(
11068 &mut self,
11069 _: &ConvertToUpperCase,
11070 window: &mut Window,
11071 cx: &mut Context<Self>,
11072 ) {
11073 self.manipulate_text(window, cx, |text| text.to_uppercase())
11074 }
11075
11076 pub fn convert_to_lower_case(
11077 &mut self,
11078 _: &ConvertToLowerCase,
11079 window: &mut Window,
11080 cx: &mut Context<Self>,
11081 ) {
11082 self.manipulate_text(window, cx, |text| text.to_lowercase())
11083 }
11084
11085 pub fn convert_to_title_case(
11086 &mut self,
11087 _: &ConvertToTitleCase,
11088 window: &mut Window,
11089 cx: &mut Context<Self>,
11090 ) {
11091 self.manipulate_text(window, cx, |text| {
11092 text.split('\n')
11093 .map(|line| line.to_case(Case::Title))
11094 .join("\n")
11095 })
11096 }
11097
11098 pub fn convert_to_snake_case(
11099 &mut self,
11100 _: &ConvertToSnakeCase,
11101 window: &mut Window,
11102 cx: &mut Context<Self>,
11103 ) {
11104 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11105 }
11106
11107 pub fn convert_to_kebab_case(
11108 &mut self,
11109 _: &ConvertToKebabCase,
11110 window: &mut Window,
11111 cx: &mut Context<Self>,
11112 ) {
11113 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11114 }
11115
11116 pub fn convert_to_upper_camel_case(
11117 &mut self,
11118 _: &ConvertToUpperCamelCase,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 ) {
11122 self.manipulate_text(window, cx, |text| {
11123 text.split('\n')
11124 .map(|line| line.to_case(Case::UpperCamel))
11125 .join("\n")
11126 })
11127 }
11128
11129 pub fn convert_to_lower_camel_case(
11130 &mut self,
11131 _: &ConvertToLowerCamelCase,
11132 window: &mut Window,
11133 cx: &mut Context<Self>,
11134 ) {
11135 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11136 }
11137
11138 pub fn convert_to_opposite_case(
11139 &mut self,
11140 _: &ConvertToOppositeCase,
11141 window: &mut Window,
11142 cx: &mut Context<Self>,
11143 ) {
11144 self.manipulate_text(window, cx, |text| {
11145 text.chars()
11146 .fold(String::with_capacity(text.len()), |mut t, c| {
11147 if c.is_uppercase() {
11148 t.extend(c.to_lowercase());
11149 } else {
11150 t.extend(c.to_uppercase());
11151 }
11152 t
11153 })
11154 })
11155 }
11156
11157 pub fn convert_to_sentence_case(
11158 &mut self,
11159 _: &ConvertToSentenceCase,
11160 window: &mut Window,
11161 cx: &mut Context<Self>,
11162 ) {
11163 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11164 }
11165
11166 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11167 self.manipulate_text(window, cx, |text| {
11168 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11169 if has_upper_case_characters {
11170 text.to_lowercase()
11171 } else {
11172 text.to_uppercase()
11173 }
11174 })
11175 }
11176
11177 pub fn convert_to_rot13(
11178 &mut self,
11179 _: &ConvertToRot13,
11180 window: &mut Window,
11181 cx: &mut Context<Self>,
11182 ) {
11183 self.manipulate_text(window, cx, |text| {
11184 text.chars()
11185 .map(|c| match c {
11186 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11187 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11188 _ => c,
11189 })
11190 .collect()
11191 })
11192 }
11193
11194 pub fn convert_to_rot47(
11195 &mut self,
11196 _: &ConvertToRot47,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 self.manipulate_text(window, cx, |text| {
11201 text.chars()
11202 .map(|c| {
11203 let code_point = c as u32;
11204 if code_point >= 33 && code_point <= 126 {
11205 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11206 }
11207 c
11208 })
11209 .collect()
11210 })
11211 }
11212
11213 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11214 where
11215 Fn: FnMut(&str) -> String,
11216 {
11217 let buffer = self.buffer.read(cx).snapshot(cx);
11218
11219 let mut new_selections = Vec::new();
11220 let mut edits = Vec::new();
11221 let mut selection_adjustment = 0i32;
11222
11223 for selection in self.selections.all::<usize>(cx) {
11224 let selection_is_empty = selection.is_empty();
11225
11226 let (start, end) = if selection_is_empty {
11227 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11228 (word_range.start, word_range.end)
11229 } else {
11230 (selection.start, selection.end)
11231 };
11232
11233 let text = buffer.text_for_range(start..end).collect::<String>();
11234 let old_length = text.len() as i32;
11235 let text = callback(&text);
11236
11237 new_selections.push(Selection {
11238 start: (start as i32 - selection_adjustment) as usize,
11239 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11240 goal: SelectionGoal::None,
11241 ..selection
11242 });
11243
11244 selection_adjustment += old_length - text.len() as i32;
11245
11246 edits.push((start..end, text));
11247 }
11248
11249 self.transact(window, cx, |this, window, cx| {
11250 this.buffer.update(cx, |buffer, cx| {
11251 buffer.edit(edits, None, cx);
11252 });
11253
11254 this.change_selections(Default::default(), window, cx, |s| {
11255 s.select(new_selections);
11256 });
11257
11258 this.request_autoscroll(Autoscroll::fit(), cx);
11259 });
11260 }
11261
11262 pub fn move_selection_on_drop(
11263 &mut self,
11264 selection: &Selection<Anchor>,
11265 target: DisplayPoint,
11266 is_cut: bool,
11267 window: &mut Window,
11268 cx: &mut Context<Self>,
11269 ) {
11270 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11271 let buffer = &display_map.buffer_snapshot;
11272 let mut edits = Vec::new();
11273 let insert_point = display_map
11274 .clip_point(target, Bias::Left)
11275 .to_point(&display_map);
11276 let text = buffer
11277 .text_for_range(selection.start..selection.end)
11278 .collect::<String>();
11279 if is_cut {
11280 edits.push(((selection.start..selection.end), String::new()));
11281 }
11282 let insert_anchor = buffer.anchor_before(insert_point);
11283 edits.push(((insert_anchor..insert_anchor), text));
11284 let last_edit_start = insert_anchor.bias_left(buffer);
11285 let last_edit_end = insert_anchor.bias_right(buffer);
11286 self.transact(window, cx, |this, window, cx| {
11287 this.buffer.update(cx, |buffer, cx| {
11288 buffer.edit(edits, None, cx);
11289 });
11290 this.change_selections(Default::default(), window, cx, |s| {
11291 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11292 });
11293 });
11294 }
11295
11296 pub fn clear_selection_drag_state(&mut self) {
11297 self.selection_drag_state = SelectionDragState::None;
11298 }
11299
11300 pub fn duplicate(
11301 &mut self,
11302 upwards: bool,
11303 whole_lines: bool,
11304 window: &mut Window,
11305 cx: &mut Context<Self>,
11306 ) {
11307 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11308
11309 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11310 let buffer = &display_map.buffer_snapshot;
11311 let selections = self.selections.all::<Point>(cx);
11312
11313 let mut edits = Vec::new();
11314 let mut selections_iter = selections.iter().peekable();
11315 while let Some(selection) = selections_iter.next() {
11316 let mut rows = selection.spanned_rows(false, &display_map);
11317 // duplicate line-wise
11318 if whole_lines || selection.start == selection.end {
11319 // Avoid duplicating the same lines twice.
11320 while let Some(next_selection) = selections_iter.peek() {
11321 let next_rows = next_selection.spanned_rows(false, &display_map);
11322 if next_rows.start < rows.end {
11323 rows.end = next_rows.end;
11324 selections_iter.next().unwrap();
11325 } else {
11326 break;
11327 }
11328 }
11329
11330 // Copy the text from the selected row region and splice it either at the start
11331 // or end of the region.
11332 let start = Point::new(rows.start.0, 0);
11333 let end = Point::new(
11334 rows.end.previous_row().0,
11335 buffer.line_len(rows.end.previous_row()),
11336 );
11337 let text = buffer
11338 .text_for_range(start..end)
11339 .chain(Some("\n"))
11340 .collect::<String>();
11341 let insert_location = if upwards {
11342 Point::new(rows.end.0, 0)
11343 } else {
11344 start
11345 };
11346 edits.push((insert_location..insert_location, text));
11347 } else {
11348 // duplicate character-wise
11349 let start = selection.start;
11350 let end = selection.end;
11351 let text = buffer.text_for_range(start..end).collect::<String>();
11352 edits.push((selection.end..selection.end, text));
11353 }
11354 }
11355
11356 self.transact(window, cx, |this, _, cx| {
11357 this.buffer.update(cx, |buffer, cx| {
11358 buffer.edit(edits, None, cx);
11359 });
11360
11361 this.request_autoscroll(Autoscroll::fit(), cx);
11362 });
11363 }
11364
11365 pub fn duplicate_line_up(
11366 &mut self,
11367 _: &DuplicateLineUp,
11368 window: &mut Window,
11369 cx: &mut Context<Self>,
11370 ) {
11371 self.duplicate(true, true, window, cx);
11372 }
11373
11374 pub fn duplicate_line_down(
11375 &mut self,
11376 _: &DuplicateLineDown,
11377 window: &mut Window,
11378 cx: &mut Context<Self>,
11379 ) {
11380 self.duplicate(false, true, window, cx);
11381 }
11382
11383 pub fn duplicate_selection(
11384 &mut self,
11385 _: &DuplicateSelection,
11386 window: &mut Window,
11387 cx: &mut Context<Self>,
11388 ) {
11389 self.duplicate(false, false, window, cx);
11390 }
11391
11392 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11393 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11394 if self.mode.is_single_line() {
11395 cx.propagate();
11396 return;
11397 }
11398
11399 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11400 let buffer = self.buffer.read(cx).snapshot(cx);
11401
11402 let mut edits = Vec::new();
11403 let mut unfold_ranges = Vec::new();
11404 let mut refold_creases = Vec::new();
11405
11406 let selections = self.selections.all::<Point>(cx);
11407 let mut selections = selections.iter().peekable();
11408 let mut contiguous_row_selections = Vec::new();
11409 let mut new_selections = Vec::new();
11410
11411 while let Some(selection) = selections.next() {
11412 // Find all the selections that span a contiguous row range
11413 let (start_row, end_row) = consume_contiguous_rows(
11414 &mut contiguous_row_selections,
11415 selection,
11416 &display_map,
11417 &mut selections,
11418 );
11419
11420 // Move the text spanned by the row range to be before the line preceding the row range
11421 if start_row.0 > 0 {
11422 let range_to_move = Point::new(
11423 start_row.previous_row().0,
11424 buffer.line_len(start_row.previous_row()),
11425 )
11426 ..Point::new(
11427 end_row.previous_row().0,
11428 buffer.line_len(end_row.previous_row()),
11429 );
11430 let insertion_point = display_map
11431 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11432 .0;
11433
11434 // Don't move lines across excerpts
11435 if buffer
11436 .excerpt_containing(insertion_point..range_to_move.end)
11437 .is_some()
11438 {
11439 let text = buffer
11440 .text_for_range(range_to_move.clone())
11441 .flat_map(|s| s.chars())
11442 .skip(1)
11443 .chain(['\n'])
11444 .collect::<String>();
11445
11446 edits.push((
11447 buffer.anchor_after(range_to_move.start)
11448 ..buffer.anchor_before(range_to_move.end),
11449 String::new(),
11450 ));
11451 let insertion_anchor = buffer.anchor_after(insertion_point);
11452 edits.push((insertion_anchor..insertion_anchor, text));
11453
11454 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11455
11456 // Move selections up
11457 new_selections.extend(contiguous_row_selections.drain(..).map(
11458 |mut selection| {
11459 selection.start.row -= row_delta;
11460 selection.end.row -= row_delta;
11461 selection
11462 },
11463 ));
11464
11465 // Move folds up
11466 unfold_ranges.push(range_to_move.clone());
11467 for fold in display_map.folds_in_range(
11468 buffer.anchor_before(range_to_move.start)
11469 ..buffer.anchor_after(range_to_move.end),
11470 ) {
11471 let mut start = fold.range.start.to_point(&buffer);
11472 let mut end = fold.range.end.to_point(&buffer);
11473 start.row -= row_delta;
11474 end.row -= row_delta;
11475 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11476 }
11477 }
11478 }
11479
11480 // If we didn't move line(s), preserve the existing selections
11481 new_selections.append(&mut contiguous_row_selections);
11482 }
11483
11484 self.transact(window, cx, |this, window, cx| {
11485 this.unfold_ranges(&unfold_ranges, true, true, cx);
11486 this.buffer.update(cx, |buffer, cx| {
11487 for (range, text) in edits {
11488 buffer.edit([(range, text)], None, cx);
11489 }
11490 });
11491 this.fold_creases(refold_creases, true, window, cx);
11492 this.change_selections(Default::default(), window, cx, |s| {
11493 s.select(new_selections);
11494 })
11495 });
11496 }
11497
11498 pub fn move_line_down(
11499 &mut self,
11500 _: &MoveLineDown,
11501 window: &mut Window,
11502 cx: &mut Context<Self>,
11503 ) {
11504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11505 if self.mode.is_single_line() {
11506 cx.propagate();
11507 return;
11508 }
11509
11510 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11511 let buffer = self.buffer.read(cx).snapshot(cx);
11512
11513 let mut edits = Vec::new();
11514 let mut unfold_ranges = Vec::new();
11515 let mut refold_creases = Vec::new();
11516
11517 let selections = self.selections.all::<Point>(cx);
11518 let mut selections = selections.iter().peekable();
11519 let mut contiguous_row_selections = Vec::new();
11520 let mut new_selections = Vec::new();
11521
11522 while let Some(selection) = selections.next() {
11523 // Find all the selections that span a contiguous row range
11524 let (start_row, end_row) = consume_contiguous_rows(
11525 &mut contiguous_row_selections,
11526 selection,
11527 &display_map,
11528 &mut selections,
11529 );
11530
11531 // Move the text spanned by the row range to be after the last line of the row range
11532 if end_row.0 <= buffer.max_point().row {
11533 let range_to_move =
11534 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11535 let insertion_point = display_map
11536 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11537 .0;
11538
11539 // Don't move lines across excerpt boundaries
11540 if buffer
11541 .excerpt_containing(range_to_move.start..insertion_point)
11542 .is_some()
11543 {
11544 let mut text = String::from("\n");
11545 text.extend(buffer.text_for_range(range_to_move.clone()));
11546 text.pop(); // Drop trailing newline
11547 edits.push((
11548 buffer.anchor_after(range_to_move.start)
11549 ..buffer.anchor_before(range_to_move.end),
11550 String::new(),
11551 ));
11552 let insertion_anchor = buffer.anchor_after(insertion_point);
11553 edits.push((insertion_anchor..insertion_anchor, text));
11554
11555 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11556
11557 // Move selections down
11558 new_selections.extend(contiguous_row_selections.drain(..).map(
11559 |mut selection| {
11560 selection.start.row += row_delta;
11561 selection.end.row += row_delta;
11562 selection
11563 },
11564 ));
11565
11566 // Move folds down
11567 unfold_ranges.push(range_to_move.clone());
11568 for fold in display_map.folds_in_range(
11569 buffer.anchor_before(range_to_move.start)
11570 ..buffer.anchor_after(range_to_move.end),
11571 ) {
11572 let mut start = fold.range.start.to_point(&buffer);
11573 let mut end = fold.range.end.to_point(&buffer);
11574 start.row += row_delta;
11575 end.row += row_delta;
11576 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11577 }
11578 }
11579 }
11580
11581 // If we didn't move line(s), preserve the existing selections
11582 new_selections.append(&mut contiguous_row_selections);
11583 }
11584
11585 self.transact(window, cx, |this, window, cx| {
11586 this.unfold_ranges(&unfold_ranges, true, true, cx);
11587 this.buffer.update(cx, |buffer, cx| {
11588 for (range, text) in edits {
11589 buffer.edit([(range, text)], None, cx);
11590 }
11591 });
11592 this.fold_creases(refold_creases, true, window, cx);
11593 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11594 });
11595 }
11596
11597 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11598 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11599 let text_layout_details = &self.text_layout_details(window);
11600 self.transact(window, cx, |this, window, cx| {
11601 let edits = this.change_selections(Default::default(), window, cx, |s| {
11602 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11603 s.move_with(|display_map, selection| {
11604 if !selection.is_empty() {
11605 return;
11606 }
11607
11608 let mut head = selection.head();
11609 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11610 if head.column() == display_map.line_len(head.row()) {
11611 transpose_offset = display_map
11612 .buffer_snapshot
11613 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11614 }
11615
11616 if transpose_offset == 0 {
11617 return;
11618 }
11619
11620 *head.column_mut() += 1;
11621 head = display_map.clip_point(head, Bias::Right);
11622 let goal = SelectionGoal::HorizontalPosition(
11623 display_map
11624 .x_for_display_point(head, text_layout_details)
11625 .into(),
11626 );
11627 selection.collapse_to(head, goal);
11628
11629 let transpose_start = display_map
11630 .buffer_snapshot
11631 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11632 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11633 let transpose_end = display_map
11634 .buffer_snapshot
11635 .clip_offset(transpose_offset + 1, Bias::Right);
11636 if let Some(ch) =
11637 display_map.buffer_snapshot.chars_at(transpose_start).next()
11638 {
11639 edits.push((transpose_start..transpose_offset, String::new()));
11640 edits.push((transpose_end..transpose_end, ch.to_string()));
11641 }
11642 }
11643 });
11644 edits
11645 });
11646 this.buffer
11647 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11648 let selections = this.selections.all::<usize>(cx);
11649 this.change_selections(Default::default(), window, cx, |s| {
11650 s.select(selections);
11651 });
11652 });
11653 }
11654
11655 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11656 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11657 if self.mode.is_single_line() {
11658 cx.propagate();
11659 return;
11660 }
11661
11662 self.rewrap_impl(RewrapOptions::default(), cx)
11663 }
11664
11665 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11666 let buffer = self.buffer.read(cx).snapshot(cx);
11667 let selections = self.selections.all::<Point>(cx);
11668
11669 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11670 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11671 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11672 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11673 .peekable();
11674
11675 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11676 row
11677 } else {
11678 return Vec::new();
11679 };
11680
11681 let language_settings = buffer.language_settings_at(selection.head(), cx);
11682 let language_scope = buffer.language_scope_at(selection.head());
11683
11684 let indent_and_prefix_for_row =
11685 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11686 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11687 let (comment_prefix, rewrap_prefix) =
11688 if let Some(language_scope) = &language_scope {
11689 let indent_end = Point::new(row, indent.len);
11690 let comment_prefix = language_scope
11691 .line_comment_prefixes()
11692 .iter()
11693 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11694 .map(|prefix| prefix.to_string());
11695 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11696 let line_text_after_indent = buffer
11697 .text_for_range(indent_end..line_end)
11698 .collect::<String>();
11699 let rewrap_prefix = language_scope
11700 .rewrap_prefixes()
11701 .iter()
11702 .find_map(|prefix_regex| {
11703 prefix_regex.find(&line_text_after_indent).map(|mat| {
11704 if mat.start() == 0 {
11705 Some(mat.as_str().to_string())
11706 } else {
11707 None
11708 }
11709 })
11710 })
11711 .flatten();
11712 (comment_prefix, rewrap_prefix)
11713 } else {
11714 (None, None)
11715 };
11716 (indent, comment_prefix, rewrap_prefix)
11717 };
11718
11719 let mut ranges = Vec::new();
11720 let from_empty_selection = selection.is_empty();
11721
11722 let mut current_range_start = first_row;
11723 let mut prev_row = first_row;
11724 let (
11725 mut current_range_indent,
11726 mut current_range_comment_prefix,
11727 mut current_range_rewrap_prefix,
11728 ) = indent_and_prefix_for_row(first_row);
11729
11730 for row in non_blank_rows_iter.skip(1) {
11731 let has_paragraph_break = row > prev_row + 1;
11732
11733 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11734 indent_and_prefix_for_row(row);
11735
11736 let has_indent_change = row_indent != current_range_indent;
11737 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11738
11739 let has_boundary_change = has_comment_change
11740 || row_rewrap_prefix.is_some()
11741 || (has_indent_change && current_range_comment_prefix.is_some());
11742
11743 if has_paragraph_break || has_boundary_change {
11744 ranges.push((
11745 language_settings.clone(),
11746 Point::new(current_range_start, 0)
11747 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11748 current_range_indent,
11749 current_range_comment_prefix.clone(),
11750 current_range_rewrap_prefix.clone(),
11751 from_empty_selection,
11752 ));
11753 current_range_start = row;
11754 current_range_indent = row_indent;
11755 current_range_comment_prefix = row_comment_prefix;
11756 current_range_rewrap_prefix = row_rewrap_prefix;
11757 }
11758 prev_row = row;
11759 }
11760
11761 ranges.push((
11762 language_settings.clone(),
11763 Point::new(current_range_start, 0)
11764 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11765 current_range_indent,
11766 current_range_comment_prefix,
11767 current_range_rewrap_prefix,
11768 from_empty_selection,
11769 ));
11770
11771 ranges
11772 });
11773
11774 let mut edits = Vec::new();
11775 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11776
11777 for (
11778 language_settings,
11779 wrap_range,
11780 indent_size,
11781 comment_prefix,
11782 rewrap_prefix,
11783 from_empty_selection,
11784 ) in wrap_ranges
11785 {
11786 let mut start_row = wrap_range.start.row;
11787 let mut end_row = wrap_range.end.row;
11788
11789 // Skip selections that overlap with a range that has already been rewrapped.
11790 let selection_range = start_row..end_row;
11791 if rewrapped_row_ranges
11792 .iter()
11793 .any(|range| range.overlaps(&selection_range))
11794 {
11795 continue;
11796 }
11797
11798 let tab_size = language_settings.tab_size;
11799
11800 let indent_prefix = indent_size.chars().collect::<String>();
11801 let mut line_prefix = indent_prefix.clone();
11802 let mut inside_comment = false;
11803 if let Some(prefix) = &comment_prefix {
11804 line_prefix.push_str(prefix);
11805 inside_comment = true;
11806 }
11807 if let Some(prefix) = &rewrap_prefix {
11808 line_prefix.push_str(prefix);
11809 }
11810
11811 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11812 RewrapBehavior::InComments => inside_comment,
11813 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11814 RewrapBehavior::Anywhere => true,
11815 };
11816
11817 let should_rewrap = options.override_language_settings
11818 || allow_rewrap_based_on_language
11819 || self.hard_wrap.is_some();
11820 if !should_rewrap {
11821 continue;
11822 }
11823
11824 if from_empty_selection {
11825 'expand_upwards: while start_row > 0 {
11826 let prev_row = start_row - 1;
11827 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11828 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11829 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11830 {
11831 start_row = prev_row;
11832 } else {
11833 break 'expand_upwards;
11834 }
11835 }
11836
11837 'expand_downwards: while end_row < buffer.max_point().row {
11838 let next_row = end_row + 1;
11839 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11840 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11841 && !buffer.is_line_blank(MultiBufferRow(next_row))
11842 {
11843 end_row = next_row;
11844 } else {
11845 break 'expand_downwards;
11846 }
11847 }
11848 }
11849
11850 let start = Point::new(start_row, 0);
11851 let start_offset = start.to_offset(&buffer);
11852 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11853 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11854 let Some(lines_without_prefixes) = selection_text
11855 .lines()
11856 .enumerate()
11857 .map(|(ix, line)| {
11858 let line_trimmed = line.trim_start();
11859 if rewrap_prefix.is_some() && ix > 0 {
11860 Ok(line_trimmed)
11861 } else {
11862 line_trimmed
11863 .strip_prefix(&line_prefix.trim_start())
11864 .with_context(|| {
11865 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11866 })
11867 }
11868 })
11869 .collect::<Result<Vec<_>, _>>()
11870 .log_err()
11871 else {
11872 continue;
11873 };
11874
11875 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11876 buffer
11877 .language_settings_at(Point::new(start_row, 0), cx)
11878 .preferred_line_length as usize
11879 });
11880
11881 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11882 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11883 } else {
11884 line_prefix.clone()
11885 };
11886
11887 let wrapped_text = wrap_with_prefix(
11888 line_prefix,
11889 subsequent_lines_prefix,
11890 lines_without_prefixes.join("\n"),
11891 wrap_column,
11892 tab_size,
11893 options.preserve_existing_whitespace,
11894 );
11895
11896 // TODO: should always use char-based diff while still supporting cursor behavior that
11897 // matches vim.
11898 let mut diff_options = DiffOptions::default();
11899 if options.override_language_settings {
11900 diff_options.max_word_diff_len = 0;
11901 diff_options.max_word_diff_line_count = 0;
11902 } else {
11903 diff_options.max_word_diff_len = usize::MAX;
11904 diff_options.max_word_diff_line_count = usize::MAX;
11905 }
11906
11907 for (old_range, new_text) in
11908 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11909 {
11910 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11911 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11912 edits.push((edit_start..edit_end, new_text));
11913 }
11914
11915 rewrapped_row_ranges.push(start_row..=end_row);
11916 }
11917
11918 self.buffer
11919 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11920 }
11921
11922 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11923 let mut text = String::new();
11924 let buffer = self.buffer.read(cx).snapshot(cx);
11925 let mut selections = self.selections.all::<Point>(cx);
11926 let mut clipboard_selections = Vec::with_capacity(selections.len());
11927 {
11928 let max_point = buffer.max_point();
11929 let mut is_first = true;
11930 for selection in &mut selections {
11931 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11932 if is_entire_line {
11933 selection.start = Point::new(selection.start.row, 0);
11934 if !selection.is_empty() && selection.end.column == 0 {
11935 selection.end = cmp::min(max_point, selection.end);
11936 } else {
11937 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11938 }
11939 selection.goal = SelectionGoal::None;
11940 }
11941 if is_first {
11942 is_first = false;
11943 } else {
11944 text += "\n";
11945 }
11946 let mut len = 0;
11947 for chunk in buffer.text_for_range(selection.start..selection.end) {
11948 text.push_str(chunk);
11949 len += chunk.len();
11950 }
11951 clipboard_selections.push(ClipboardSelection {
11952 len,
11953 is_entire_line,
11954 first_line_indent: buffer
11955 .indent_size_for_line(MultiBufferRow(selection.start.row))
11956 .len,
11957 });
11958 }
11959 }
11960
11961 self.transact(window, cx, |this, window, cx| {
11962 this.change_selections(Default::default(), window, cx, |s| {
11963 s.select(selections);
11964 });
11965 this.insert("", window, cx);
11966 });
11967 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11968 }
11969
11970 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11971 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11972 let item = self.cut_common(window, cx);
11973 cx.write_to_clipboard(item);
11974 }
11975
11976 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11978 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11979 s.move_with(|snapshot, sel| {
11980 if sel.is_empty() {
11981 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11982 }
11983 });
11984 });
11985 let item = self.cut_common(window, cx);
11986 cx.set_global(KillRing(item))
11987 }
11988
11989 pub fn kill_ring_yank(
11990 &mut self,
11991 _: &KillRingYank,
11992 window: &mut Window,
11993 cx: &mut Context<Self>,
11994 ) {
11995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11996 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11997 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11998 (kill_ring.text().to_string(), kill_ring.metadata_json())
11999 } else {
12000 return;
12001 }
12002 } else {
12003 return;
12004 };
12005 self.do_paste(&text, metadata, false, window, cx);
12006 }
12007
12008 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12009 self.do_copy(true, cx);
12010 }
12011
12012 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12013 self.do_copy(false, cx);
12014 }
12015
12016 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12017 let selections = self.selections.all::<Point>(cx);
12018 let buffer = self.buffer.read(cx).read(cx);
12019 let mut text = String::new();
12020
12021 let mut clipboard_selections = Vec::with_capacity(selections.len());
12022 {
12023 let max_point = buffer.max_point();
12024 let mut is_first = true;
12025 for selection in &selections {
12026 let mut start = selection.start;
12027 let mut end = selection.end;
12028 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12029 if is_entire_line {
12030 start = Point::new(start.row, 0);
12031 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12032 }
12033
12034 let mut trimmed_selections = Vec::new();
12035 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12036 let row = MultiBufferRow(start.row);
12037 let first_indent = buffer.indent_size_for_line(row);
12038 if first_indent.len == 0 || start.column > first_indent.len {
12039 trimmed_selections.push(start..end);
12040 } else {
12041 trimmed_selections.push(
12042 Point::new(row.0, first_indent.len)
12043 ..Point::new(row.0, buffer.line_len(row)),
12044 );
12045 for row in start.row + 1..=end.row {
12046 let mut line_len = buffer.line_len(MultiBufferRow(row));
12047 if row == end.row {
12048 line_len = end.column;
12049 }
12050 if line_len == 0 {
12051 trimmed_selections
12052 .push(Point::new(row, 0)..Point::new(row, line_len));
12053 continue;
12054 }
12055 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12056 if row_indent_size.len >= first_indent.len {
12057 trimmed_selections.push(
12058 Point::new(row, first_indent.len)..Point::new(row, line_len),
12059 );
12060 } else {
12061 trimmed_selections.clear();
12062 trimmed_selections.push(start..end);
12063 break;
12064 }
12065 }
12066 }
12067 } else {
12068 trimmed_selections.push(start..end);
12069 }
12070
12071 for trimmed_range in trimmed_selections {
12072 if is_first {
12073 is_first = false;
12074 } else {
12075 text += "\n";
12076 }
12077 let mut len = 0;
12078 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12079 text.push_str(chunk);
12080 len += chunk.len();
12081 }
12082 clipboard_selections.push(ClipboardSelection {
12083 len,
12084 is_entire_line,
12085 first_line_indent: buffer
12086 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12087 .len,
12088 });
12089 }
12090 }
12091 }
12092
12093 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12094 text,
12095 clipboard_selections,
12096 ));
12097 }
12098
12099 pub fn do_paste(
12100 &mut self,
12101 text: &String,
12102 clipboard_selections: Option<Vec<ClipboardSelection>>,
12103 handle_entire_lines: bool,
12104 window: &mut Window,
12105 cx: &mut Context<Self>,
12106 ) {
12107 if self.read_only(cx) {
12108 return;
12109 }
12110
12111 let clipboard_text = Cow::Borrowed(text);
12112
12113 self.transact(window, cx, |this, window, cx| {
12114 let had_active_edit_prediction = this.has_active_edit_prediction();
12115
12116 if let Some(mut clipboard_selections) = clipboard_selections {
12117 let old_selections = this.selections.all::<usize>(cx);
12118 let all_selections_were_entire_line =
12119 clipboard_selections.iter().all(|s| s.is_entire_line);
12120 let first_selection_indent_column =
12121 clipboard_selections.first().map(|s| s.first_line_indent);
12122 if clipboard_selections.len() != old_selections.len() {
12123 clipboard_selections.drain(..);
12124 }
12125 let cursor_offset = this.selections.last::<usize>(cx).head();
12126 let mut auto_indent_on_paste = true;
12127
12128 this.buffer.update(cx, |buffer, cx| {
12129 let snapshot = buffer.read(cx);
12130 auto_indent_on_paste = snapshot
12131 .language_settings_at(cursor_offset, cx)
12132 .auto_indent_on_paste;
12133
12134 let mut start_offset = 0;
12135 let mut edits = Vec::new();
12136 let mut original_indent_columns = Vec::new();
12137 for (ix, selection) in old_selections.iter().enumerate() {
12138 let to_insert;
12139 let entire_line;
12140 let original_indent_column;
12141 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12142 let end_offset = start_offset + clipboard_selection.len;
12143 to_insert = &clipboard_text[start_offset..end_offset];
12144 entire_line = clipboard_selection.is_entire_line;
12145 start_offset = end_offset + 1;
12146 original_indent_column = Some(clipboard_selection.first_line_indent);
12147 } else {
12148 to_insert = clipboard_text.as_str();
12149 entire_line = all_selections_were_entire_line;
12150 original_indent_column = first_selection_indent_column
12151 }
12152
12153 // If the corresponding selection was empty when this slice of the
12154 // clipboard text was written, then the entire line containing the
12155 // selection was copied. If this selection is also currently empty,
12156 // then paste the line before the current line of the buffer.
12157 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12158 let column = selection.start.to_point(&snapshot).column as usize;
12159 let line_start = selection.start - column;
12160 line_start..line_start
12161 } else {
12162 selection.range()
12163 };
12164
12165 edits.push((range, to_insert));
12166 original_indent_columns.push(original_indent_column);
12167 }
12168 drop(snapshot);
12169
12170 buffer.edit(
12171 edits,
12172 if auto_indent_on_paste {
12173 Some(AutoindentMode::Block {
12174 original_indent_columns,
12175 })
12176 } else {
12177 None
12178 },
12179 cx,
12180 );
12181 });
12182
12183 let selections = this.selections.all::<usize>(cx);
12184 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12185 } else {
12186 this.insert(&clipboard_text, window, cx);
12187 }
12188
12189 let trigger_in_words =
12190 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12191
12192 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12193 });
12194 }
12195
12196 pub fn diff_clipboard_with_selection(
12197 &mut self,
12198 _: &DiffClipboardWithSelection,
12199 window: &mut Window,
12200 cx: &mut Context<Self>,
12201 ) {
12202 let selections = self.selections.all::<usize>(cx);
12203
12204 if selections.is_empty() {
12205 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12206 return;
12207 };
12208
12209 let clipboard_text = match cx.read_from_clipboard() {
12210 Some(item) => match item.entries().first() {
12211 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12212 _ => None,
12213 },
12214 None => None,
12215 };
12216
12217 let Some(clipboard_text) = clipboard_text else {
12218 log::warn!("Clipboard doesn't contain text.");
12219 return;
12220 };
12221
12222 window.dispatch_action(
12223 Box::new(DiffClipboardWithSelectionData {
12224 clipboard_text,
12225 editor: cx.entity(),
12226 }),
12227 cx,
12228 );
12229 }
12230
12231 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12232 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12233 if let Some(item) = cx.read_from_clipboard() {
12234 let entries = item.entries();
12235
12236 match entries.first() {
12237 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12238 // of all the pasted entries.
12239 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12240 .do_paste(
12241 clipboard_string.text(),
12242 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12243 true,
12244 window,
12245 cx,
12246 ),
12247 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12248 }
12249 }
12250 }
12251
12252 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12253 if self.read_only(cx) {
12254 return;
12255 }
12256
12257 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12258
12259 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12260 if let Some((selections, _)) =
12261 self.selection_history.transaction(transaction_id).cloned()
12262 {
12263 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12264 s.select_anchors(selections.to_vec());
12265 });
12266 } else {
12267 log::error!(
12268 "No entry in selection_history found for undo. \
12269 This may correspond to a bug where undo does not update the selection. \
12270 If this is occurring, please add details to \
12271 https://github.com/zed-industries/zed/issues/22692"
12272 );
12273 }
12274 self.request_autoscroll(Autoscroll::fit(), cx);
12275 self.unmark_text(window, cx);
12276 self.refresh_edit_prediction(true, false, window, cx);
12277 cx.emit(EditorEvent::Edited { transaction_id });
12278 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12279 }
12280 }
12281
12282 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12283 if self.read_only(cx) {
12284 return;
12285 }
12286
12287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12288
12289 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12290 if let Some((_, Some(selections))) =
12291 self.selection_history.transaction(transaction_id).cloned()
12292 {
12293 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12294 s.select_anchors(selections.to_vec());
12295 });
12296 } else {
12297 log::error!(
12298 "No entry in selection_history found for redo. \
12299 This may correspond to a bug where undo does not update the selection. \
12300 If this is occurring, please add details to \
12301 https://github.com/zed-industries/zed/issues/22692"
12302 );
12303 }
12304 self.request_autoscroll(Autoscroll::fit(), cx);
12305 self.unmark_text(window, cx);
12306 self.refresh_edit_prediction(true, false, window, cx);
12307 cx.emit(EditorEvent::Edited { transaction_id });
12308 }
12309 }
12310
12311 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12312 self.buffer
12313 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12314 }
12315
12316 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12317 self.buffer
12318 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12319 }
12320
12321 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12322 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12323 self.change_selections(Default::default(), window, cx, |s| {
12324 s.move_with(|map, selection| {
12325 let cursor = if selection.is_empty() {
12326 movement::left(map, selection.start)
12327 } else {
12328 selection.start
12329 };
12330 selection.collapse_to(cursor, SelectionGoal::None);
12331 });
12332 })
12333 }
12334
12335 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12337 self.change_selections(Default::default(), window, cx, |s| {
12338 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12339 })
12340 }
12341
12342 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12343 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12344 self.change_selections(Default::default(), window, cx, |s| {
12345 s.move_with(|map, selection| {
12346 let cursor = if selection.is_empty() {
12347 movement::right(map, selection.end)
12348 } else {
12349 selection.end
12350 };
12351 selection.collapse_to(cursor, SelectionGoal::None)
12352 });
12353 })
12354 }
12355
12356 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12358 self.change_selections(Default::default(), window, cx, |s| {
12359 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12360 })
12361 }
12362
12363 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12364 if self.take_rename(true, window, cx).is_some() {
12365 return;
12366 }
12367
12368 if self.mode.is_single_line() {
12369 cx.propagate();
12370 return;
12371 }
12372
12373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12374
12375 let text_layout_details = &self.text_layout_details(window);
12376 let selection_count = self.selections.count();
12377 let first_selection = self.selections.first_anchor();
12378
12379 self.change_selections(Default::default(), window, cx, |s| {
12380 s.move_with(|map, selection| {
12381 if !selection.is_empty() {
12382 selection.goal = SelectionGoal::None;
12383 }
12384 let (cursor, goal) = movement::up(
12385 map,
12386 selection.start,
12387 selection.goal,
12388 false,
12389 text_layout_details,
12390 );
12391 selection.collapse_to(cursor, goal);
12392 });
12393 });
12394
12395 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12396 {
12397 cx.propagate();
12398 }
12399 }
12400
12401 pub fn move_up_by_lines(
12402 &mut self,
12403 action: &MoveUpByLines,
12404 window: &mut Window,
12405 cx: &mut Context<Self>,
12406 ) {
12407 if self.take_rename(true, window, cx).is_some() {
12408 return;
12409 }
12410
12411 if self.mode.is_single_line() {
12412 cx.propagate();
12413 return;
12414 }
12415
12416 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12417
12418 let text_layout_details = &self.text_layout_details(window);
12419
12420 self.change_selections(Default::default(), window, cx, |s| {
12421 s.move_with(|map, selection| {
12422 if !selection.is_empty() {
12423 selection.goal = SelectionGoal::None;
12424 }
12425 let (cursor, goal) = movement::up_by_rows(
12426 map,
12427 selection.start,
12428 action.lines,
12429 selection.goal,
12430 false,
12431 text_layout_details,
12432 );
12433 selection.collapse_to(cursor, goal);
12434 });
12435 })
12436 }
12437
12438 pub fn move_down_by_lines(
12439 &mut self,
12440 action: &MoveDownByLines,
12441 window: &mut Window,
12442 cx: &mut Context<Self>,
12443 ) {
12444 if self.take_rename(true, window, cx).is_some() {
12445 return;
12446 }
12447
12448 if self.mode.is_single_line() {
12449 cx.propagate();
12450 return;
12451 }
12452
12453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12454
12455 let text_layout_details = &self.text_layout_details(window);
12456
12457 self.change_selections(Default::default(), window, cx, |s| {
12458 s.move_with(|map, selection| {
12459 if !selection.is_empty() {
12460 selection.goal = SelectionGoal::None;
12461 }
12462 let (cursor, goal) = movement::down_by_rows(
12463 map,
12464 selection.start,
12465 action.lines,
12466 selection.goal,
12467 false,
12468 text_layout_details,
12469 );
12470 selection.collapse_to(cursor, goal);
12471 });
12472 })
12473 }
12474
12475 pub fn select_down_by_lines(
12476 &mut self,
12477 action: &SelectDownByLines,
12478 window: &mut Window,
12479 cx: &mut Context<Self>,
12480 ) {
12481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12482 let text_layout_details = &self.text_layout_details(window);
12483 self.change_selections(Default::default(), window, cx, |s| {
12484 s.move_heads_with(|map, head, goal| {
12485 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12486 })
12487 })
12488 }
12489
12490 pub fn select_up_by_lines(
12491 &mut self,
12492 action: &SelectUpByLines,
12493 window: &mut Window,
12494 cx: &mut Context<Self>,
12495 ) {
12496 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12497 let text_layout_details = &self.text_layout_details(window);
12498 self.change_selections(Default::default(), window, cx, |s| {
12499 s.move_heads_with(|map, head, goal| {
12500 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12501 })
12502 })
12503 }
12504
12505 pub fn select_page_up(
12506 &mut self,
12507 _: &SelectPageUp,
12508 window: &mut Window,
12509 cx: &mut Context<Self>,
12510 ) {
12511 let Some(row_count) = self.visible_row_count() else {
12512 return;
12513 };
12514
12515 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12516
12517 let text_layout_details = &self.text_layout_details(window);
12518
12519 self.change_selections(Default::default(), window, cx, |s| {
12520 s.move_heads_with(|map, head, goal| {
12521 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12522 })
12523 })
12524 }
12525
12526 pub fn move_page_up(
12527 &mut self,
12528 action: &MovePageUp,
12529 window: &mut Window,
12530 cx: &mut Context<Self>,
12531 ) {
12532 if self.take_rename(true, window, cx).is_some() {
12533 return;
12534 }
12535
12536 if self
12537 .context_menu
12538 .borrow_mut()
12539 .as_mut()
12540 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12541 .unwrap_or(false)
12542 {
12543 return;
12544 }
12545
12546 if matches!(self.mode, EditorMode::SingleLine) {
12547 cx.propagate();
12548 return;
12549 }
12550
12551 let Some(row_count) = self.visible_row_count() else {
12552 return;
12553 };
12554
12555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12556
12557 let effects = if action.center_cursor {
12558 SelectionEffects::scroll(Autoscroll::center())
12559 } else {
12560 SelectionEffects::default()
12561 };
12562
12563 let text_layout_details = &self.text_layout_details(window);
12564
12565 self.change_selections(effects, window, cx, |s| {
12566 s.move_with(|map, selection| {
12567 if !selection.is_empty() {
12568 selection.goal = SelectionGoal::None;
12569 }
12570 let (cursor, goal) = movement::up_by_rows(
12571 map,
12572 selection.end,
12573 row_count,
12574 selection.goal,
12575 false,
12576 text_layout_details,
12577 );
12578 selection.collapse_to(cursor, goal);
12579 });
12580 });
12581 }
12582
12583 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12585 let text_layout_details = &self.text_layout_details(window);
12586 self.change_selections(Default::default(), window, cx, |s| {
12587 s.move_heads_with(|map, head, goal| {
12588 movement::up(map, head, goal, false, text_layout_details)
12589 })
12590 })
12591 }
12592
12593 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12594 self.take_rename(true, window, cx);
12595
12596 if self.mode.is_single_line() {
12597 cx.propagate();
12598 return;
12599 }
12600
12601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12602
12603 let text_layout_details = &self.text_layout_details(window);
12604 let selection_count = self.selections.count();
12605 let first_selection = self.selections.first_anchor();
12606
12607 self.change_selections(Default::default(), window, cx, |s| {
12608 s.move_with(|map, selection| {
12609 if !selection.is_empty() {
12610 selection.goal = SelectionGoal::None;
12611 }
12612 let (cursor, goal) = movement::down(
12613 map,
12614 selection.end,
12615 selection.goal,
12616 false,
12617 text_layout_details,
12618 );
12619 selection.collapse_to(cursor, goal);
12620 });
12621 });
12622
12623 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12624 {
12625 cx.propagate();
12626 }
12627 }
12628
12629 pub fn select_page_down(
12630 &mut self,
12631 _: &SelectPageDown,
12632 window: &mut Window,
12633 cx: &mut Context<Self>,
12634 ) {
12635 let Some(row_count) = self.visible_row_count() else {
12636 return;
12637 };
12638
12639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12640
12641 let text_layout_details = &self.text_layout_details(window);
12642
12643 self.change_selections(Default::default(), window, cx, |s| {
12644 s.move_heads_with(|map, head, goal| {
12645 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12646 })
12647 })
12648 }
12649
12650 pub fn move_page_down(
12651 &mut self,
12652 action: &MovePageDown,
12653 window: &mut Window,
12654 cx: &mut Context<Self>,
12655 ) {
12656 if self.take_rename(true, window, cx).is_some() {
12657 return;
12658 }
12659
12660 if self
12661 .context_menu
12662 .borrow_mut()
12663 .as_mut()
12664 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12665 .unwrap_or(false)
12666 {
12667 return;
12668 }
12669
12670 if matches!(self.mode, EditorMode::SingleLine) {
12671 cx.propagate();
12672 return;
12673 }
12674
12675 let Some(row_count) = self.visible_row_count() else {
12676 return;
12677 };
12678
12679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12680
12681 let effects = if action.center_cursor {
12682 SelectionEffects::scroll(Autoscroll::center())
12683 } else {
12684 SelectionEffects::default()
12685 };
12686
12687 let text_layout_details = &self.text_layout_details(window);
12688 self.change_selections(effects, window, cx, |s| {
12689 s.move_with(|map, selection| {
12690 if !selection.is_empty() {
12691 selection.goal = SelectionGoal::None;
12692 }
12693 let (cursor, goal) = movement::down_by_rows(
12694 map,
12695 selection.end,
12696 row_count,
12697 selection.goal,
12698 false,
12699 text_layout_details,
12700 );
12701 selection.collapse_to(cursor, goal);
12702 });
12703 });
12704 }
12705
12706 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12707 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12708 let text_layout_details = &self.text_layout_details(window);
12709 self.change_selections(Default::default(), window, cx, |s| {
12710 s.move_heads_with(|map, head, goal| {
12711 movement::down(map, head, goal, false, text_layout_details)
12712 })
12713 });
12714 }
12715
12716 pub fn context_menu_first(
12717 &mut self,
12718 _: &ContextMenuFirst,
12719 window: &mut Window,
12720 cx: &mut Context<Self>,
12721 ) {
12722 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12723 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12724 }
12725 }
12726
12727 pub fn context_menu_prev(
12728 &mut self,
12729 _: &ContextMenuPrevious,
12730 window: &mut Window,
12731 cx: &mut Context<Self>,
12732 ) {
12733 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12734 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12735 }
12736 }
12737
12738 pub fn context_menu_next(
12739 &mut self,
12740 _: &ContextMenuNext,
12741 window: &mut Window,
12742 cx: &mut Context<Self>,
12743 ) {
12744 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12745 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12746 }
12747 }
12748
12749 pub fn context_menu_last(
12750 &mut self,
12751 _: &ContextMenuLast,
12752 window: &mut Window,
12753 cx: &mut Context<Self>,
12754 ) {
12755 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12756 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12757 }
12758 }
12759
12760 pub fn signature_help_prev(
12761 &mut self,
12762 _: &SignatureHelpPrevious,
12763 _: &mut Window,
12764 cx: &mut Context<Self>,
12765 ) {
12766 if let Some(popover) = self.signature_help_state.popover_mut() {
12767 if popover.current_signature == 0 {
12768 popover.current_signature = popover.signatures.len() - 1;
12769 } else {
12770 popover.current_signature -= 1;
12771 }
12772 cx.notify();
12773 }
12774 }
12775
12776 pub fn signature_help_next(
12777 &mut self,
12778 _: &SignatureHelpNext,
12779 _: &mut Window,
12780 cx: &mut Context<Self>,
12781 ) {
12782 if let Some(popover) = self.signature_help_state.popover_mut() {
12783 if popover.current_signature + 1 == popover.signatures.len() {
12784 popover.current_signature = 0;
12785 } else {
12786 popover.current_signature += 1;
12787 }
12788 cx.notify();
12789 }
12790 }
12791
12792 pub fn move_to_previous_word_start(
12793 &mut self,
12794 _: &MoveToPreviousWordStart,
12795 window: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) {
12798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12799 self.change_selections(Default::default(), window, cx, |s| {
12800 s.move_cursors_with(|map, head, _| {
12801 (
12802 movement::previous_word_start(map, head),
12803 SelectionGoal::None,
12804 )
12805 });
12806 })
12807 }
12808
12809 pub fn move_to_previous_subword_start(
12810 &mut self,
12811 _: &MoveToPreviousSubwordStart,
12812 window: &mut Window,
12813 cx: &mut Context<Self>,
12814 ) {
12815 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12816 self.change_selections(Default::default(), window, cx, |s| {
12817 s.move_cursors_with(|map, head, _| {
12818 (
12819 movement::previous_subword_start(map, head),
12820 SelectionGoal::None,
12821 )
12822 });
12823 })
12824 }
12825
12826 pub fn select_to_previous_word_start(
12827 &mut self,
12828 _: &SelectToPreviousWordStart,
12829 window: &mut Window,
12830 cx: &mut Context<Self>,
12831 ) {
12832 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12833 self.change_selections(Default::default(), window, cx, |s| {
12834 s.move_heads_with(|map, head, _| {
12835 (
12836 movement::previous_word_start(map, head),
12837 SelectionGoal::None,
12838 )
12839 });
12840 })
12841 }
12842
12843 pub fn select_to_previous_subword_start(
12844 &mut self,
12845 _: &SelectToPreviousSubwordStart,
12846 window: &mut Window,
12847 cx: &mut Context<Self>,
12848 ) {
12849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12850 self.change_selections(Default::default(), window, cx, |s| {
12851 s.move_heads_with(|map, head, _| {
12852 (
12853 movement::previous_subword_start(map, head),
12854 SelectionGoal::None,
12855 )
12856 });
12857 })
12858 }
12859
12860 pub fn delete_to_previous_word_start(
12861 &mut self,
12862 action: &DeleteToPreviousWordStart,
12863 window: &mut Window,
12864 cx: &mut Context<Self>,
12865 ) {
12866 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12867 self.transact(window, cx, |this, window, cx| {
12868 this.select_autoclose_pair(window, cx);
12869 this.change_selections(Default::default(), window, cx, |s| {
12870 s.move_with(|map, selection| {
12871 if selection.is_empty() {
12872 let cursor = if action.ignore_newlines {
12873 movement::previous_word_start(map, selection.head())
12874 } else {
12875 movement::previous_word_start_or_newline(map, selection.head())
12876 };
12877 selection.set_head(cursor, SelectionGoal::None);
12878 }
12879 });
12880 });
12881 this.insert("", window, cx);
12882 });
12883 }
12884
12885 pub fn delete_to_previous_subword_start(
12886 &mut self,
12887 _: &DeleteToPreviousSubwordStart,
12888 window: &mut Window,
12889 cx: &mut Context<Self>,
12890 ) {
12891 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12892 self.transact(window, cx, |this, window, cx| {
12893 this.select_autoclose_pair(window, cx);
12894 this.change_selections(Default::default(), window, cx, |s| {
12895 s.move_with(|map, selection| {
12896 if selection.is_empty() {
12897 let cursor = movement::previous_subword_start(map, selection.head());
12898 selection.set_head(cursor, SelectionGoal::None);
12899 }
12900 });
12901 });
12902 this.insert("", window, cx);
12903 });
12904 }
12905
12906 pub fn move_to_next_word_end(
12907 &mut self,
12908 _: &MoveToNextWordEnd,
12909 window: &mut Window,
12910 cx: &mut Context<Self>,
12911 ) {
12912 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12913 self.change_selections(Default::default(), window, cx, |s| {
12914 s.move_cursors_with(|map, head, _| {
12915 (movement::next_word_end(map, head), SelectionGoal::None)
12916 });
12917 })
12918 }
12919
12920 pub fn move_to_next_subword_end(
12921 &mut self,
12922 _: &MoveToNextSubwordEnd,
12923 window: &mut Window,
12924 cx: &mut Context<Self>,
12925 ) {
12926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12927 self.change_selections(Default::default(), window, cx, |s| {
12928 s.move_cursors_with(|map, head, _| {
12929 (movement::next_subword_end(map, head), SelectionGoal::None)
12930 });
12931 })
12932 }
12933
12934 pub fn select_to_next_word_end(
12935 &mut self,
12936 _: &SelectToNextWordEnd,
12937 window: &mut Window,
12938 cx: &mut Context<Self>,
12939 ) {
12940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12941 self.change_selections(Default::default(), window, cx, |s| {
12942 s.move_heads_with(|map, head, _| {
12943 (movement::next_word_end(map, head), SelectionGoal::None)
12944 });
12945 })
12946 }
12947
12948 pub fn select_to_next_subword_end(
12949 &mut self,
12950 _: &SelectToNextSubwordEnd,
12951 window: &mut Window,
12952 cx: &mut Context<Self>,
12953 ) {
12954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12955 self.change_selections(Default::default(), window, cx, |s| {
12956 s.move_heads_with(|map, head, _| {
12957 (movement::next_subword_end(map, head), SelectionGoal::None)
12958 });
12959 })
12960 }
12961
12962 pub fn delete_to_next_word_end(
12963 &mut self,
12964 action: &DeleteToNextWordEnd,
12965 window: &mut Window,
12966 cx: &mut Context<Self>,
12967 ) {
12968 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12969 self.transact(window, cx, |this, window, cx| {
12970 this.change_selections(Default::default(), window, cx, |s| {
12971 s.move_with(|map, selection| {
12972 if selection.is_empty() {
12973 let cursor = if action.ignore_newlines {
12974 movement::next_word_end(map, selection.head())
12975 } else {
12976 movement::next_word_end_or_newline(map, selection.head())
12977 };
12978 selection.set_head(cursor, SelectionGoal::None);
12979 }
12980 });
12981 });
12982 this.insert("", window, cx);
12983 });
12984 }
12985
12986 pub fn delete_to_next_subword_end(
12987 &mut self,
12988 _: &DeleteToNextSubwordEnd,
12989 window: &mut Window,
12990 cx: &mut Context<Self>,
12991 ) {
12992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12993 self.transact(window, cx, |this, window, cx| {
12994 this.change_selections(Default::default(), window, cx, |s| {
12995 s.move_with(|map, selection| {
12996 if selection.is_empty() {
12997 let cursor = movement::next_subword_end(map, selection.head());
12998 selection.set_head(cursor, SelectionGoal::None);
12999 }
13000 });
13001 });
13002 this.insert("", window, cx);
13003 });
13004 }
13005
13006 pub fn move_to_beginning_of_line(
13007 &mut self,
13008 action: &MoveToBeginningOfLine,
13009 window: &mut Window,
13010 cx: &mut Context<Self>,
13011 ) {
13012 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13013 self.change_selections(Default::default(), window, cx, |s| {
13014 s.move_cursors_with(|map, head, _| {
13015 (
13016 movement::indented_line_beginning(
13017 map,
13018 head,
13019 action.stop_at_soft_wraps,
13020 action.stop_at_indent,
13021 ),
13022 SelectionGoal::None,
13023 )
13024 });
13025 })
13026 }
13027
13028 pub fn select_to_beginning_of_line(
13029 &mut self,
13030 action: &SelectToBeginningOfLine,
13031 window: &mut Window,
13032 cx: &mut Context<Self>,
13033 ) {
13034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13035 self.change_selections(Default::default(), window, cx, |s| {
13036 s.move_heads_with(|map, head, _| {
13037 (
13038 movement::indented_line_beginning(
13039 map,
13040 head,
13041 action.stop_at_soft_wraps,
13042 action.stop_at_indent,
13043 ),
13044 SelectionGoal::None,
13045 )
13046 });
13047 });
13048 }
13049
13050 pub fn delete_to_beginning_of_line(
13051 &mut self,
13052 action: &DeleteToBeginningOfLine,
13053 window: &mut Window,
13054 cx: &mut Context<Self>,
13055 ) {
13056 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13057 self.transact(window, cx, |this, window, cx| {
13058 this.change_selections(Default::default(), window, cx, |s| {
13059 s.move_with(|_, selection| {
13060 selection.reversed = true;
13061 });
13062 });
13063
13064 this.select_to_beginning_of_line(
13065 &SelectToBeginningOfLine {
13066 stop_at_soft_wraps: false,
13067 stop_at_indent: action.stop_at_indent,
13068 },
13069 window,
13070 cx,
13071 );
13072 this.backspace(&Backspace, window, cx);
13073 });
13074 }
13075
13076 pub fn move_to_end_of_line(
13077 &mut self,
13078 action: &MoveToEndOfLine,
13079 window: &mut Window,
13080 cx: &mut Context<Self>,
13081 ) {
13082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13083 self.change_selections(Default::default(), window, cx, |s| {
13084 s.move_cursors_with(|map, head, _| {
13085 (
13086 movement::line_end(map, head, action.stop_at_soft_wraps),
13087 SelectionGoal::None,
13088 )
13089 });
13090 })
13091 }
13092
13093 pub fn select_to_end_of_line(
13094 &mut self,
13095 action: &SelectToEndOfLine,
13096 window: &mut Window,
13097 cx: &mut Context<Self>,
13098 ) {
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13100 self.change_selections(Default::default(), window, cx, |s| {
13101 s.move_heads_with(|map, head, _| {
13102 (
13103 movement::line_end(map, head, action.stop_at_soft_wraps),
13104 SelectionGoal::None,
13105 )
13106 });
13107 })
13108 }
13109
13110 pub fn delete_to_end_of_line(
13111 &mut self,
13112 _: &DeleteToEndOfLine,
13113 window: &mut Window,
13114 cx: &mut Context<Self>,
13115 ) {
13116 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13117 self.transact(window, cx, |this, window, cx| {
13118 this.select_to_end_of_line(
13119 &SelectToEndOfLine {
13120 stop_at_soft_wraps: false,
13121 },
13122 window,
13123 cx,
13124 );
13125 this.delete(&Delete, window, cx);
13126 });
13127 }
13128
13129 pub fn cut_to_end_of_line(
13130 &mut self,
13131 _: &CutToEndOfLine,
13132 window: &mut Window,
13133 cx: &mut Context<Self>,
13134 ) {
13135 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13136 self.transact(window, cx, |this, window, cx| {
13137 this.select_to_end_of_line(
13138 &SelectToEndOfLine {
13139 stop_at_soft_wraps: false,
13140 },
13141 window,
13142 cx,
13143 );
13144 this.cut(&Cut, window, cx);
13145 });
13146 }
13147
13148 pub fn move_to_start_of_paragraph(
13149 &mut self,
13150 _: &MoveToStartOfParagraph,
13151 window: &mut Window,
13152 cx: &mut Context<Self>,
13153 ) {
13154 if matches!(self.mode, EditorMode::SingleLine) {
13155 cx.propagate();
13156 return;
13157 }
13158 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13159 self.change_selections(Default::default(), window, cx, |s| {
13160 s.move_with(|map, selection| {
13161 selection.collapse_to(
13162 movement::start_of_paragraph(map, selection.head(), 1),
13163 SelectionGoal::None,
13164 )
13165 });
13166 })
13167 }
13168
13169 pub fn move_to_end_of_paragraph(
13170 &mut self,
13171 _: &MoveToEndOfParagraph,
13172 window: &mut Window,
13173 cx: &mut Context<Self>,
13174 ) {
13175 if matches!(self.mode, EditorMode::SingleLine) {
13176 cx.propagate();
13177 return;
13178 }
13179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13180 self.change_selections(Default::default(), window, cx, |s| {
13181 s.move_with(|map, selection| {
13182 selection.collapse_to(
13183 movement::end_of_paragraph(map, selection.head(), 1),
13184 SelectionGoal::None,
13185 )
13186 });
13187 })
13188 }
13189
13190 pub fn select_to_start_of_paragraph(
13191 &mut self,
13192 _: &SelectToStartOfParagraph,
13193 window: &mut Window,
13194 cx: &mut Context<Self>,
13195 ) {
13196 if matches!(self.mode, EditorMode::SingleLine) {
13197 cx.propagate();
13198 return;
13199 }
13200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13201 self.change_selections(Default::default(), window, cx, |s| {
13202 s.move_heads_with(|map, head, _| {
13203 (
13204 movement::start_of_paragraph(map, head, 1),
13205 SelectionGoal::None,
13206 )
13207 });
13208 })
13209 }
13210
13211 pub fn select_to_end_of_paragraph(
13212 &mut self,
13213 _: &SelectToEndOfParagraph,
13214 window: &mut Window,
13215 cx: &mut Context<Self>,
13216 ) {
13217 if matches!(self.mode, EditorMode::SingleLine) {
13218 cx.propagate();
13219 return;
13220 }
13221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13222 self.change_selections(Default::default(), window, cx, |s| {
13223 s.move_heads_with(|map, head, _| {
13224 (
13225 movement::end_of_paragraph(map, head, 1),
13226 SelectionGoal::None,
13227 )
13228 });
13229 })
13230 }
13231
13232 pub fn move_to_start_of_excerpt(
13233 &mut self,
13234 _: &MoveToStartOfExcerpt,
13235 window: &mut Window,
13236 cx: &mut Context<Self>,
13237 ) {
13238 if matches!(self.mode, EditorMode::SingleLine) {
13239 cx.propagate();
13240 return;
13241 }
13242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13243 self.change_selections(Default::default(), window, cx, |s| {
13244 s.move_with(|map, selection| {
13245 selection.collapse_to(
13246 movement::start_of_excerpt(
13247 map,
13248 selection.head(),
13249 workspace::searchable::Direction::Prev,
13250 ),
13251 SelectionGoal::None,
13252 )
13253 });
13254 })
13255 }
13256
13257 pub fn move_to_start_of_next_excerpt(
13258 &mut self,
13259 _: &MoveToStartOfNextExcerpt,
13260 window: &mut Window,
13261 cx: &mut Context<Self>,
13262 ) {
13263 if matches!(self.mode, EditorMode::SingleLine) {
13264 cx.propagate();
13265 return;
13266 }
13267
13268 self.change_selections(Default::default(), window, cx, |s| {
13269 s.move_with(|map, selection| {
13270 selection.collapse_to(
13271 movement::start_of_excerpt(
13272 map,
13273 selection.head(),
13274 workspace::searchable::Direction::Next,
13275 ),
13276 SelectionGoal::None,
13277 )
13278 });
13279 })
13280 }
13281
13282 pub fn move_to_end_of_excerpt(
13283 &mut self,
13284 _: &MoveToEndOfExcerpt,
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_with(|map, selection| {
13295 selection.collapse_to(
13296 movement::end_of_excerpt(
13297 map,
13298 selection.head(),
13299 workspace::searchable::Direction::Next,
13300 ),
13301 SelectionGoal::None,
13302 )
13303 });
13304 })
13305 }
13306
13307 pub fn move_to_end_of_previous_excerpt(
13308 &mut self,
13309 _: &MoveToEndOfPreviousExcerpt,
13310 window: &mut Window,
13311 cx: &mut Context<Self>,
13312 ) {
13313 if matches!(self.mode, EditorMode::SingleLine) {
13314 cx.propagate();
13315 return;
13316 }
13317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13318 self.change_selections(Default::default(), window, cx, |s| {
13319 s.move_with(|map, selection| {
13320 selection.collapse_to(
13321 movement::end_of_excerpt(
13322 map,
13323 selection.head(),
13324 workspace::searchable::Direction::Prev,
13325 ),
13326 SelectionGoal::None,
13327 )
13328 });
13329 })
13330 }
13331
13332 pub fn select_to_start_of_excerpt(
13333 &mut self,
13334 _: &SelectToStartOfExcerpt,
13335 window: &mut Window,
13336 cx: &mut Context<Self>,
13337 ) {
13338 if matches!(self.mode, EditorMode::SingleLine) {
13339 cx.propagate();
13340 return;
13341 }
13342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13343 self.change_selections(Default::default(), window, cx, |s| {
13344 s.move_heads_with(|map, head, _| {
13345 (
13346 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13347 SelectionGoal::None,
13348 )
13349 });
13350 })
13351 }
13352
13353 pub fn select_to_start_of_next_excerpt(
13354 &mut self,
13355 _: &SelectToStartOfNextExcerpt,
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_heads_with(|map, head, _| {
13366 (
13367 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13368 SelectionGoal::None,
13369 )
13370 });
13371 })
13372 }
13373
13374 pub fn select_to_end_of_excerpt(
13375 &mut self,
13376 _: &SelectToEndOfExcerpt,
13377 window: &mut Window,
13378 cx: &mut Context<Self>,
13379 ) {
13380 if matches!(self.mode, EditorMode::SingleLine) {
13381 cx.propagate();
13382 return;
13383 }
13384 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13385 self.change_selections(Default::default(), window, cx, |s| {
13386 s.move_heads_with(|map, head, _| {
13387 (
13388 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13389 SelectionGoal::None,
13390 )
13391 });
13392 })
13393 }
13394
13395 pub fn select_to_end_of_previous_excerpt(
13396 &mut self,
13397 _: &SelectToEndOfPreviousExcerpt,
13398 window: &mut Window,
13399 cx: &mut Context<Self>,
13400 ) {
13401 if matches!(self.mode, EditorMode::SingleLine) {
13402 cx.propagate();
13403 return;
13404 }
13405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13406 self.change_selections(Default::default(), window, cx, |s| {
13407 s.move_heads_with(|map, head, _| {
13408 (
13409 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13410 SelectionGoal::None,
13411 )
13412 });
13413 })
13414 }
13415
13416 pub fn move_to_beginning(
13417 &mut self,
13418 _: &MoveToBeginning,
13419 window: &mut Window,
13420 cx: &mut Context<Self>,
13421 ) {
13422 if matches!(self.mode, EditorMode::SingleLine) {
13423 cx.propagate();
13424 return;
13425 }
13426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13427 self.change_selections(Default::default(), window, cx, |s| {
13428 s.select_ranges(vec![0..0]);
13429 });
13430 }
13431
13432 pub fn select_to_beginning(
13433 &mut self,
13434 _: &SelectToBeginning,
13435 window: &mut Window,
13436 cx: &mut Context<Self>,
13437 ) {
13438 let mut selection = self.selections.last::<Point>(cx);
13439 selection.set_head(Point::zero(), SelectionGoal::None);
13440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13441 self.change_selections(Default::default(), window, cx, |s| {
13442 s.select(vec![selection]);
13443 });
13444 }
13445
13446 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13447 if matches!(self.mode, EditorMode::SingleLine) {
13448 cx.propagate();
13449 return;
13450 }
13451 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13452 let cursor = self.buffer.read(cx).read(cx).len();
13453 self.change_selections(Default::default(), window, cx, |s| {
13454 s.select_ranges(vec![cursor..cursor])
13455 });
13456 }
13457
13458 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13459 self.nav_history = nav_history;
13460 }
13461
13462 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13463 self.nav_history.as_ref()
13464 }
13465
13466 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13467 self.push_to_nav_history(
13468 self.selections.newest_anchor().head(),
13469 None,
13470 false,
13471 true,
13472 cx,
13473 );
13474 }
13475
13476 fn push_to_nav_history(
13477 &mut self,
13478 cursor_anchor: Anchor,
13479 new_position: Option<Point>,
13480 is_deactivate: bool,
13481 always: bool,
13482 cx: &mut Context<Self>,
13483 ) {
13484 if let Some(nav_history) = self.nav_history.as_mut() {
13485 let buffer = self.buffer.read(cx).read(cx);
13486 let cursor_position = cursor_anchor.to_point(&buffer);
13487 let scroll_state = self.scroll_manager.anchor();
13488 let scroll_top_row = scroll_state.top_row(&buffer);
13489 drop(buffer);
13490
13491 if let Some(new_position) = new_position {
13492 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13493 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13494 return;
13495 }
13496 }
13497
13498 nav_history.push(
13499 Some(NavigationData {
13500 cursor_anchor,
13501 cursor_position,
13502 scroll_anchor: scroll_state,
13503 scroll_top_row,
13504 }),
13505 cx,
13506 );
13507 cx.emit(EditorEvent::PushedToNavHistory {
13508 anchor: cursor_anchor,
13509 is_deactivate,
13510 })
13511 }
13512 }
13513
13514 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13515 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13516 let buffer = self.buffer.read(cx).snapshot(cx);
13517 let mut selection = self.selections.first::<usize>(cx);
13518 selection.set_head(buffer.len(), SelectionGoal::None);
13519 self.change_selections(Default::default(), window, cx, |s| {
13520 s.select(vec![selection]);
13521 });
13522 }
13523
13524 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13526 let end = self.buffer.read(cx).read(cx).len();
13527 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13528 s.select_ranges(vec![0..end]);
13529 });
13530 }
13531
13532 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13533 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13534 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13535 let mut selections = self.selections.all::<Point>(cx);
13536 let max_point = display_map.buffer_snapshot.max_point();
13537 for selection in &mut selections {
13538 let rows = selection.spanned_rows(true, &display_map);
13539 selection.start = Point::new(rows.start.0, 0);
13540 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13541 selection.reversed = false;
13542 }
13543 self.change_selections(Default::default(), window, cx, |s| {
13544 s.select(selections);
13545 });
13546 }
13547
13548 pub fn split_selection_into_lines(
13549 &mut self,
13550 action: &SplitSelectionIntoLines,
13551 window: &mut Window,
13552 cx: &mut Context<Self>,
13553 ) {
13554 let selections = self
13555 .selections
13556 .all::<Point>(cx)
13557 .into_iter()
13558 .map(|selection| selection.start..selection.end)
13559 .collect::<Vec<_>>();
13560 self.unfold_ranges(&selections, true, true, cx);
13561
13562 let mut new_selection_ranges = Vec::new();
13563 {
13564 let buffer = self.buffer.read(cx).read(cx);
13565 for selection in selections {
13566 for row in selection.start.row..selection.end.row {
13567 let line_start = Point::new(row, 0);
13568 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13569
13570 if action.keep_selections {
13571 // Keep the selection range for each line
13572 let selection_start = if row == selection.start.row {
13573 selection.start
13574 } else {
13575 line_start
13576 };
13577 new_selection_ranges.push(selection_start..line_end);
13578 } else {
13579 // Collapse to cursor at end of line
13580 new_selection_ranges.push(line_end..line_end);
13581 }
13582 }
13583
13584 let is_multiline_selection = selection.start.row != selection.end.row;
13585 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13586 // so this action feels more ergonomic when paired with other selection operations
13587 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13588 if !should_skip_last {
13589 if action.keep_selections {
13590 if is_multiline_selection {
13591 let line_start = Point::new(selection.end.row, 0);
13592 new_selection_ranges.push(line_start..selection.end);
13593 } else {
13594 new_selection_ranges.push(selection.start..selection.end);
13595 }
13596 } else {
13597 new_selection_ranges.push(selection.end..selection.end);
13598 }
13599 }
13600 }
13601 }
13602 self.change_selections(Default::default(), window, cx, |s| {
13603 s.select_ranges(new_selection_ranges);
13604 });
13605 }
13606
13607 pub fn add_selection_above(
13608 &mut self,
13609 _: &AddSelectionAbove,
13610 window: &mut Window,
13611 cx: &mut Context<Self>,
13612 ) {
13613 self.add_selection(true, window, cx);
13614 }
13615
13616 pub fn add_selection_below(
13617 &mut self,
13618 _: &AddSelectionBelow,
13619 window: &mut Window,
13620 cx: &mut Context<Self>,
13621 ) {
13622 self.add_selection(false, window, cx);
13623 }
13624
13625 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13627
13628 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13629 let all_selections = self.selections.all::<Point>(cx);
13630 let text_layout_details = self.text_layout_details(window);
13631
13632 let (mut columnar_selections, new_selections_to_columnarize) = {
13633 if let Some(state) = self.add_selections_state.as_ref() {
13634 let columnar_selection_ids: HashSet<_> = state
13635 .groups
13636 .iter()
13637 .flat_map(|group| group.stack.iter())
13638 .copied()
13639 .collect();
13640
13641 all_selections
13642 .into_iter()
13643 .partition(|s| columnar_selection_ids.contains(&s.id))
13644 } else {
13645 (Vec::new(), all_selections)
13646 }
13647 };
13648
13649 let mut state = self
13650 .add_selections_state
13651 .take()
13652 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13653
13654 for selection in new_selections_to_columnarize {
13655 let range = selection.display_range(&display_map).sorted();
13656 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13657 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13658 let positions = start_x.min(end_x)..start_x.max(end_x);
13659 let mut stack = Vec::new();
13660 for row in range.start.row().0..=range.end.row().0 {
13661 if let Some(selection) = self.selections.build_columnar_selection(
13662 &display_map,
13663 DisplayRow(row),
13664 &positions,
13665 selection.reversed,
13666 &text_layout_details,
13667 ) {
13668 stack.push(selection.id);
13669 columnar_selections.push(selection);
13670 }
13671 }
13672 if !stack.is_empty() {
13673 if above {
13674 stack.reverse();
13675 }
13676 state.groups.push(AddSelectionsGroup { above, stack });
13677 }
13678 }
13679
13680 let mut final_selections = Vec::new();
13681 let end_row = if above {
13682 DisplayRow(0)
13683 } else {
13684 display_map.max_point().row()
13685 };
13686
13687 let mut last_added_item_per_group = HashMap::default();
13688 for group in state.groups.iter_mut() {
13689 if let Some(last_id) = group.stack.last() {
13690 last_added_item_per_group.insert(*last_id, group);
13691 }
13692 }
13693
13694 for selection in columnar_selections {
13695 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13696 if above == group.above {
13697 let range = selection.display_range(&display_map).sorted();
13698 debug_assert_eq!(range.start.row(), range.end.row());
13699 let mut row = range.start.row();
13700 let positions =
13701 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13702 px(start)..px(end)
13703 } else {
13704 let start_x =
13705 display_map.x_for_display_point(range.start, &text_layout_details);
13706 let end_x =
13707 display_map.x_for_display_point(range.end, &text_layout_details);
13708 start_x.min(end_x)..start_x.max(end_x)
13709 };
13710
13711 let mut maybe_new_selection = None;
13712 while row != end_row {
13713 if above {
13714 row.0 -= 1;
13715 } else {
13716 row.0 += 1;
13717 }
13718 if let Some(new_selection) = self.selections.build_columnar_selection(
13719 &display_map,
13720 row,
13721 &positions,
13722 selection.reversed,
13723 &text_layout_details,
13724 ) {
13725 maybe_new_selection = Some(new_selection);
13726 break;
13727 }
13728 }
13729
13730 if let Some(new_selection) = maybe_new_selection {
13731 group.stack.push(new_selection.id);
13732 if above {
13733 final_selections.push(new_selection);
13734 final_selections.push(selection);
13735 } else {
13736 final_selections.push(selection);
13737 final_selections.push(new_selection);
13738 }
13739 } else {
13740 final_selections.push(selection);
13741 }
13742 } else {
13743 group.stack.pop();
13744 }
13745 } else {
13746 final_selections.push(selection);
13747 }
13748 }
13749
13750 self.change_selections(Default::default(), window, cx, |s| {
13751 s.select(final_selections);
13752 });
13753
13754 let final_selection_ids: HashSet<_> = self
13755 .selections
13756 .all::<Point>(cx)
13757 .iter()
13758 .map(|s| s.id)
13759 .collect();
13760 state.groups.retain_mut(|group| {
13761 // selections might get merged above so we remove invalid items from stacks
13762 group.stack.retain(|id| final_selection_ids.contains(id));
13763
13764 // single selection in stack can be treated as initial state
13765 group.stack.len() > 1
13766 });
13767
13768 if !state.groups.is_empty() {
13769 self.add_selections_state = Some(state);
13770 }
13771 }
13772
13773 fn select_match_ranges(
13774 &mut self,
13775 range: Range<usize>,
13776 reversed: bool,
13777 replace_newest: bool,
13778 auto_scroll: Option<Autoscroll>,
13779 window: &mut Window,
13780 cx: &mut Context<Editor>,
13781 ) {
13782 self.unfold_ranges(
13783 std::slice::from_ref(&range),
13784 false,
13785 auto_scroll.is_some(),
13786 cx,
13787 );
13788 let effects = if let Some(scroll) = auto_scroll {
13789 SelectionEffects::scroll(scroll)
13790 } else {
13791 SelectionEffects::no_scroll()
13792 };
13793 self.change_selections(effects, window, cx, |s| {
13794 if replace_newest {
13795 s.delete(s.newest_anchor().id);
13796 }
13797 if reversed {
13798 s.insert_range(range.end..range.start);
13799 } else {
13800 s.insert_range(range);
13801 }
13802 });
13803 }
13804
13805 pub fn select_next_match_internal(
13806 &mut self,
13807 display_map: &DisplaySnapshot,
13808 replace_newest: bool,
13809 autoscroll: Option<Autoscroll>,
13810 window: &mut Window,
13811 cx: &mut Context<Self>,
13812 ) -> Result<()> {
13813 let buffer = &display_map.buffer_snapshot;
13814 let mut selections = self.selections.all::<usize>(cx);
13815 if let Some(mut select_next_state) = self.select_next_state.take() {
13816 let query = &select_next_state.query;
13817 if !select_next_state.done {
13818 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13819 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13820 let mut next_selected_range = None;
13821
13822 let bytes_after_last_selection =
13823 buffer.bytes_in_range(last_selection.end..buffer.len());
13824 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13825 let query_matches = query
13826 .stream_find_iter(bytes_after_last_selection)
13827 .map(|result| (last_selection.end, result))
13828 .chain(
13829 query
13830 .stream_find_iter(bytes_before_first_selection)
13831 .map(|result| (0, result)),
13832 );
13833
13834 for (start_offset, query_match) in query_matches {
13835 let query_match = query_match.unwrap(); // can only fail due to I/O
13836 let offset_range =
13837 start_offset + query_match.start()..start_offset + query_match.end();
13838
13839 if !select_next_state.wordwise
13840 || (!buffer.is_inside_word(offset_range.start, false)
13841 && !buffer.is_inside_word(offset_range.end, false))
13842 {
13843 // TODO: This is n^2, because we might check all the selections
13844 if !selections
13845 .iter()
13846 .any(|selection| selection.range().overlaps(&offset_range))
13847 {
13848 next_selected_range = Some(offset_range);
13849 break;
13850 }
13851 }
13852 }
13853
13854 if let Some(next_selected_range) = next_selected_range {
13855 self.select_match_ranges(
13856 next_selected_range,
13857 last_selection.reversed,
13858 replace_newest,
13859 autoscroll,
13860 window,
13861 cx,
13862 );
13863 } else {
13864 select_next_state.done = true;
13865 }
13866 }
13867
13868 self.select_next_state = Some(select_next_state);
13869 } else {
13870 let mut only_carets = true;
13871 let mut same_text_selected = true;
13872 let mut selected_text = None;
13873
13874 let mut selections_iter = selections.iter().peekable();
13875 while let Some(selection) = selections_iter.next() {
13876 if selection.start != selection.end {
13877 only_carets = false;
13878 }
13879
13880 if same_text_selected {
13881 if selected_text.is_none() {
13882 selected_text =
13883 Some(buffer.text_for_range(selection.range()).collect::<String>());
13884 }
13885
13886 if let Some(next_selection) = selections_iter.peek() {
13887 if next_selection.range().len() == selection.range().len() {
13888 let next_selected_text = buffer
13889 .text_for_range(next_selection.range())
13890 .collect::<String>();
13891 if Some(next_selected_text) != selected_text {
13892 same_text_selected = false;
13893 selected_text = None;
13894 }
13895 } else {
13896 same_text_selected = false;
13897 selected_text = None;
13898 }
13899 }
13900 }
13901 }
13902
13903 if only_carets {
13904 for selection in &mut selections {
13905 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13906 selection.start = word_range.start;
13907 selection.end = word_range.end;
13908 selection.goal = SelectionGoal::None;
13909 selection.reversed = false;
13910 self.select_match_ranges(
13911 selection.start..selection.end,
13912 selection.reversed,
13913 replace_newest,
13914 autoscroll,
13915 window,
13916 cx,
13917 );
13918 }
13919
13920 if selections.len() == 1 {
13921 let selection = selections
13922 .last()
13923 .expect("ensured that there's only one selection");
13924 let query = buffer
13925 .text_for_range(selection.start..selection.end)
13926 .collect::<String>();
13927 let is_empty = query.is_empty();
13928 let select_state = SelectNextState {
13929 query: AhoCorasick::new(&[query])?,
13930 wordwise: true,
13931 done: is_empty,
13932 };
13933 self.select_next_state = Some(select_state);
13934 } else {
13935 self.select_next_state = None;
13936 }
13937 } else if let Some(selected_text) = selected_text {
13938 self.select_next_state = Some(SelectNextState {
13939 query: AhoCorasick::new(&[selected_text])?,
13940 wordwise: false,
13941 done: false,
13942 });
13943 self.select_next_match_internal(
13944 display_map,
13945 replace_newest,
13946 autoscroll,
13947 window,
13948 cx,
13949 )?;
13950 }
13951 }
13952 Ok(())
13953 }
13954
13955 pub fn select_all_matches(
13956 &mut self,
13957 _action: &SelectAllMatches,
13958 window: &mut Window,
13959 cx: &mut Context<Self>,
13960 ) -> Result<()> {
13961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13962
13963 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13964
13965 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13966 let Some(select_next_state) = self.select_next_state.as_mut() else {
13967 return Ok(());
13968 };
13969 if select_next_state.done {
13970 return Ok(());
13971 }
13972
13973 let mut new_selections = Vec::new();
13974
13975 let reversed = self.selections.oldest::<usize>(cx).reversed;
13976 let buffer = &display_map.buffer_snapshot;
13977 let query_matches = select_next_state
13978 .query
13979 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13980
13981 for query_match in query_matches.into_iter() {
13982 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13983 let offset_range = if reversed {
13984 query_match.end()..query_match.start()
13985 } else {
13986 query_match.start()..query_match.end()
13987 };
13988
13989 if !select_next_state.wordwise
13990 || (!buffer.is_inside_word(offset_range.start, false)
13991 && !buffer.is_inside_word(offset_range.end, false))
13992 {
13993 new_selections.push(offset_range.start..offset_range.end);
13994 }
13995 }
13996
13997 select_next_state.done = true;
13998
13999 if new_selections.is_empty() {
14000 log::error!("bug: new_selections is empty in select_all_matches");
14001 return Ok(());
14002 }
14003
14004 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14005 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14006 selections.select_ranges(new_selections)
14007 });
14008
14009 Ok(())
14010 }
14011
14012 pub fn select_next(
14013 &mut self,
14014 action: &SelectNext,
14015 window: &mut Window,
14016 cx: &mut Context<Self>,
14017 ) -> Result<()> {
14018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14019 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14020 self.select_next_match_internal(
14021 &display_map,
14022 action.replace_newest,
14023 Some(Autoscroll::newest()),
14024 window,
14025 cx,
14026 )?;
14027 Ok(())
14028 }
14029
14030 pub fn select_previous(
14031 &mut self,
14032 action: &SelectPrevious,
14033 window: &mut Window,
14034 cx: &mut Context<Self>,
14035 ) -> Result<()> {
14036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14038 let buffer = &display_map.buffer_snapshot;
14039 let mut selections = self.selections.all::<usize>(cx);
14040 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14041 let query = &select_prev_state.query;
14042 if !select_prev_state.done {
14043 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14044 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14045 let mut next_selected_range = None;
14046 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14047 let bytes_before_last_selection =
14048 buffer.reversed_bytes_in_range(0..last_selection.start);
14049 let bytes_after_first_selection =
14050 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14051 let query_matches = query
14052 .stream_find_iter(bytes_before_last_selection)
14053 .map(|result| (last_selection.start, result))
14054 .chain(
14055 query
14056 .stream_find_iter(bytes_after_first_selection)
14057 .map(|result| (buffer.len(), result)),
14058 );
14059 for (end_offset, query_match) in query_matches {
14060 let query_match = query_match.unwrap(); // can only fail due to I/O
14061 let offset_range =
14062 end_offset - query_match.end()..end_offset - query_match.start();
14063
14064 if !select_prev_state.wordwise
14065 || (!buffer.is_inside_word(offset_range.start, false)
14066 && !buffer.is_inside_word(offset_range.end, false))
14067 {
14068 next_selected_range = Some(offset_range);
14069 break;
14070 }
14071 }
14072
14073 if let Some(next_selected_range) = next_selected_range {
14074 self.select_match_ranges(
14075 next_selected_range,
14076 last_selection.reversed,
14077 action.replace_newest,
14078 Some(Autoscroll::newest()),
14079 window,
14080 cx,
14081 );
14082 } else {
14083 select_prev_state.done = true;
14084 }
14085 }
14086
14087 self.select_prev_state = Some(select_prev_state);
14088 } else {
14089 let mut only_carets = true;
14090 let mut same_text_selected = true;
14091 let mut selected_text = None;
14092
14093 let mut selections_iter = selections.iter().peekable();
14094 while let Some(selection) = selections_iter.next() {
14095 if selection.start != selection.end {
14096 only_carets = false;
14097 }
14098
14099 if same_text_selected {
14100 if selected_text.is_none() {
14101 selected_text =
14102 Some(buffer.text_for_range(selection.range()).collect::<String>());
14103 }
14104
14105 if let Some(next_selection) = selections_iter.peek() {
14106 if next_selection.range().len() == selection.range().len() {
14107 let next_selected_text = buffer
14108 .text_for_range(next_selection.range())
14109 .collect::<String>();
14110 if Some(next_selected_text) != selected_text {
14111 same_text_selected = false;
14112 selected_text = None;
14113 }
14114 } else {
14115 same_text_selected = false;
14116 selected_text = None;
14117 }
14118 }
14119 }
14120 }
14121
14122 if only_carets {
14123 for selection in &mut selections {
14124 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14125 selection.start = word_range.start;
14126 selection.end = word_range.end;
14127 selection.goal = SelectionGoal::None;
14128 selection.reversed = false;
14129 self.select_match_ranges(
14130 selection.start..selection.end,
14131 selection.reversed,
14132 action.replace_newest,
14133 Some(Autoscroll::newest()),
14134 window,
14135 cx,
14136 );
14137 }
14138 if selections.len() == 1 {
14139 let selection = selections
14140 .last()
14141 .expect("ensured that there's only one selection");
14142 let query = buffer
14143 .text_for_range(selection.start..selection.end)
14144 .collect::<String>();
14145 let is_empty = query.is_empty();
14146 let select_state = SelectNextState {
14147 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14148 wordwise: true,
14149 done: is_empty,
14150 };
14151 self.select_prev_state = Some(select_state);
14152 } else {
14153 self.select_prev_state = None;
14154 }
14155 } else if let Some(selected_text) = selected_text {
14156 self.select_prev_state = Some(SelectNextState {
14157 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14158 wordwise: false,
14159 done: false,
14160 });
14161 self.select_previous(action, window, cx)?;
14162 }
14163 }
14164 Ok(())
14165 }
14166
14167 pub fn find_next_match(
14168 &mut self,
14169 _: &FindNextMatch,
14170 window: &mut Window,
14171 cx: &mut Context<Self>,
14172 ) -> Result<()> {
14173 let selections = self.selections.disjoint_anchors();
14174 match selections.first() {
14175 Some(first) if selections.len() >= 2 => {
14176 self.change_selections(Default::default(), window, cx, |s| {
14177 s.select_ranges([first.range()]);
14178 });
14179 }
14180 _ => self.select_next(
14181 &SelectNext {
14182 replace_newest: true,
14183 },
14184 window,
14185 cx,
14186 )?,
14187 }
14188 Ok(())
14189 }
14190
14191 pub fn find_previous_match(
14192 &mut self,
14193 _: &FindPreviousMatch,
14194 window: &mut Window,
14195 cx: &mut Context<Self>,
14196 ) -> Result<()> {
14197 let selections = self.selections.disjoint_anchors();
14198 match selections.last() {
14199 Some(last) if selections.len() >= 2 => {
14200 self.change_selections(Default::default(), window, cx, |s| {
14201 s.select_ranges([last.range()]);
14202 });
14203 }
14204 _ => self.select_previous(
14205 &SelectPrevious {
14206 replace_newest: true,
14207 },
14208 window,
14209 cx,
14210 )?,
14211 }
14212 Ok(())
14213 }
14214
14215 pub fn toggle_comments(
14216 &mut self,
14217 action: &ToggleComments,
14218 window: &mut Window,
14219 cx: &mut Context<Self>,
14220 ) {
14221 if self.read_only(cx) {
14222 return;
14223 }
14224 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14225 let text_layout_details = &self.text_layout_details(window);
14226 self.transact(window, cx, |this, window, cx| {
14227 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14228 let mut edits = Vec::new();
14229 let mut selection_edit_ranges = Vec::new();
14230 let mut last_toggled_row = None;
14231 let snapshot = this.buffer.read(cx).read(cx);
14232 let empty_str: Arc<str> = Arc::default();
14233 let mut suffixes_inserted = Vec::new();
14234 let ignore_indent = action.ignore_indent;
14235
14236 fn comment_prefix_range(
14237 snapshot: &MultiBufferSnapshot,
14238 row: MultiBufferRow,
14239 comment_prefix: &str,
14240 comment_prefix_whitespace: &str,
14241 ignore_indent: bool,
14242 ) -> Range<Point> {
14243 let indent_size = if ignore_indent {
14244 0
14245 } else {
14246 snapshot.indent_size_for_line(row).len
14247 };
14248
14249 let start = Point::new(row.0, indent_size);
14250
14251 let mut line_bytes = snapshot
14252 .bytes_in_range(start..snapshot.max_point())
14253 .flatten()
14254 .copied();
14255
14256 // If this line currently begins with the line comment prefix, then record
14257 // the range containing the prefix.
14258 if line_bytes
14259 .by_ref()
14260 .take(comment_prefix.len())
14261 .eq(comment_prefix.bytes())
14262 {
14263 // Include any whitespace that matches the comment prefix.
14264 let matching_whitespace_len = line_bytes
14265 .zip(comment_prefix_whitespace.bytes())
14266 .take_while(|(a, b)| a == b)
14267 .count() as u32;
14268 let end = Point::new(
14269 start.row,
14270 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14271 );
14272 start..end
14273 } else {
14274 start..start
14275 }
14276 }
14277
14278 fn comment_suffix_range(
14279 snapshot: &MultiBufferSnapshot,
14280 row: MultiBufferRow,
14281 comment_suffix: &str,
14282 comment_suffix_has_leading_space: bool,
14283 ) -> Range<Point> {
14284 let end = Point::new(row.0, snapshot.line_len(row));
14285 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14286
14287 let mut line_end_bytes = snapshot
14288 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14289 .flatten()
14290 .copied();
14291
14292 let leading_space_len = if suffix_start_column > 0
14293 && line_end_bytes.next() == Some(b' ')
14294 && comment_suffix_has_leading_space
14295 {
14296 1
14297 } else {
14298 0
14299 };
14300
14301 // If this line currently begins with the line comment prefix, then record
14302 // the range containing the prefix.
14303 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14304 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14305 start..end
14306 } else {
14307 end..end
14308 }
14309 }
14310
14311 // TODO: Handle selections that cross excerpts
14312 for selection in &mut selections {
14313 let start_column = snapshot
14314 .indent_size_for_line(MultiBufferRow(selection.start.row))
14315 .len;
14316 let language = if let Some(language) =
14317 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14318 {
14319 language
14320 } else {
14321 continue;
14322 };
14323
14324 selection_edit_ranges.clear();
14325
14326 // If multiple selections contain a given row, avoid processing that
14327 // row more than once.
14328 let mut start_row = MultiBufferRow(selection.start.row);
14329 if last_toggled_row == Some(start_row) {
14330 start_row = start_row.next_row();
14331 }
14332 let end_row =
14333 if selection.end.row > selection.start.row && selection.end.column == 0 {
14334 MultiBufferRow(selection.end.row - 1)
14335 } else {
14336 MultiBufferRow(selection.end.row)
14337 };
14338 last_toggled_row = Some(end_row);
14339
14340 if start_row > end_row {
14341 continue;
14342 }
14343
14344 // If the language has line comments, toggle those.
14345 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14346
14347 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14348 if ignore_indent {
14349 full_comment_prefixes = full_comment_prefixes
14350 .into_iter()
14351 .map(|s| Arc::from(s.trim_end()))
14352 .collect();
14353 }
14354
14355 if !full_comment_prefixes.is_empty() {
14356 let first_prefix = full_comment_prefixes
14357 .first()
14358 .expect("prefixes is non-empty");
14359 let prefix_trimmed_lengths = full_comment_prefixes
14360 .iter()
14361 .map(|p| p.trim_end_matches(' ').len())
14362 .collect::<SmallVec<[usize; 4]>>();
14363
14364 let mut all_selection_lines_are_comments = true;
14365
14366 for row in start_row.0..=end_row.0 {
14367 let row = MultiBufferRow(row);
14368 if start_row < end_row && snapshot.is_line_blank(row) {
14369 continue;
14370 }
14371
14372 let prefix_range = full_comment_prefixes
14373 .iter()
14374 .zip(prefix_trimmed_lengths.iter().copied())
14375 .map(|(prefix, trimmed_prefix_len)| {
14376 comment_prefix_range(
14377 snapshot.deref(),
14378 row,
14379 &prefix[..trimmed_prefix_len],
14380 &prefix[trimmed_prefix_len..],
14381 ignore_indent,
14382 )
14383 })
14384 .max_by_key(|range| range.end.column - range.start.column)
14385 .expect("prefixes is non-empty");
14386
14387 if prefix_range.is_empty() {
14388 all_selection_lines_are_comments = false;
14389 }
14390
14391 selection_edit_ranges.push(prefix_range);
14392 }
14393
14394 if all_selection_lines_are_comments {
14395 edits.extend(
14396 selection_edit_ranges
14397 .iter()
14398 .cloned()
14399 .map(|range| (range, empty_str.clone())),
14400 );
14401 } else {
14402 let min_column = selection_edit_ranges
14403 .iter()
14404 .map(|range| range.start.column)
14405 .min()
14406 .unwrap_or(0);
14407 edits.extend(selection_edit_ranges.iter().map(|range| {
14408 let position = Point::new(range.start.row, min_column);
14409 (position..position, first_prefix.clone())
14410 }));
14411 }
14412 } else if let Some(BlockCommentConfig {
14413 start: full_comment_prefix,
14414 end: comment_suffix,
14415 ..
14416 }) = language.block_comment()
14417 {
14418 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14419 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14420 let prefix_range = comment_prefix_range(
14421 snapshot.deref(),
14422 start_row,
14423 comment_prefix,
14424 comment_prefix_whitespace,
14425 ignore_indent,
14426 );
14427 let suffix_range = comment_suffix_range(
14428 snapshot.deref(),
14429 end_row,
14430 comment_suffix.trim_start_matches(' '),
14431 comment_suffix.starts_with(' '),
14432 );
14433
14434 if prefix_range.is_empty() || suffix_range.is_empty() {
14435 edits.push((
14436 prefix_range.start..prefix_range.start,
14437 full_comment_prefix.clone(),
14438 ));
14439 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14440 suffixes_inserted.push((end_row, comment_suffix.len()));
14441 } else {
14442 edits.push((prefix_range, empty_str.clone()));
14443 edits.push((suffix_range, empty_str.clone()));
14444 }
14445 } else {
14446 continue;
14447 }
14448 }
14449
14450 drop(snapshot);
14451 this.buffer.update(cx, |buffer, cx| {
14452 buffer.edit(edits, None, cx);
14453 });
14454
14455 // Adjust selections so that they end before any comment suffixes that
14456 // were inserted.
14457 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14458 let mut selections = this.selections.all::<Point>(cx);
14459 let snapshot = this.buffer.read(cx).read(cx);
14460 for selection in &mut selections {
14461 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14462 match row.cmp(&MultiBufferRow(selection.end.row)) {
14463 Ordering::Less => {
14464 suffixes_inserted.next();
14465 continue;
14466 }
14467 Ordering::Greater => break,
14468 Ordering::Equal => {
14469 if selection.end.column == snapshot.line_len(row) {
14470 if selection.is_empty() {
14471 selection.start.column -= suffix_len as u32;
14472 }
14473 selection.end.column -= suffix_len as u32;
14474 }
14475 break;
14476 }
14477 }
14478 }
14479 }
14480
14481 drop(snapshot);
14482 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14483
14484 let selections = this.selections.all::<Point>(cx);
14485 let selections_on_single_row = selections.windows(2).all(|selections| {
14486 selections[0].start.row == selections[1].start.row
14487 && selections[0].end.row == selections[1].end.row
14488 && selections[0].start.row == selections[0].end.row
14489 });
14490 let selections_selecting = selections
14491 .iter()
14492 .any(|selection| selection.start != selection.end);
14493 let advance_downwards = action.advance_downwards
14494 && selections_on_single_row
14495 && !selections_selecting
14496 && !matches!(this.mode, EditorMode::SingleLine);
14497
14498 if advance_downwards {
14499 let snapshot = this.buffer.read(cx).snapshot(cx);
14500
14501 this.change_selections(Default::default(), window, cx, |s| {
14502 s.move_cursors_with(|display_snapshot, display_point, _| {
14503 let mut point = display_point.to_point(display_snapshot);
14504 point.row += 1;
14505 point = snapshot.clip_point(point, Bias::Left);
14506 let display_point = point.to_display_point(display_snapshot);
14507 let goal = SelectionGoal::HorizontalPosition(
14508 display_snapshot
14509 .x_for_display_point(display_point, text_layout_details)
14510 .into(),
14511 );
14512 (display_point, goal)
14513 })
14514 });
14515 }
14516 });
14517 }
14518
14519 pub fn select_enclosing_symbol(
14520 &mut self,
14521 _: &SelectEnclosingSymbol,
14522 window: &mut Window,
14523 cx: &mut Context<Self>,
14524 ) {
14525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14526
14527 let buffer = self.buffer.read(cx).snapshot(cx);
14528 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14529
14530 fn update_selection(
14531 selection: &Selection<usize>,
14532 buffer_snap: &MultiBufferSnapshot,
14533 ) -> Option<Selection<usize>> {
14534 let cursor = selection.head();
14535 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14536 for symbol in symbols.iter().rev() {
14537 let start = symbol.range.start.to_offset(buffer_snap);
14538 let end = symbol.range.end.to_offset(buffer_snap);
14539 let new_range = start..end;
14540 if start < selection.start || end > selection.end {
14541 return Some(Selection {
14542 id: selection.id,
14543 start: new_range.start,
14544 end: new_range.end,
14545 goal: SelectionGoal::None,
14546 reversed: selection.reversed,
14547 });
14548 }
14549 }
14550 None
14551 }
14552
14553 let mut selected_larger_symbol = false;
14554 let new_selections = old_selections
14555 .iter()
14556 .map(|selection| match update_selection(selection, &buffer) {
14557 Some(new_selection) => {
14558 if new_selection.range() != selection.range() {
14559 selected_larger_symbol = true;
14560 }
14561 new_selection
14562 }
14563 None => selection.clone(),
14564 })
14565 .collect::<Vec<_>>();
14566
14567 if selected_larger_symbol {
14568 self.change_selections(Default::default(), window, cx, |s| {
14569 s.select(new_selections);
14570 });
14571 }
14572 }
14573
14574 pub fn select_larger_syntax_node(
14575 &mut self,
14576 _: &SelectLargerSyntaxNode,
14577 window: &mut Window,
14578 cx: &mut Context<Self>,
14579 ) {
14580 let Some(visible_row_count) = self.visible_row_count() else {
14581 return;
14582 };
14583 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14584 if old_selections.is_empty() {
14585 return;
14586 }
14587
14588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14589
14590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14591 let buffer = self.buffer.read(cx).snapshot(cx);
14592
14593 let mut selected_larger_node = false;
14594 let mut new_selections = old_selections
14595 .iter()
14596 .map(|selection| {
14597 let old_range = selection.start..selection.end;
14598
14599 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14600 // manually select word at selection
14601 if ["string_content", "inline"].contains(&node.kind()) {
14602 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14603 // ignore if word is already selected
14604 if !word_range.is_empty() && old_range != word_range {
14605 let (last_word_range, _) =
14606 buffer.surrounding_word(old_range.end, false);
14607 // only select word if start and end point belongs to same word
14608 if word_range == last_word_range {
14609 selected_larger_node = true;
14610 return Selection {
14611 id: selection.id,
14612 start: word_range.start,
14613 end: word_range.end,
14614 goal: SelectionGoal::None,
14615 reversed: selection.reversed,
14616 };
14617 }
14618 }
14619 }
14620 }
14621
14622 let mut new_range = old_range.clone();
14623 while let Some((_node, containing_range)) =
14624 buffer.syntax_ancestor(new_range.clone())
14625 {
14626 new_range = match containing_range {
14627 MultiOrSingleBufferOffsetRange::Single(_) => break,
14628 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14629 };
14630 if !display_map.intersects_fold(new_range.start)
14631 && !display_map.intersects_fold(new_range.end)
14632 {
14633 break;
14634 }
14635 }
14636
14637 selected_larger_node |= new_range != old_range;
14638 Selection {
14639 id: selection.id,
14640 start: new_range.start,
14641 end: new_range.end,
14642 goal: SelectionGoal::None,
14643 reversed: selection.reversed,
14644 }
14645 })
14646 .collect::<Vec<_>>();
14647
14648 if !selected_larger_node {
14649 return; // don't put this call in the history
14650 }
14651
14652 // scroll based on transformation done to the last selection created by the user
14653 let (last_old, last_new) = old_selections
14654 .last()
14655 .zip(new_selections.last().cloned())
14656 .expect("old_selections isn't empty");
14657
14658 // revert selection
14659 let is_selection_reversed = {
14660 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14661 new_selections.last_mut().expect("checked above").reversed =
14662 should_newest_selection_be_reversed;
14663 should_newest_selection_be_reversed
14664 };
14665
14666 if selected_larger_node {
14667 self.select_syntax_node_history.disable_clearing = true;
14668 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14669 s.select(new_selections.clone());
14670 });
14671 self.select_syntax_node_history.disable_clearing = false;
14672 }
14673
14674 let start_row = last_new.start.to_display_point(&display_map).row().0;
14675 let end_row = last_new.end.to_display_point(&display_map).row().0;
14676 let selection_height = end_row - start_row + 1;
14677 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14678
14679 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14680 let scroll_behavior = if fits_on_the_screen {
14681 self.request_autoscroll(Autoscroll::fit(), cx);
14682 SelectSyntaxNodeScrollBehavior::FitSelection
14683 } else if is_selection_reversed {
14684 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14685 SelectSyntaxNodeScrollBehavior::CursorTop
14686 } else {
14687 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14688 SelectSyntaxNodeScrollBehavior::CursorBottom
14689 };
14690
14691 self.select_syntax_node_history.push((
14692 old_selections,
14693 scroll_behavior,
14694 is_selection_reversed,
14695 ));
14696 }
14697
14698 pub fn select_smaller_syntax_node(
14699 &mut self,
14700 _: &SelectSmallerSyntaxNode,
14701 window: &mut Window,
14702 cx: &mut Context<Self>,
14703 ) {
14704 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14705
14706 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14707 self.select_syntax_node_history.pop()
14708 {
14709 if let Some(selection) = selections.last_mut() {
14710 selection.reversed = is_selection_reversed;
14711 }
14712
14713 self.select_syntax_node_history.disable_clearing = true;
14714 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14715 s.select(selections.to_vec());
14716 });
14717 self.select_syntax_node_history.disable_clearing = false;
14718
14719 match scroll_behavior {
14720 SelectSyntaxNodeScrollBehavior::CursorTop => {
14721 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14722 }
14723 SelectSyntaxNodeScrollBehavior::FitSelection => {
14724 self.request_autoscroll(Autoscroll::fit(), cx);
14725 }
14726 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14727 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14728 }
14729 }
14730 }
14731 }
14732
14733 pub fn unwrap_syntax_node(
14734 &mut self,
14735 _: &UnwrapSyntaxNode,
14736 window: &mut Window,
14737 cx: &mut Context<Self>,
14738 ) {
14739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14740
14741 let buffer = self.buffer.read(cx).snapshot(cx);
14742 let selections = self
14743 .selections
14744 .all::<usize>(cx)
14745 .into_iter()
14746 // subtracting the offset requires sorting
14747 .sorted_by_key(|i| i.start);
14748
14749 let full_edits = selections
14750 .into_iter()
14751 .filter_map(|selection| {
14752 // Only requires two branches once if-let-chains stabilize (#53667)
14753 let child = if !selection.is_empty() {
14754 selection.range()
14755 } else if let Some((_, ancestor_range)) =
14756 buffer.syntax_ancestor(selection.start..selection.end)
14757 {
14758 match ancestor_range {
14759 MultiOrSingleBufferOffsetRange::Single(range) => range,
14760 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14761 }
14762 } else {
14763 selection.range()
14764 };
14765
14766 let mut parent = child.clone();
14767 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14768 parent = match ancestor_range {
14769 MultiOrSingleBufferOffsetRange::Single(range) => range,
14770 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14771 };
14772 if parent.start < child.start || parent.end > child.end {
14773 break;
14774 }
14775 }
14776
14777 if parent == child {
14778 return None;
14779 }
14780 let text = buffer.text_for_range(child).collect::<String>();
14781 Some((selection.id, parent, text))
14782 })
14783 .collect::<Vec<_>>();
14784
14785 self.transact(window, cx, |this, window, cx| {
14786 this.buffer.update(cx, |buffer, cx| {
14787 buffer.edit(
14788 full_edits
14789 .iter()
14790 .map(|(_, p, t)| (p.clone(), t.clone()))
14791 .collect::<Vec<_>>(),
14792 None,
14793 cx,
14794 );
14795 });
14796 this.change_selections(Default::default(), window, cx, |s| {
14797 let mut offset = 0;
14798 let mut selections = vec![];
14799 for (id, parent, text) in full_edits {
14800 let start = parent.start - offset;
14801 offset += parent.len() - text.len();
14802 selections.push(Selection {
14803 id,
14804 start,
14805 end: start + text.len(),
14806 reversed: false,
14807 goal: Default::default(),
14808 });
14809 }
14810 s.select(selections);
14811 });
14812 });
14813 }
14814
14815 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14816 if !EditorSettings::get_global(cx).gutter.runnables {
14817 self.clear_tasks();
14818 return Task::ready(());
14819 }
14820 let project = self.project().map(Entity::downgrade);
14821 let task_sources = self.lsp_task_sources(cx);
14822 let multi_buffer = self.buffer.downgrade();
14823 cx.spawn_in(window, async move |editor, cx| {
14824 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14825 let Some(project) = project.and_then(|p| p.upgrade()) else {
14826 return;
14827 };
14828 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14829 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14830 }) else {
14831 return;
14832 };
14833
14834 let hide_runnables = project
14835 .update(cx, |project, _| project.is_via_collab())
14836 .unwrap_or(true);
14837 if hide_runnables {
14838 return;
14839 }
14840 let new_rows =
14841 cx.background_spawn({
14842 let snapshot = display_snapshot.clone();
14843 async move {
14844 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14845 }
14846 })
14847 .await;
14848 let Ok(lsp_tasks) =
14849 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14850 else {
14851 return;
14852 };
14853 let lsp_tasks = lsp_tasks.await;
14854
14855 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14856 lsp_tasks
14857 .into_iter()
14858 .flat_map(|(kind, tasks)| {
14859 tasks.into_iter().filter_map(move |(location, task)| {
14860 Some((kind.clone(), location?, task))
14861 })
14862 })
14863 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14864 let buffer = location.target.buffer;
14865 let buffer_snapshot = buffer.read(cx).snapshot();
14866 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14867 |(excerpt_id, snapshot, _)| {
14868 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14869 display_snapshot
14870 .buffer_snapshot
14871 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14872 } else {
14873 None
14874 }
14875 },
14876 );
14877 if let Some(offset) = offset {
14878 let task_buffer_range =
14879 location.target.range.to_point(&buffer_snapshot);
14880 let context_buffer_range =
14881 task_buffer_range.to_offset(&buffer_snapshot);
14882 let context_range = BufferOffset(context_buffer_range.start)
14883 ..BufferOffset(context_buffer_range.end);
14884
14885 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14886 .or_insert_with(|| RunnableTasks {
14887 templates: Vec::new(),
14888 offset,
14889 column: task_buffer_range.start.column,
14890 extra_variables: HashMap::default(),
14891 context_range,
14892 })
14893 .templates
14894 .push((kind, task.original_task().clone()));
14895 }
14896
14897 acc
14898 })
14899 }) else {
14900 return;
14901 };
14902
14903 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14904 buffer.language_settings(cx).tasks.prefer_lsp
14905 }) else {
14906 return;
14907 };
14908
14909 let rows = Self::runnable_rows(
14910 project,
14911 display_snapshot,
14912 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14913 new_rows,
14914 cx.clone(),
14915 )
14916 .await;
14917 editor
14918 .update(cx, |editor, _| {
14919 editor.clear_tasks();
14920 for (key, mut value) in rows {
14921 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14922 value.templates.extend(lsp_tasks.templates);
14923 }
14924
14925 editor.insert_tasks(key, value);
14926 }
14927 for (key, value) in lsp_tasks_by_rows {
14928 editor.insert_tasks(key, value);
14929 }
14930 })
14931 .ok();
14932 })
14933 }
14934 fn fetch_runnable_ranges(
14935 snapshot: &DisplaySnapshot,
14936 range: Range<Anchor>,
14937 ) -> Vec<language::RunnableRange> {
14938 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14939 }
14940
14941 fn runnable_rows(
14942 project: Entity<Project>,
14943 snapshot: DisplaySnapshot,
14944 prefer_lsp: bool,
14945 runnable_ranges: Vec<RunnableRange>,
14946 cx: AsyncWindowContext,
14947 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14948 cx.spawn(async move |cx| {
14949 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14950 for mut runnable in runnable_ranges {
14951 let Some(tasks) = cx
14952 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14953 .ok()
14954 else {
14955 continue;
14956 };
14957 let mut tasks = tasks.await;
14958
14959 if prefer_lsp {
14960 tasks.retain(|(task_kind, _)| {
14961 !matches!(task_kind, TaskSourceKind::Language { .. })
14962 });
14963 }
14964 if tasks.is_empty() {
14965 continue;
14966 }
14967
14968 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14969 let Some(row) = snapshot
14970 .buffer_snapshot
14971 .buffer_line_for_row(MultiBufferRow(point.row))
14972 .map(|(_, range)| range.start.row)
14973 else {
14974 continue;
14975 };
14976
14977 let context_range =
14978 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14979 runnable_rows.push((
14980 (runnable.buffer_id, row),
14981 RunnableTasks {
14982 templates: tasks,
14983 offset: snapshot
14984 .buffer_snapshot
14985 .anchor_before(runnable.run_range.start),
14986 context_range,
14987 column: point.column,
14988 extra_variables: runnable.extra_captures,
14989 },
14990 ));
14991 }
14992 runnable_rows
14993 })
14994 }
14995
14996 fn templates_with_tags(
14997 project: &Entity<Project>,
14998 runnable: &mut Runnable,
14999 cx: &mut App,
15000 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15001 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15002 let (worktree_id, file) = project
15003 .buffer_for_id(runnable.buffer, cx)
15004 .and_then(|buffer| buffer.read(cx).file())
15005 .map(|file| (file.worktree_id(cx), file.clone()))
15006 .unzip();
15007
15008 (
15009 project.task_store().read(cx).task_inventory().cloned(),
15010 worktree_id,
15011 file,
15012 )
15013 });
15014
15015 let tags = mem::take(&mut runnable.tags);
15016 let language = runnable.language.clone();
15017 cx.spawn(async move |cx| {
15018 let mut templates_with_tags = Vec::new();
15019 if let Some(inventory) = inventory {
15020 for RunnableTag(tag) in tags {
15021 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15022 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15023 }) else {
15024 return templates_with_tags;
15025 };
15026 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15027 move |(_, template)| {
15028 template.tags.iter().any(|source_tag| source_tag == &tag)
15029 },
15030 ));
15031 }
15032 }
15033 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15034
15035 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15036 // Strongest source wins; if we have worktree tag binding, prefer that to
15037 // global and language bindings;
15038 // if we have a global binding, prefer that to language binding.
15039 let first_mismatch = templates_with_tags
15040 .iter()
15041 .position(|(tag_source, _)| tag_source != leading_tag_source);
15042 if let Some(index) = first_mismatch {
15043 templates_with_tags.truncate(index);
15044 }
15045 }
15046
15047 templates_with_tags
15048 })
15049 }
15050
15051 pub fn move_to_enclosing_bracket(
15052 &mut self,
15053 _: &MoveToEnclosingBracket,
15054 window: &mut Window,
15055 cx: &mut Context<Self>,
15056 ) {
15057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15058 self.change_selections(Default::default(), window, cx, |s| {
15059 s.move_offsets_with(|snapshot, selection| {
15060 let Some(enclosing_bracket_ranges) =
15061 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15062 else {
15063 return;
15064 };
15065
15066 let mut best_length = usize::MAX;
15067 let mut best_inside = false;
15068 let mut best_in_bracket_range = false;
15069 let mut best_destination = None;
15070 for (open, close) in enclosing_bracket_ranges {
15071 let close = close.to_inclusive();
15072 let length = close.end() - open.start;
15073 let inside = selection.start >= open.end && selection.end <= *close.start();
15074 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15075 || close.contains(&selection.head());
15076
15077 // If best is next to a bracket and current isn't, skip
15078 if !in_bracket_range && best_in_bracket_range {
15079 continue;
15080 }
15081
15082 // Prefer smaller lengths unless best is inside and current isn't
15083 if length > best_length && (best_inside || !inside) {
15084 continue;
15085 }
15086
15087 best_length = length;
15088 best_inside = inside;
15089 best_in_bracket_range = in_bracket_range;
15090 best_destination = Some(
15091 if close.contains(&selection.start) && close.contains(&selection.end) {
15092 if inside { open.end } else { open.start }
15093 } else if inside {
15094 *close.start()
15095 } else {
15096 *close.end()
15097 },
15098 );
15099 }
15100
15101 if let Some(destination) = best_destination {
15102 selection.collapse_to(destination, SelectionGoal::None);
15103 }
15104 })
15105 });
15106 }
15107
15108 pub fn undo_selection(
15109 &mut self,
15110 _: &UndoSelection,
15111 window: &mut Window,
15112 cx: &mut Context<Self>,
15113 ) {
15114 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15115 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15116 self.selection_history.mode = SelectionHistoryMode::Undoing;
15117 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15118 this.end_selection(window, cx);
15119 this.change_selections(
15120 SelectionEffects::scroll(Autoscroll::newest()),
15121 window,
15122 cx,
15123 |s| s.select_anchors(entry.selections.to_vec()),
15124 );
15125 });
15126 self.selection_history.mode = SelectionHistoryMode::Normal;
15127
15128 self.select_next_state = entry.select_next_state;
15129 self.select_prev_state = entry.select_prev_state;
15130 self.add_selections_state = entry.add_selections_state;
15131 }
15132 }
15133
15134 pub fn redo_selection(
15135 &mut self,
15136 _: &RedoSelection,
15137 window: &mut Window,
15138 cx: &mut Context<Self>,
15139 ) {
15140 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15141 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15142 self.selection_history.mode = SelectionHistoryMode::Redoing;
15143 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15144 this.end_selection(window, cx);
15145 this.change_selections(
15146 SelectionEffects::scroll(Autoscroll::newest()),
15147 window,
15148 cx,
15149 |s| s.select_anchors(entry.selections.to_vec()),
15150 );
15151 });
15152 self.selection_history.mode = SelectionHistoryMode::Normal;
15153
15154 self.select_next_state = entry.select_next_state;
15155 self.select_prev_state = entry.select_prev_state;
15156 self.add_selections_state = entry.add_selections_state;
15157 }
15158 }
15159
15160 pub fn expand_excerpts(
15161 &mut self,
15162 action: &ExpandExcerpts,
15163 _: &mut Window,
15164 cx: &mut Context<Self>,
15165 ) {
15166 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15167 }
15168
15169 pub fn expand_excerpts_down(
15170 &mut self,
15171 action: &ExpandExcerptsDown,
15172 _: &mut Window,
15173 cx: &mut Context<Self>,
15174 ) {
15175 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15176 }
15177
15178 pub fn expand_excerpts_up(
15179 &mut self,
15180 action: &ExpandExcerptsUp,
15181 _: &mut Window,
15182 cx: &mut Context<Self>,
15183 ) {
15184 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15185 }
15186
15187 pub fn expand_excerpts_for_direction(
15188 &mut self,
15189 lines: u32,
15190 direction: ExpandExcerptDirection,
15191
15192 cx: &mut Context<Self>,
15193 ) {
15194 let selections = self.selections.disjoint_anchors();
15195
15196 let lines = if lines == 0 {
15197 EditorSettings::get_global(cx).expand_excerpt_lines
15198 } else {
15199 lines
15200 };
15201
15202 self.buffer.update(cx, |buffer, cx| {
15203 let snapshot = buffer.snapshot(cx);
15204 let mut excerpt_ids = selections
15205 .iter()
15206 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15207 .collect::<Vec<_>>();
15208 excerpt_ids.sort();
15209 excerpt_ids.dedup();
15210 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15211 })
15212 }
15213
15214 pub fn expand_excerpt(
15215 &mut self,
15216 excerpt: ExcerptId,
15217 direction: ExpandExcerptDirection,
15218 window: &mut Window,
15219 cx: &mut Context<Self>,
15220 ) {
15221 let current_scroll_position = self.scroll_position(cx);
15222 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15223 let mut should_scroll_up = false;
15224
15225 if direction == ExpandExcerptDirection::Down {
15226 let multi_buffer = self.buffer.read(cx);
15227 let snapshot = multi_buffer.snapshot(cx);
15228 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15229 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15230 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15231 {
15232 let buffer_snapshot = buffer.read(cx).snapshot();
15233 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15234 let last_row = buffer_snapshot.max_point().row;
15235 let lines_below = last_row.saturating_sub(excerpt_end_row);
15236 should_scroll_up = lines_below >= lines_to_expand;
15237 }
15238 }
15239
15240 self.buffer.update(cx, |buffer, cx| {
15241 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15242 });
15243
15244 if should_scroll_up {
15245 let new_scroll_position =
15246 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15247 self.set_scroll_position(new_scroll_position, window, cx);
15248 }
15249 }
15250
15251 pub fn go_to_singleton_buffer_point(
15252 &mut self,
15253 point: Point,
15254 window: &mut Window,
15255 cx: &mut Context<Self>,
15256 ) {
15257 self.go_to_singleton_buffer_range(point..point, window, cx);
15258 }
15259
15260 pub fn go_to_singleton_buffer_range(
15261 &mut self,
15262 range: Range<Point>,
15263 window: &mut Window,
15264 cx: &mut Context<Self>,
15265 ) {
15266 let multibuffer = self.buffer().read(cx);
15267 let Some(buffer) = multibuffer.as_singleton() else {
15268 return;
15269 };
15270 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15271 return;
15272 };
15273 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15274 return;
15275 };
15276 self.change_selections(
15277 SelectionEffects::default().nav_history(true),
15278 window,
15279 cx,
15280 |s| s.select_anchor_ranges([start..end]),
15281 );
15282 }
15283
15284 pub fn go_to_diagnostic(
15285 &mut self,
15286 action: &GoToDiagnostic,
15287 window: &mut Window,
15288 cx: &mut Context<Self>,
15289 ) {
15290 if !self.diagnostics_enabled() {
15291 return;
15292 }
15293 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15294 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15295 }
15296
15297 pub fn go_to_prev_diagnostic(
15298 &mut self,
15299 action: &GoToPreviousDiagnostic,
15300 window: &mut Window,
15301 cx: &mut Context<Self>,
15302 ) {
15303 if !self.diagnostics_enabled() {
15304 return;
15305 }
15306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15307 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15308 }
15309
15310 pub fn go_to_diagnostic_impl(
15311 &mut self,
15312 direction: Direction,
15313 severity: GoToDiagnosticSeverityFilter,
15314 window: &mut Window,
15315 cx: &mut Context<Self>,
15316 ) {
15317 let buffer = self.buffer.read(cx).snapshot(cx);
15318 let selection = self.selections.newest::<usize>(cx);
15319
15320 let mut active_group_id = None;
15321 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15322 && active_group.active_range.start.to_offset(&buffer) == selection.start
15323 {
15324 active_group_id = Some(active_group.group_id);
15325 }
15326
15327 fn filtered(
15328 snapshot: EditorSnapshot,
15329 severity: GoToDiagnosticSeverityFilter,
15330 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15331 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15332 diagnostics
15333 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15334 .filter(|entry| entry.range.start != entry.range.end)
15335 .filter(|entry| !entry.diagnostic.is_unnecessary)
15336 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15337 }
15338
15339 let snapshot = self.snapshot(window, cx);
15340 let before = filtered(
15341 snapshot.clone(),
15342 severity,
15343 buffer
15344 .diagnostics_in_range(0..selection.start)
15345 .filter(|entry| entry.range.start <= selection.start),
15346 );
15347 let after = filtered(
15348 snapshot,
15349 severity,
15350 buffer
15351 .diagnostics_in_range(selection.start..buffer.len())
15352 .filter(|entry| entry.range.start >= selection.start),
15353 );
15354
15355 let mut found: Option<DiagnosticEntry<usize>> = None;
15356 if direction == Direction::Prev {
15357 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15358 {
15359 for diagnostic in prev_diagnostics.into_iter().rev() {
15360 if diagnostic.range.start != selection.start
15361 || active_group_id
15362 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15363 {
15364 found = Some(diagnostic);
15365 break 'outer;
15366 }
15367 }
15368 }
15369 } else {
15370 for diagnostic in after.chain(before) {
15371 if diagnostic.range.start != selection.start
15372 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15373 {
15374 found = Some(diagnostic);
15375 break;
15376 }
15377 }
15378 }
15379 let Some(next_diagnostic) = found else {
15380 return;
15381 };
15382
15383 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15384 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15385 return;
15386 };
15387 self.change_selections(Default::default(), window, cx, |s| {
15388 s.select_ranges(vec![
15389 next_diagnostic.range.start..next_diagnostic.range.start,
15390 ])
15391 });
15392 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15393 self.refresh_edit_prediction(false, true, window, cx);
15394 }
15395
15396 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15397 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15398 let snapshot = self.snapshot(window, cx);
15399 let selection = self.selections.newest::<Point>(cx);
15400 self.go_to_hunk_before_or_after_position(
15401 &snapshot,
15402 selection.head(),
15403 Direction::Next,
15404 window,
15405 cx,
15406 );
15407 }
15408
15409 pub fn go_to_hunk_before_or_after_position(
15410 &mut self,
15411 snapshot: &EditorSnapshot,
15412 position: Point,
15413 direction: Direction,
15414 window: &mut Window,
15415 cx: &mut Context<Editor>,
15416 ) {
15417 let row = if direction == Direction::Next {
15418 self.hunk_after_position(snapshot, position)
15419 .map(|hunk| hunk.row_range.start)
15420 } else {
15421 self.hunk_before_position(snapshot, position)
15422 };
15423
15424 if let Some(row) = row {
15425 let destination = Point::new(row.0, 0);
15426 let autoscroll = Autoscroll::center();
15427
15428 self.unfold_ranges(&[destination..destination], false, false, cx);
15429 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15430 s.select_ranges([destination..destination]);
15431 });
15432 }
15433 }
15434
15435 fn hunk_after_position(
15436 &mut self,
15437 snapshot: &EditorSnapshot,
15438 position: Point,
15439 ) -> Option<MultiBufferDiffHunk> {
15440 snapshot
15441 .buffer_snapshot
15442 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15443 .find(|hunk| hunk.row_range.start.0 > position.row)
15444 .or_else(|| {
15445 snapshot
15446 .buffer_snapshot
15447 .diff_hunks_in_range(Point::zero()..position)
15448 .find(|hunk| hunk.row_range.end.0 < position.row)
15449 })
15450 }
15451
15452 fn go_to_prev_hunk(
15453 &mut self,
15454 _: &GoToPreviousHunk,
15455 window: &mut Window,
15456 cx: &mut Context<Self>,
15457 ) {
15458 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15459 let snapshot = self.snapshot(window, cx);
15460 let selection = self.selections.newest::<Point>(cx);
15461 self.go_to_hunk_before_or_after_position(
15462 &snapshot,
15463 selection.head(),
15464 Direction::Prev,
15465 window,
15466 cx,
15467 );
15468 }
15469
15470 fn hunk_before_position(
15471 &mut self,
15472 snapshot: &EditorSnapshot,
15473 position: Point,
15474 ) -> Option<MultiBufferRow> {
15475 snapshot
15476 .buffer_snapshot
15477 .diff_hunk_before(position)
15478 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15479 }
15480
15481 fn go_to_next_change(
15482 &mut self,
15483 _: &GoToNextChange,
15484 window: &mut Window,
15485 cx: &mut Context<Self>,
15486 ) {
15487 if let Some(selections) = self
15488 .change_list
15489 .next_change(1, Direction::Next)
15490 .map(|s| s.to_vec())
15491 {
15492 self.change_selections(Default::default(), window, cx, |s| {
15493 let map = s.display_map();
15494 s.select_display_ranges(selections.iter().map(|a| {
15495 let point = a.to_display_point(&map);
15496 point..point
15497 }))
15498 })
15499 }
15500 }
15501
15502 fn go_to_previous_change(
15503 &mut self,
15504 _: &GoToPreviousChange,
15505 window: &mut Window,
15506 cx: &mut Context<Self>,
15507 ) {
15508 if let Some(selections) = self
15509 .change_list
15510 .next_change(1, Direction::Prev)
15511 .map(|s| s.to_vec())
15512 {
15513 self.change_selections(Default::default(), window, cx, |s| {
15514 let map = s.display_map();
15515 s.select_display_ranges(selections.iter().map(|a| {
15516 let point = a.to_display_point(&map);
15517 point..point
15518 }))
15519 })
15520 }
15521 }
15522
15523 fn go_to_line<T: 'static>(
15524 &mut self,
15525 position: Anchor,
15526 highlight_color: Option<Hsla>,
15527 window: &mut Window,
15528 cx: &mut Context<Self>,
15529 ) {
15530 let snapshot = self.snapshot(window, cx).display_snapshot;
15531 let position = position.to_point(&snapshot.buffer_snapshot);
15532 let start = snapshot
15533 .buffer_snapshot
15534 .clip_point(Point::new(position.row, 0), Bias::Left);
15535 let end = start + Point::new(1, 0);
15536 let start = snapshot.buffer_snapshot.anchor_before(start);
15537 let end = snapshot.buffer_snapshot.anchor_before(end);
15538
15539 self.highlight_rows::<T>(
15540 start..end,
15541 highlight_color
15542 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15543 Default::default(),
15544 cx,
15545 );
15546
15547 if self.buffer.read(cx).is_singleton() {
15548 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15549 }
15550 }
15551
15552 pub fn go_to_definition(
15553 &mut self,
15554 _: &GoToDefinition,
15555 window: &mut Window,
15556 cx: &mut Context<Self>,
15557 ) -> Task<Result<Navigated>> {
15558 let definition =
15559 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15560 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15561 cx.spawn_in(window, async move |editor, cx| {
15562 if definition.await? == Navigated::Yes {
15563 return Ok(Navigated::Yes);
15564 }
15565 match fallback_strategy {
15566 GoToDefinitionFallback::None => Ok(Navigated::No),
15567 GoToDefinitionFallback::FindAllReferences => {
15568 match editor.update_in(cx, |editor, window, cx| {
15569 editor.find_all_references(&FindAllReferences, window, cx)
15570 })? {
15571 Some(references) => references.await,
15572 None => Ok(Navigated::No),
15573 }
15574 }
15575 }
15576 })
15577 }
15578
15579 pub fn go_to_declaration(
15580 &mut self,
15581 _: &GoToDeclaration,
15582 window: &mut Window,
15583 cx: &mut Context<Self>,
15584 ) -> Task<Result<Navigated>> {
15585 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15586 }
15587
15588 pub fn go_to_declaration_split(
15589 &mut self,
15590 _: &GoToDeclaration,
15591 window: &mut Window,
15592 cx: &mut Context<Self>,
15593 ) -> Task<Result<Navigated>> {
15594 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15595 }
15596
15597 pub fn go_to_implementation(
15598 &mut self,
15599 _: &GoToImplementation,
15600 window: &mut Window,
15601 cx: &mut Context<Self>,
15602 ) -> Task<Result<Navigated>> {
15603 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15604 }
15605
15606 pub fn go_to_implementation_split(
15607 &mut self,
15608 _: &GoToImplementationSplit,
15609 window: &mut Window,
15610 cx: &mut Context<Self>,
15611 ) -> Task<Result<Navigated>> {
15612 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15613 }
15614
15615 pub fn go_to_type_definition(
15616 &mut self,
15617 _: &GoToTypeDefinition,
15618 window: &mut Window,
15619 cx: &mut Context<Self>,
15620 ) -> Task<Result<Navigated>> {
15621 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15622 }
15623
15624 pub fn go_to_definition_split(
15625 &mut self,
15626 _: &GoToDefinitionSplit,
15627 window: &mut Window,
15628 cx: &mut Context<Self>,
15629 ) -> Task<Result<Navigated>> {
15630 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15631 }
15632
15633 pub fn go_to_type_definition_split(
15634 &mut self,
15635 _: &GoToTypeDefinitionSplit,
15636 window: &mut Window,
15637 cx: &mut Context<Self>,
15638 ) -> Task<Result<Navigated>> {
15639 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15640 }
15641
15642 fn go_to_definition_of_kind(
15643 &mut self,
15644 kind: GotoDefinitionKind,
15645 split: bool,
15646 window: &mut Window,
15647 cx: &mut Context<Self>,
15648 ) -> Task<Result<Navigated>> {
15649 let Some(provider) = self.semantics_provider.clone() else {
15650 return Task::ready(Ok(Navigated::No));
15651 };
15652 let head = self.selections.newest::<usize>(cx).head();
15653 let buffer = self.buffer.read(cx);
15654 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15655 return Task::ready(Ok(Navigated::No));
15656 };
15657 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15658 return Task::ready(Ok(Navigated::No));
15659 };
15660
15661 cx.spawn_in(window, async move |editor, cx| {
15662 let definitions = definitions.await?;
15663 let navigated = editor
15664 .update_in(cx, |editor, window, cx| {
15665 editor.navigate_to_hover_links(
15666 Some(kind),
15667 definitions
15668 .into_iter()
15669 .filter(|location| {
15670 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15671 })
15672 .map(HoverLink::Text)
15673 .collect::<Vec<_>>(),
15674 split,
15675 window,
15676 cx,
15677 )
15678 })?
15679 .await?;
15680 anyhow::Ok(navigated)
15681 })
15682 }
15683
15684 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15685 let selection = self.selections.newest_anchor();
15686 let head = selection.head();
15687 let tail = selection.tail();
15688
15689 let Some((buffer, start_position)) =
15690 self.buffer.read(cx).text_anchor_for_position(head, cx)
15691 else {
15692 return;
15693 };
15694
15695 let end_position = if head != tail {
15696 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15697 return;
15698 };
15699 Some(pos)
15700 } else {
15701 None
15702 };
15703
15704 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15705 let url = if let Some(end_pos) = end_position {
15706 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15707 } else {
15708 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15709 };
15710
15711 if let Some(url) = url {
15712 editor.update(cx, |_, cx| {
15713 cx.open_url(&url);
15714 })
15715 } else {
15716 Ok(())
15717 }
15718 });
15719
15720 url_finder.detach();
15721 }
15722
15723 pub fn open_selected_filename(
15724 &mut self,
15725 _: &OpenSelectedFilename,
15726 window: &mut Window,
15727 cx: &mut Context<Self>,
15728 ) {
15729 let Some(workspace) = self.workspace() else {
15730 return;
15731 };
15732
15733 let position = self.selections.newest_anchor().head();
15734
15735 let Some((buffer, buffer_position)) =
15736 self.buffer.read(cx).text_anchor_for_position(position, cx)
15737 else {
15738 return;
15739 };
15740
15741 let project = self.project.clone();
15742
15743 cx.spawn_in(window, async move |_, cx| {
15744 let result = find_file(&buffer, project, buffer_position, cx).await;
15745
15746 if let Some((_, path)) = result {
15747 workspace
15748 .update_in(cx, |workspace, window, cx| {
15749 workspace.open_resolved_path(path, window, cx)
15750 })?
15751 .await?;
15752 }
15753 anyhow::Ok(())
15754 })
15755 .detach();
15756 }
15757
15758 pub(crate) fn navigate_to_hover_links(
15759 &mut self,
15760 kind: Option<GotoDefinitionKind>,
15761 definitions: Vec<HoverLink>,
15762 split: bool,
15763 window: &mut Window,
15764 cx: &mut Context<Editor>,
15765 ) -> Task<Result<Navigated>> {
15766 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15767 let mut first_url_or_file = None;
15768 let definitions: Vec<_> = definitions
15769 .into_iter()
15770 .filter_map(|def| match def {
15771 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15772 HoverLink::InlayHint(lsp_location, server_id) => {
15773 let computation =
15774 self.compute_target_location(lsp_location, server_id, window, cx);
15775 Some(cx.background_spawn(computation))
15776 }
15777 HoverLink::Url(url) => {
15778 first_url_or_file = Some(Either::Left(url));
15779 None
15780 }
15781 HoverLink::File(path) => {
15782 first_url_or_file = Some(Either::Right(path));
15783 None
15784 }
15785 })
15786 .collect();
15787
15788 let workspace = self.workspace();
15789
15790 cx.spawn_in(window, async move |editor, acx| {
15791 let mut locations: Vec<Location> = future::join_all(definitions)
15792 .await
15793 .into_iter()
15794 .filter_map(|location| location.transpose())
15795 .collect::<Result<_>>()
15796 .context("location tasks")?;
15797
15798 if locations.len() > 1 {
15799 let Some(workspace) = workspace else {
15800 return Ok(Navigated::No);
15801 };
15802
15803 let tab_kind = match kind {
15804 Some(GotoDefinitionKind::Implementation) => "Implementations",
15805 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15806 Some(GotoDefinitionKind::Declaration) => "Declarations",
15807 Some(GotoDefinitionKind::Type) => "Types",
15808 };
15809 let title = editor
15810 .update_in(acx, |_, _, cx| {
15811 let target = locations
15812 .iter()
15813 .map(|location| {
15814 location
15815 .buffer
15816 .read(cx)
15817 .text_for_range(location.range.clone())
15818 .collect::<String>()
15819 })
15820 .filter(|text| !text.contains('\n'))
15821 .unique()
15822 .take(3)
15823 .join(", ");
15824 if target.is_empty() {
15825 tab_kind.to_owned()
15826 } else {
15827 format!("{tab_kind} for {target}")
15828 }
15829 })
15830 .context("buffer title")?;
15831
15832 let opened = workspace
15833 .update_in(acx, |workspace, window, cx| {
15834 Self::open_locations_in_multibuffer(
15835 workspace,
15836 locations,
15837 title,
15838 split,
15839 MultibufferSelectionMode::First,
15840 window,
15841 cx,
15842 )
15843 })
15844 .is_ok();
15845
15846 anyhow::Ok(Navigated::from_bool(opened))
15847 } else if locations.is_empty() {
15848 // If there is one definition, just open it directly
15849 match first_url_or_file {
15850 Some(Either::Left(url)) => {
15851 acx.update(|_, cx| cx.open_url(&url))?;
15852 Ok(Navigated::Yes)
15853 }
15854 Some(Either::Right(path)) => {
15855 let Some(workspace) = workspace else {
15856 return Ok(Navigated::No);
15857 };
15858
15859 workspace
15860 .update_in(acx, |workspace, window, cx| {
15861 workspace.open_resolved_path(path, window, cx)
15862 })?
15863 .await?;
15864 Ok(Navigated::Yes)
15865 }
15866 None => Ok(Navigated::No),
15867 }
15868 } else {
15869 let Some(workspace) = workspace else {
15870 return Ok(Navigated::No);
15871 };
15872
15873 let target = locations.pop().unwrap();
15874 editor.update_in(acx, |editor, window, cx| {
15875 let pane = workspace.read(cx).active_pane().clone();
15876
15877 let range = target.range.to_point(target.buffer.read(cx));
15878 let range = editor.range_for_match(&range);
15879 let range = collapse_multiline_range(range);
15880
15881 if !split
15882 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15883 {
15884 editor.go_to_singleton_buffer_range(range, window, cx);
15885 } else {
15886 window.defer(cx, move |window, cx| {
15887 let target_editor: Entity<Self> =
15888 workspace.update(cx, |workspace, cx| {
15889 let pane = if split {
15890 workspace.adjacent_pane(window, cx)
15891 } else {
15892 workspace.active_pane().clone()
15893 };
15894
15895 workspace.open_project_item(
15896 pane,
15897 target.buffer.clone(),
15898 true,
15899 true,
15900 window,
15901 cx,
15902 )
15903 });
15904 target_editor.update(cx, |target_editor, cx| {
15905 // When selecting a definition in a different buffer, disable the nav history
15906 // to avoid creating a history entry at the previous cursor location.
15907 pane.update(cx, |pane, _| pane.disable_history());
15908 target_editor.go_to_singleton_buffer_range(range, window, cx);
15909 pane.update(cx, |pane, _| pane.enable_history());
15910 });
15911 });
15912 }
15913 Navigated::Yes
15914 })
15915 }
15916 })
15917 }
15918
15919 fn compute_target_location(
15920 &self,
15921 lsp_location: lsp::Location,
15922 server_id: LanguageServerId,
15923 window: &mut Window,
15924 cx: &mut Context<Self>,
15925 ) -> Task<anyhow::Result<Option<Location>>> {
15926 let Some(project) = self.project.clone() else {
15927 return Task::ready(Ok(None));
15928 };
15929
15930 cx.spawn_in(window, async move |editor, cx| {
15931 let location_task = editor.update(cx, |_, cx| {
15932 project.update(cx, |project, cx| {
15933 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
15934 })
15935 })?;
15936 let location = Some({
15937 let target_buffer_handle = location_task.await.context("open local buffer")?;
15938 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15939 let target_start = target_buffer
15940 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15941 let target_end = target_buffer
15942 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15943 target_buffer.anchor_after(target_start)
15944 ..target_buffer.anchor_before(target_end)
15945 })?;
15946 Location {
15947 buffer: target_buffer_handle,
15948 range,
15949 }
15950 });
15951 Ok(location)
15952 })
15953 }
15954
15955 pub fn find_all_references(
15956 &mut self,
15957 _: &FindAllReferences,
15958 window: &mut Window,
15959 cx: &mut Context<Self>,
15960 ) -> Option<Task<Result<Navigated>>> {
15961 let selection = self.selections.newest::<usize>(cx);
15962 let multi_buffer = self.buffer.read(cx);
15963 let head = selection.head();
15964
15965 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15966 let head_anchor = multi_buffer_snapshot.anchor_at(
15967 head,
15968 if head < selection.tail() {
15969 Bias::Right
15970 } else {
15971 Bias::Left
15972 },
15973 );
15974
15975 match self
15976 .find_all_references_task_sources
15977 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15978 {
15979 Ok(_) => {
15980 log::info!(
15981 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15982 );
15983 return None;
15984 }
15985 Err(i) => {
15986 self.find_all_references_task_sources.insert(i, head_anchor);
15987 }
15988 }
15989
15990 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15991 let workspace = self.workspace()?;
15992 let project = workspace.read(cx).project().clone();
15993 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15994 Some(cx.spawn_in(window, async move |editor, cx| {
15995 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15996 if let Ok(i) = editor
15997 .find_all_references_task_sources
15998 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15999 {
16000 editor.find_all_references_task_sources.remove(i);
16001 }
16002 });
16003
16004 let locations = references.await?;
16005 if locations.is_empty() {
16006 return anyhow::Ok(Navigated::No);
16007 }
16008
16009 workspace.update_in(cx, |workspace, window, cx| {
16010 let target = locations
16011 .iter()
16012 .map(|location| {
16013 location
16014 .buffer
16015 .read(cx)
16016 .text_for_range(location.range.clone())
16017 .collect::<String>()
16018 })
16019 .filter(|text| !text.contains('\n'))
16020 .unique()
16021 .take(3)
16022 .join(", ");
16023 let title = if target.is_empty() {
16024 "References".to_owned()
16025 } else {
16026 format!("References to {target}")
16027 };
16028 Self::open_locations_in_multibuffer(
16029 workspace,
16030 locations,
16031 title,
16032 false,
16033 MultibufferSelectionMode::First,
16034 window,
16035 cx,
16036 );
16037 Navigated::Yes
16038 })
16039 }))
16040 }
16041
16042 /// Opens a multibuffer with the given project locations in it
16043 pub fn open_locations_in_multibuffer(
16044 workspace: &mut Workspace,
16045 mut locations: Vec<Location>,
16046 title: String,
16047 split: bool,
16048 multibuffer_selection_mode: MultibufferSelectionMode,
16049 window: &mut Window,
16050 cx: &mut Context<Workspace>,
16051 ) {
16052 if locations.is_empty() {
16053 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16054 return;
16055 }
16056
16057 // If there are multiple definitions, open them in a multibuffer
16058 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16059 let mut locations = locations.into_iter().peekable();
16060 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16061 let capability = workspace.project().read(cx).capability();
16062
16063 let excerpt_buffer = cx.new(|cx| {
16064 let mut multibuffer = MultiBuffer::new(capability);
16065 while let Some(location) = locations.next() {
16066 let buffer = location.buffer.read(cx);
16067 let mut ranges_for_buffer = Vec::new();
16068 let range = location.range.to_point(buffer);
16069 ranges_for_buffer.push(range.clone());
16070
16071 while let Some(next_location) = locations.peek() {
16072 if next_location.buffer == location.buffer {
16073 ranges_for_buffer.push(next_location.range.to_point(buffer));
16074 locations.next();
16075 } else {
16076 break;
16077 }
16078 }
16079
16080 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16081 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16082 PathKey::for_buffer(&location.buffer, cx),
16083 location.buffer.clone(),
16084 ranges_for_buffer,
16085 DEFAULT_MULTIBUFFER_CONTEXT,
16086 cx,
16087 );
16088 ranges.extend(new_ranges)
16089 }
16090
16091 multibuffer.with_title(title)
16092 });
16093
16094 let editor = cx.new(|cx| {
16095 Editor::for_multibuffer(
16096 excerpt_buffer,
16097 Some(workspace.project().clone()),
16098 window,
16099 cx,
16100 )
16101 });
16102 editor.update(cx, |editor, cx| {
16103 match multibuffer_selection_mode {
16104 MultibufferSelectionMode::First => {
16105 if let Some(first_range) = ranges.first() {
16106 editor.change_selections(
16107 SelectionEffects::no_scroll(),
16108 window,
16109 cx,
16110 |selections| {
16111 selections.clear_disjoint();
16112 selections
16113 .select_anchor_ranges(std::iter::once(first_range.clone()));
16114 },
16115 );
16116 }
16117 editor.highlight_background::<Self>(
16118 &ranges,
16119 |theme| theme.colors().editor_highlighted_line_background,
16120 cx,
16121 );
16122 }
16123 MultibufferSelectionMode::All => {
16124 editor.change_selections(
16125 SelectionEffects::no_scroll(),
16126 window,
16127 cx,
16128 |selections| {
16129 selections.clear_disjoint();
16130 selections.select_anchor_ranges(ranges);
16131 },
16132 );
16133 }
16134 }
16135 editor.register_buffers_with_language_servers(cx);
16136 });
16137
16138 let item = Box::new(editor);
16139 let item_id = item.item_id();
16140
16141 if split {
16142 workspace.split_item(SplitDirection::Right, item, window, cx);
16143 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16144 let (preview_item_id, preview_item_idx) =
16145 workspace.active_pane().read_with(cx, |pane, _| {
16146 (pane.preview_item_id(), pane.preview_item_idx())
16147 });
16148
16149 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16150
16151 if let Some(preview_item_id) = preview_item_id {
16152 workspace.active_pane().update(cx, |pane, cx| {
16153 pane.remove_item(preview_item_id, false, false, window, cx);
16154 });
16155 }
16156 } else {
16157 workspace.add_item_to_active_pane(item, None, true, window, cx);
16158 }
16159 workspace.active_pane().update(cx, |pane, cx| {
16160 pane.set_preview_item_id(Some(item_id), cx);
16161 });
16162 }
16163
16164 pub fn rename(
16165 &mut self,
16166 _: &Rename,
16167 window: &mut Window,
16168 cx: &mut Context<Self>,
16169 ) -> Option<Task<Result<()>>> {
16170 use language::ToOffset as _;
16171
16172 let provider = self.semantics_provider.clone()?;
16173 let selection = self.selections.newest_anchor().clone();
16174 let (cursor_buffer, cursor_buffer_position) = self
16175 .buffer
16176 .read(cx)
16177 .text_anchor_for_position(selection.head(), cx)?;
16178 let (tail_buffer, cursor_buffer_position_end) = self
16179 .buffer
16180 .read(cx)
16181 .text_anchor_for_position(selection.tail(), cx)?;
16182 if tail_buffer != cursor_buffer {
16183 return None;
16184 }
16185
16186 let snapshot = cursor_buffer.read(cx).snapshot();
16187 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16188 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16189 let prepare_rename = provider
16190 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16191 .unwrap_or_else(|| Task::ready(Ok(None)));
16192 drop(snapshot);
16193
16194 Some(cx.spawn_in(window, async move |this, cx| {
16195 let rename_range = if let Some(range) = prepare_rename.await? {
16196 Some(range)
16197 } else {
16198 this.update(cx, |this, cx| {
16199 let buffer = this.buffer.read(cx).snapshot(cx);
16200 let mut buffer_highlights = this
16201 .document_highlights_for_position(selection.head(), &buffer)
16202 .filter(|highlight| {
16203 highlight.start.excerpt_id == selection.head().excerpt_id
16204 && highlight.end.excerpt_id == selection.head().excerpt_id
16205 });
16206 buffer_highlights
16207 .next()
16208 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16209 })?
16210 };
16211 if let Some(rename_range) = rename_range {
16212 this.update_in(cx, |this, window, cx| {
16213 let snapshot = cursor_buffer.read(cx).snapshot();
16214 let rename_buffer_range = rename_range.to_offset(&snapshot);
16215 let cursor_offset_in_rename_range =
16216 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16217 let cursor_offset_in_rename_range_end =
16218 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16219
16220 this.take_rename(false, window, cx);
16221 let buffer = this.buffer.read(cx).read(cx);
16222 let cursor_offset = selection.head().to_offset(&buffer);
16223 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16224 let rename_end = rename_start + rename_buffer_range.len();
16225 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16226 let mut old_highlight_id = None;
16227 let old_name: Arc<str> = buffer
16228 .chunks(rename_start..rename_end, true)
16229 .map(|chunk| {
16230 if old_highlight_id.is_none() {
16231 old_highlight_id = chunk.syntax_highlight_id;
16232 }
16233 chunk.text
16234 })
16235 .collect::<String>()
16236 .into();
16237
16238 drop(buffer);
16239
16240 // Position the selection in the rename editor so that it matches the current selection.
16241 this.show_local_selections = false;
16242 let rename_editor = cx.new(|cx| {
16243 let mut editor = Editor::single_line(window, cx);
16244 editor.buffer.update(cx, |buffer, cx| {
16245 buffer.edit([(0..0, old_name.clone())], None, cx)
16246 });
16247 let rename_selection_range = match cursor_offset_in_rename_range
16248 .cmp(&cursor_offset_in_rename_range_end)
16249 {
16250 Ordering::Equal => {
16251 editor.select_all(&SelectAll, window, cx);
16252 return editor;
16253 }
16254 Ordering::Less => {
16255 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16256 }
16257 Ordering::Greater => {
16258 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16259 }
16260 };
16261 if rename_selection_range.end > old_name.len() {
16262 editor.select_all(&SelectAll, window, cx);
16263 } else {
16264 editor.change_selections(Default::default(), window, cx, |s| {
16265 s.select_ranges([rename_selection_range]);
16266 });
16267 }
16268 editor
16269 });
16270 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16271 if e == &EditorEvent::Focused {
16272 cx.emit(EditorEvent::FocusedIn)
16273 }
16274 })
16275 .detach();
16276
16277 let write_highlights =
16278 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16279 let read_highlights =
16280 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16281 let ranges = write_highlights
16282 .iter()
16283 .flat_map(|(_, ranges)| ranges.iter())
16284 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16285 .cloned()
16286 .collect();
16287
16288 this.highlight_text::<Rename>(
16289 ranges,
16290 HighlightStyle {
16291 fade_out: Some(0.6),
16292 ..Default::default()
16293 },
16294 cx,
16295 );
16296 let rename_focus_handle = rename_editor.focus_handle(cx);
16297 window.focus(&rename_focus_handle);
16298 let block_id = this.insert_blocks(
16299 [BlockProperties {
16300 style: BlockStyle::Flex,
16301 placement: BlockPlacement::Below(range.start),
16302 height: Some(1),
16303 render: Arc::new({
16304 let rename_editor = rename_editor.clone();
16305 move |cx: &mut BlockContext| {
16306 let mut text_style = cx.editor_style.text.clone();
16307 if let Some(highlight_style) = old_highlight_id
16308 .and_then(|h| h.style(&cx.editor_style.syntax))
16309 {
16310 text_style = text_style.highlight(highlight_style);
16311 }
16312 div()
16313 .block_mouse_except_scroll()
16314 .pl(cx.anchor_x)
16315 .child(EditorElement::new(
16316 &rename_editor,
16317 EditorStyle {
16318 background: cx.theme().system().transparent,
16319 local_player: cx.editor_style.local_player,
16320 text: text_style,
16321 scrollbar_width: cx.editor_style.scrollbar_width,
16322 syntax: cx.editor_style.syntax.clone(),
16323 status: cx.editor_style.status.clone(),
16324 inlay_hints_style: HighlightStyle {
16325 font_weight: Some(FontWeight::BOLD),
16326 ..make_inlay_hints_style(cx.app)
16327 },
16328 edit_prediction_styles: make_suggestion_styles(
16329 cx.app,
16330 ),
16331 ..EditorStyle::default()
16332 },
16333 ))
16334 .into_any_element()
16335 }
16336 }),
16337 priority: 0,
16338 }],
16339 Some(Autoscroll::fit()),
16340 cx,
16341 )[0];
16342 this.pending_rename = Some(RenameState {
16343 range,
16344 old_name,
16345 editor: rename_editor,
16346 block_id,
16347 });
16348 })?;
16349 }
16350
16351 Ok(())
16352 }))
16353 }
16354
16355 pub fn confirm_rename(
16356 &mut self,
16357 _: &ConfirmRename,
16358 window: &mut Window,
16359 cx: &mut Context<Self>,
16360 ) -> Option<Task<Result<()>>> {
16361 let rename = self.take_rename(false, window, cx)?;
16362 let workspace = self.workspace()?.downgrade();
16363 let (buffer, start) = self
16364 .buffer
16365 .read(cx)
16366 .text_anchor_for_position(rename.range.start, cx)?;
16367 let (end_buffer, _) = self
16368 .buffer
16369 .read(cx)
16370 .text_anchor_for_position(rename.range.end, cx)?;
16371 if buffer != end_buffer {
16372 return None;
16373 }
16374
16375 let old_name = rename.old_name;
16376 let new_name = rename.editor.read(cx).text(cx);
16377
16378 let rename = self.semantics_provider.as_ref()?.perform_rename(
16379 &buffer,
16380 start,
16381 new_name.clone(),
16382 cx,
16383 )?;
16384
16385 Some(cx.spawn_in(window, async move |editor, cx| {
16386 let project_transaction = rename.await?;
16387 Self::open_project_transaction(
16388 &editor,
16389 workspace,
16390 project_transaction,
16391 format!("Rename: {} → {}", old_name, new_name),
16392 cx,
16393 )
16394 .await?;
16395
16396 editor.update(cx, |editor, cx| {
16397 editor.refresh_document_highlights(cx);
16398 })?;
16399 Ok(())
16400 }))
16401 }
16402
16403 fn take_rename(
16404 &mut self,
16405 moving_cursor: bool,
16406 window: &mut Window,
16407 cx: &mut Context<Self>,
16408 ) -> Option<RenameState> {
16409 let rename = self.pending_rename.take()?;
16410 if rename.editor.focus_handle(cx).is_focused(window) {
16411 window.focus(&self.focus_handle);
16412 }
16413
16414 self.remove_blocks(
16415 [rename.block_id].into_iter().collect(),
16416 Some(Autoscroll::fit()),
16417 cx,
16418 );
16419 self.clear_highlights::<Rename>(cx);
16420 self.show_local_selections = true;
16421
16422 if moving_cursor {
16423 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16424 editor.selections.newest::<usize>(cx).head()
16425 });
16426
16427 // Update the selection to match the position of the selection inside
16428 // the rename editor.
16429 let snapshot = self.buffer.read(cx).read(cx);
16430 let rename_range = rename.range.to_offset(&snapshot);
16431 let cursor_in_editor = snapshot
16432 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16433 .min(rename_range.end);
16434 drop(snapshot);
16435
16436 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16437 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16438 });
16439 } else {
16440 self.refresh_document_highlights(cx);
16441 }
16442
16443 Some(rename)
16444 }
16445
16446 pub fn pending_rename(&self) -> Option<&RenameState> {
16447 self.pending_rename.as_ref()
16448 }
16449
16450 fn format(
16451 &mut self,
16452 _: &Format,
16453 window: &mut Window,
16454 cx: &mut Context<Self>,
16455 ) -> Option<Task<Result<()>>> {
16456 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16457
16458 let project = match &self.project {
16459 Some(project) => project.clone(),
16460 None => return None,
16461 };
16462
16463 Some(self.perform_format(
16464 project,
16465 FormatTrigger::Manual,
16466 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16467 window,
16468 cx,
16469 ))
16470 }
16471
16472 fn format_selections(
16473 &mut self,
16474 _: &FormatSelections,
16475 window: &mut Window,
16476 cx: &mut Context<Self>,
16477 ) -> Option<Task<Result<()>>> {
16478 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16479
16480 let project = match &self.project {
16481 Some(project) => project.clone(),
16482 None => return None,
16483 };
16484
16485 let ranges = self
16486 .selections
16487 .all_adjusted(cx)
16488 .into_iter()
16489 .map(|selection| selection.range())
16490 .collect_vec();
16491
16492 Some(self.perform_format(
16493 project,
16494 FormatTrigger::Manual,
16495 FormatTarget::Ranges(ranges),
16496 window,
16497 cx,
16498 ))
16499 }
16500
16501 fn perform_format(
16502 &mut self,
16503 project: Entity<Project>,
16504 trigger: FormatTrigger,
16505 target: FormatTarget,
16506 window: &mut Window,
16507 cx: &mut Context<Self>,
16508 ) -> Task<Result<()>> {
16509 let buffer = self.buffer.clone();
16510 let (buffers, target) = match target {
16511 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16512 FormatTarget::Ranges(selection_ranges) => {
16513 let multi_buffer = buffer.read(cx);
16514 let snapshot = multi_buffer.read(cx);
16515 let mut buffers = HashSet::default();
16516 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16517 BTreeMap::new();
16518 for selection_range in selection_ranges {
16519 for (buffer, buffer_range, _) in
16520 snapshot.range_to_buffer_ranges(selection_range)
16521 {
16522 let buffer_id = buffer.remote_id();
16523 let start = buffer.anchor_before(buffer_range.start);
16524 let end = buffer.anchor_after(buffer_range.end);
16525 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16526 buffer_id_to_ranges
16527 .entry(buffer_id)
16528 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16529 .or_insert_with(|| vec![start..end]);
16530 }
16531 }
16532 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16533 }
16534 };
16535
16536 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16537 let selections_prev = transaction_id_prev
16538 .and_then(|transaction_id_prev| {
16539 // default to selections as they were after the last edit, if we have them,
16540 // instead of how they are now.
16541 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16542 // will take you back to where you made the last edit, instead of staying where you scrolled
16543 self.selection_history
16544 .transaction(transaction_id_prev)
16545 .map(|t| t.0.clone())
16546 })
16547 .unwrap_or_else(|| self.selections.disjoint_anchors());
16548
16549 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16550 let format = project.update(cx, |project, cx| {
16551 project.format(buffers, target, true, trigger, cx)
16552 });
16553
16554 cx.spawn_in(window, async move |editor, cx| {
16555 let transaction = futures::select_biased! {
16556 transaction = format.log_err().fuse() => transaction,
16557 () = timeout => {
16558 log::warn!("timed out waiting for formatting");
16559 None
16560 }
16561 };
16562
16563 buffer
16564 .update(cx, |buffer, cx| {
16565 if let Some(transaction) = transaction
16566 && !buffer.is_singleton()
16567 {
16568 buffer.push_transaction(&transaction.0, cx);
16569 }
16570 cx.notify();
16571 })
16572 .ok();
16573
16574 if let Some(transaction_id_now) =
16575 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16576 {
16577 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16578 if has_new_transaction {
16579 _ = editor.update(cx, |editor, _| {
16580 editor
16581 .selection_history
16582 .insert_transaction(transaction_id_now, selections_prev);
16583 });
16584 }
16585 }
16586
16587 Ok(())
16588 })
16589 }
16590
16591 fn organize_imports(
16592 &mut self,
16593 _: &OrganizeImports,
16594 window: &mut Window,
16595 cx: &mut Context<Self>,
16596 ) -> Option<Task<Result<()>>> {
16597 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16598 let project = match &self.project {
16599 Some(project) => project.clone(),
16600 None => return None,
16601 };
16602 Some(self.perform_code_action_kind(
16603 project,
16604 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16605 window,
16606 cx,
16607 ))
16608 }
16609
16610 fn perform_code_action_kind(
16611 &mut self,
16612 project: Entity<Project>,
16613 kind: CodeActionKind,
16614 window: &mut Window,
16615 cx: &mut Context<Self>,
16616 ) -> Task<Result<()>> {
16617 let buffer = self.buffer.clone();
16618 let buffers = buffer.read(cx).all_buffers();
16619 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16620 let apply_action = project.update(cx, |project, cx| {
16621 project.apply_code_action_kind(buffers, kind, true, cx)
16622 });
16623 cx.spawn_in(window, async move |_, cx| {
16624 let transaction = futures::select_biased! {
16625 () = timeout => {
16626 log::warn!("timed out waiting for executing code action");
16627 None
16628 }
16629 transaction = apply_action.log_err().fuse() => transaction,
16630 };
16631 buffer
16632 .update(cx, |buffer, cx| {
16633 // check if we need this
16634 if let Some(transaction) = transaction
16635 && !buffer.is_singleton()
16636 {
16637 buffer.push_transaction(&transaction.0, cx);
16638 }
16639 cx.notify();
16640 })
16641 .ok();
16642 Ok(())
16643 })
16644 }
16645
16646 pub fn restart_language_server(
16647 &mut self,
16648 _: &RestartLanguageServer,
16649 _: &mut Window,
16650 cx: &mut Context<Self>,
16651 ) {
16652 if let Some(project) = self.project.clone() {
16653 self.buffer.update(cx, |multi_buffer, cx| {
16654 project.update(cx, |project, cx| {
16655 project.restart_language_servers_for_buffers(
16656 multi_buffer.all_buffers().into_iter().collect(),
16657 HashSet::default(),
16658 cx,
16659 );
16660 });
16661 })
16662 }
16663 }
16664
16665 pub fn stop_language_server(
16666 &mut self,
16667 _: &StopLanguageServer,
16668 _: &mut Window,
16669 cx: &mut Context<Self>,
16670 ) {
16671 if let Some(project) = self.project.clone() {
16672 self.buffer.update(cx, |multi_buffer, cx| {
16673 project.update(cx, |project, cx| {
16674 project.stop_language_servers_for_buffers(
16675 multi_buffer.all_buffers().into_iter().collect(),
16676 HashSet::default(),
16677 cx,
16678 );
16679 cx.emit(project::Event::RefreshInlayHints);
16680 });
16681 });
16682 }
16683 }
16684
16685 fn cancel_language_server_work(
16686 workspace: &mut Workspace,
16687 _: &actions::CancelLanguageServerWork,
16688 _: &mut Window,
16689 cx: &mut Context<Workspace>,
16690 ) {
16691 let project = workspace.project();
16692 let buffers = workspace
16693 .active_item(cx)
16694 .and_then(|item| item.act_as::<Editor>(cx))
16695 .map_or(HashSet::default(), |editor| {
16696 editor.read(cx).buffer.read(cx).all_buffers()
16697 });
16698 project.update(cx, |project, cx| {
16699 project.cancel_language_server_work_for_buffers(buffers, cx);
16700 });
16701 }
16702
16703 fn show_character_palette(
16704 &mut self,
16705 _: &ShowCharacterPalette,
16706 window: &mut Window,
16707 _: &mut Context<Self>,
16708 ) {
16709 window.show_character_palette();
16710 }
16711
16712 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16713 if !self.diagnostics_enabled() {
16714 return;
16715 }
16716
16717 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16718 let buffer = self.buffer.read(cx).snapshot(cx);
16719 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16720 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16721 let is_valid = buffer
16722 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16723 .any(|entry| {
16724 entry.diagnostic.is_primary
16725 && !entry.range.is_empty()
16726 && entry.range.start == primary_range_start
16727 && entry.diagnostic.message == active_diagnostics.active_message
16728 });
16729
16730 if !is_valid {
16731 self.dismiss_diagnostics(cx);
16732 }
16733 }
16734 }
16735
16736 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16737 match &self.active_diagnostics {
16738 ActiveDiagnostic::Group(group) => Some(group),
16739 _ => None,
16740 }
16741 }
16742
16743 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16744 if !self.diagnostics_enabled() {
16745 return;
16746 }
16747 self.dismiss_diagnostics(cx);
16748 self.active_diagnostics = ActiveDiagnostic::All;
16749 }
16750
16751 fn activate_diagnostics(
16752 &mut self,
16753 buffer_id: BufferId,
16754 diagnostic: DiagnosticEntry<usize>,
16755 window: &mut Window,
16756 cx: &mut Context<Self>,
16757 ) {
16758 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16759 return;
16760 }
16761 self.dismiss_diagnostics(cx);
16762 let snapshot = self.snapshot(window, cx);
16763 let buffer = self.buffer.read(cx).snapshot(cx);
16764 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16765 return;
16766 };
16767
16768 let diagnostic_group = buffer
16769 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16770 .collect::<Vec<_>>();
16771
16772 let blocks =
16773 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16774
16775 let blocks = self.display_map.update(cx, |display_map, cx| {
16776 display_map.insert_blocks(blocks, cx).into_iter().collect()
16777 });
16778 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16779 active_range: buffer.anchor_before(diagnostic.range.start)
16780 ..buffer.anchor_after(diagnostic.range.end),
16781 active_message: diagnostic.diagnostic.message.clone(),
16782 group_id: diagnostic.diagnostic.group_id,
16783 blocks,
16784 });
16785 cx.notify();
16786 }
16787
16788 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16789 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16790 return;
16791 };
16792
16793 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16794 if let ActiveDiagnostic::Group(group) = prev {
16795 self.display_map.update(cx, |display_map, cx| {
16796 display_map.remove_blocks(group.blocks, cx);
16797 });
16798 cx.notify();
16799 }
16800 }
16801
16802 /// Disable inline diagnostics rendering for this editor.
16803 pub fn disable_inline_diagnostics(&mut self) {
16804 self.inline_diagnostics_enabled = false;
16805 self.inline_diagnostics_update = Task::ready(());
16806 self.inline_diagnostics.clear();
16807 }
16808
16809 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16810 self.diagnostics_enabled = false;
16811 self.dismiss_diagnostics(cx);
16812 self.inline_diagnostics_update = Task::ready(());
16813 self.inline_diagnostics.clear();
16814 }
16815
16816 pub fn diagnostics_enabled(&self) -> bool {
16817 self.diagnostics_enabled && self.mode.is_full()
16818 }
16819
16820 pub fn inline_diagnostics_enabled(&self) -> bool {
16821 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16822 }
16823
16824 pub fn show_inline_diagnostics(&self) -> bool {
16825 self.show_inline_diagnostics
16826 }
16827
16828 pub fn toggle_inline_diagnostics(
16829 &mut self,
16830 _: &ToggleInlineDiagnostics,
16831 window: &mut Window,
16832 cx: &mut Context<Editor>,
16833 ) {
16834 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16835 self.refresh_inline_diagnostics(false, window, cx);
16836 }
16837
16838 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16839 self.diagnostics_max_severity = severity;
16840 self.display_map.update(cx, |display_map, _| {
16841 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16842 });
16843 }
16844
16845 pub fn toggle_diagnostics(
16846 &mut self,
16847 _: &ToggleDiagnostics,
16848 window: &mut Window,
16849 cx: &mut Context<Editor>,
16850 ) {
16851 if !self.diagnostics_enabled() {
16852 return;
16853 }
16854
16855 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16856 EditorSettings::get_global(cx)
16857 .diagnostics_max_severity
16858 .filter(|severity| severity != &DiagnosticSeverity::Off)
16859 .unwrap_or(DiagnosticSeverity::Hint)
16860 } else {
16861 DiagnosticSeverity::Off
16862 };
16863 self.set_max_diagnostics_severity(new_severity, cx);
16864 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16865 self.active_diagnostics = ActiveDiagnostic::None;
16866 self.inline_diagnostics_update = Task::ready(());
16867 self.inline_diagnostics.clear();
16868 } else {
16869 self.refresh_inline_diagnostics(false, window, cx);
16870 }
16871
16872 cx.notify();
16873 }
16874
16875 pub fn toggle_minimap(
16876 &mut self,
16877 _: &ToggleMinimap,
16878 window: &mut Window,
16879 cx: &mut Context<Editor>,
16880 ) {
16881 if self.supports_minimap(cx) {
16882 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16883 }
16884 }
16885
16886 fn refresh_inline_diagnostics(
16887 &mut self,
16888 debounce: bool,
16889 window: &mut Window,
16890 cx: &mut Context<Self>,
16891 ) {
16892 let max_severity = ProjectSettings::get_global(cx)
16893 .diagnostics
16894 .inline
16895 .max_severity
16896 .unwrap_or(self.diagnostics_max_severity);
16897
16898 if !self.inline_diagnostics_enabled()
16899 || !self.show_inline_diagnostics
16900 || max_severity == DiagnosticSeverity::Off
16901 {
16902 self.inline_diagnostics_update = Task::ready(());
16903 self.inline_diagnostics.clear();
16904 return;
16905 }
16906
16907 let debounce_ms = ProjectSettings::get_global(cx)
16908 .diagnostics
16909 .inline
16910 .update_debounce_ms;
16911 let debounce = if debounce && debounce_ms > 0 {
16912 Some(Duration::from_millis(debounce_ms))
16913 } else {
16914 None
16915 };
16916 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16917 if let Some(debounce) = debounce {
16918 cx.background_executor().timer(debounce).await;
16919 }
16920 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16921 editor
16922 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16923 .ok()
16924 }) else {
16925 return;
16926 };
16927
16928 let new_inline_diagnostics = cx
16929 .background_spawn(async move {
16930 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16931 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16932 let message = diagnostic_entry
16933 .diagnostic
16934 .message
16935 .split_once('\n')
16936 .map(|(line, _)| line)
16937 .map(SharedString::new)
16938 .unwrap_or_else(|| {
16939 SharedString::from(diagnostic_entry.diagnostic.message)
16940 });
16941 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16942 let (Ok(i) | Err(i)) = inline_diagnostics
16943 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16944 inline_diagnostics.insert(
16945 i,
16946 (
16947 start_anchor,
16948 InlineDiagnostic {
16949 message,
16950 group_id: diagnostic_entry.diagnostic.group_id,
16951 start: diagnostic_entry.range.start.to_point(&snapshot),
16952 is_primary: diagnostic_entry.diagnostic.is_primary,
16953 severity: diagnostic_entry.diagnostic.severity,
16954 },
16955 ),
16956 );
16957 }
16958 inline_diagnostics
16959 })
16960 .await;
16961
16962 editor
16963 .update(cx, |editor, cx| {
16964 editor.inline_diagnostics = new_inline_diagnostics;
16965 cx.notify();
16966 })
16967 .ok();
16968 });
16969 }
16970
16971 fn pull_diagnostics(
16972 &mut self,
16973 buffer_id: Option<BufferId>,
16974 window: &Window,
16975 cx: &mut Context<Self>,
16976 ) -> Option<()> {
16977 if !self.mode().is_full() {
16978 return None;
16979 }
16980 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16981 .diagnostics
16982 .lsp_pull_diagnostics;
16983 if !pull_diagnostics_settings.enabled {
16984 return None;
16985 }
16986 let project = self.project()?.downgrade();
16987 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16988 let mut buffers = self.buffer.read(cx).all_buffers();
16989 if let Some(buffer_id) = buffer_id {
16990 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16991 }
16992
16993 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16994 cx.background_executor().timer(debounce).await;
16995
16996 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16997 buffers
16998 .into_iter()
16999 .filter_map(|buffer| {
17000 project
17001 .update(cx, |project, cx| {
17002 project.lsp_store().update(cx, |lsp_store, cx| {
17003 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17004 })
17005 })
17006 .ok()
17007 })
17008 .collect::<FuturesUnordered<_>>()
17009 }) else {
17010 return;
17011 };
17012
17013 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17014 match pull_task {
17015 Ok(()) => {
17016 if editor
17017 .update_in(cx, |editor, window, cx| {
17018 editor.update_diagnostics_state(window, cx);
17019 })
17020 .is_err()
17021 {
17022 return;
17023 }
17024 }
17025 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17026 }
17027 }
17028 });
17029
17030 Some(())
17031 }
17032
17033 pub fn set_selections_from_remote(
17034 &mut self,
17035 selections: Vec<Selection<Anchor>>,
17036 pending_selection: Option<Selection<Anchor>>,
17037 window: &mut Window,
17038 cx: &mut Context<Self>,
17039 ) {
17040 let old_cursor_position = self.selections.newest_anchor().head();
17041 self.selections.change_with(cx, |s| {
17042 s.select_anchors(selections);
17043 if let Some(pending_selection) = pending_selection {
17044 s.set_pending(pending_selection, SelectMode::Character);
17045 } else {
17046 s.clear_pending();
17047 }
17048 });
17049 self.selections_did_change(
17050 false,
17051 &old_cursor_position,
17052 SelectionEffects::default(),
17053 window,
17054 cx,
17055 );
17056 }
17057
17058 pub fn transact(
17059 &mut self,
17060 window: &mut Window,
17061 cx: &mut Context<Self>,
17062 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17063 ) -> Option<TransactionId> {
17064 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17065 this.start_transaction_at(Instant::now(), window, cx);
17066 update(this, window, cx);
17067 this.end_transaction_at(Instant::now(), cx)
17068 })
17069 }
17070
17071 pub fn start_transaction_at(
17072 &mut self,
17073 now: Instant,
17074 window: &mut Window,
17075 cx: &mut Context<Self>,
17076 ) -> Option<TransactionId> {
17077 self.end_selection(window, cx);
17078 if let Some(tx_id) = self
17079 .buffer
17080 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17081 {
17082 self.selection_history
17083 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17084 cx.emit(EditorEvent::TransactionBegun {
17085 transaction_id: tx_id,
17086 });
17087 Some(tx_id)
17088 } else {
17089 None
17090 }
17091 }
17092
17093 pub fn end_transaction_at(
17094 &mut self,
17095 now: Instant,
17096 cx: &mut Context<Self>,
17097 ) -> Option<TransactionId> {
17098 if let Some(transaction_id) = self
17099 .buffer
17100 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17101 {
17102 if let Some((_, end_selections)) =
17103 self.selection_history.transaction_mut(transaction_id)
17104 {
17105 *end_selections = Some(self.selections.disjoint_anchors());
17106 } else {
17107 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17108 }
17109
17110 cx.emit(EditorEvent::Edited { transaction_id });
17111 Some(transaction_id)
17112 } else {
17113 None
17114 }
17115 }
17116
17117 pub fn modify_transaction_selection_history(
17118 &mut self,
17119 transaction_id: TransactionId,
17120 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17121 ) -> bool {
17122 self.selection_history
17123 .transaction_mut(transaction_id)
17124 .map(modify)
17125 .is_some()
17126 }
17127
17128 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17129 if self.selection_mark_mode {
17130 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17131 s.move_with(|_, sel| {
17132 sel.collapse_to(sel.head(), SelectionGoal::None);
17133 });
17134 })
17135 }
17136 self.selection_mark_mode = true;
17137 cx.notify();
17138 }
17139
17140 pub fn swap_selection_ends(
17141 &mut self,
17142 _: &actions::SwapSelectionEnds,
17143 window: &mut Window,
17144 cx: &mut Context<Self>,
17145 ) {
17146 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17147 s.move_with(|_, sel| {
17148 if sel.start != sel.end {
17149 sel.reversed = !sel.reversed
17150 }
17151 });
17152 });
17153 self.request_autoscroll(Autoscroll::newest(), cx);
17154 cx.notify();
17155 }
17156
17157 pub fn toggle_focus(
17158 workspace: &mut Workspace,
17159 _: &actions::ToggleFocus,
17160 window: &mut Window,
17161 cx: &mut Context<Workspace>,
17162 ) {
17163 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17164 return;
17165 };
17166 workspace.activate_item(&item, true, true, window, cx);
17167 }
17168
17169 pub fn toggle_fold(
17170 &mut self,
17171 _: &actions::ToggleFold,
17172 window: &mut Window,
17173 cx: &mut Context<Self>,
17174 ) {
17175 if self.is_singleton(cx) {
17176 let selection = self.selections.newest::<Point>(cx);
17177
17178 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17179 let range = if selection.is_empty() {
17180 let point = selection.head().to_display_point(&display_map);
17181 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17182 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17183 .to_point(&display_map);
17184 start..end
17185 } else {
17186 selection.range()
17187 };
17188 if display_map.folds_in_range(range).next().is_some() {
17189 self.unfold_lines(&Default::default(), window, cx)
17190 } else {
17191 self.fold(&Default::default(), window, cx)
17192 }
17193 } else {
17194 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17195 let buffer_ids: HashSet<_> = self
17196 .selections
17197 .disjoint_anchor_ranges()
17198 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17199 .collect();
17200
17201 let should_unfold = buffer_ids
17202 .iter()
17203 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17204
17205 for buffer_id in buffer_ids {
17206 if should_unfold {
17207 self.unfold_buffer(buffer_id, cx);
17208 } else {
17209 self.fold_buffer(buffer_id, cx);
17210 }
17211 }
17212 }
17213 }
17214
17215 pub fn toggle_fold_recursive(
17216 &mut self,
17217 _: &actions::ToggleFoldRecursive,
17218 window: &mut Window,
17219 cx: &mut Context<Self>,
17220 ) {
17221 let selection = self.selections.newest::<Point>(cx);
17222
17223 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17224 let range = if selection.is_empty() {
17225 let point = selection.head().to_display_point(&display_map);
17226 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17227 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17228 .to_point(&display_map);
17229 start..end
17230 } else {
17231 selection.range()
17232 };
17233 if display_map.folds_in_range(range).next().is_some() {
17234 self.unfold_recursive(&Default::default(), window, cx)
17235 } else {
17236 self.fold_recursive(&Default::default(), window, cx)
17237 }
17238 }
17239
17240 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17241 if self.is_singleton(cx) {
17242 let mut to_fold = Vec::new();
17243 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17244 let selections = self.selections.all_adjusted(cx);
17245
17246 for selection in selections {
17247 let range = selection.range().sorted();
17248 let buffer_start_row = range.start.row;
17249
17250 if range.start.row != range.end.row {
17251 let mut found = false;
17252 let mut row = range.start.row;
17253 while row <= range.end.row {
17254 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17255 {
17256 found = true;
17257 row = crease.range().end.row + 1;
17258 to_fold.push(crease);
17259 } else {
17260 row += 1
17261 }
17262 }
17263 if found {
17264 continue;
17265 }
17266 }
17267
17268 for row in (0..=range.start.row).rev() {
17269 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17270 && crease.range().end.row >= buffer_start_row
17271 {
17272 to_fold.push(crease);
17273 if row <= range.start.row {
17274 break;
17275 }
17276 }
17277 }
17278 }
17279
17280 self.fold_creases(to_fold, true, window, cx);
17281 } else {
17282 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17283 let buffer_ids = self
17284 .selections
17285 .disjoint_anchor_ranges()
17286 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17287 .collect::<HashSet<_>>();
17288 for buffer_id in buffer_ids {
17289 self.fold_buffer(buffer_id, cx);
17290 }
17291 }
17292 }
17293
17294 pub fn toggle_fold_all(
17295 &mut self,
17296 _: &actions::ToggleFoldAll,
17297 window: &mut Window,
17298 cx: &mut Context<Self>,
17299 ) {
17300 if self.buffer.read(cx).is_singleton() {
17301 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17302 let has_folds = display_map
17303 .folds_in_range(0..display_map.buffer_snapshot.len())
17304 .next()
17305 .is_some();
17306
17307 if has_folds {
17308 self.unfold_all(&actions::UnfoldAll, window, cx);
17309 } else {
17310 self.fold_all(&actions::FoldAll, window, cx);
17311 }
17312 } else {
17313 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17314 let should_unfold = buffer_ids
17315 .iter()
17316 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17317
17318 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17319 editor
17320 .update_in(cx, |editor, _, cx| {
17321 for buffer_id in buffer_ids {
17322 if should_unfold {
17323 editor.unfold_buffer(buffer_id, cx);
17324 } else {
17325 editor.fold_buffer(buffer_id, cx);
17326 }
17327 }
17328 })
17329 .ok();
17330 });
17331 }
17332 }
17333
17334 fn fold_at_level(
17335 &mut self,
17336 fold_at: &FoldAtLevel,
17337 window: &mut Window,
17338 cx: &mut Context<Self>,
17339 ) {
17340 if !self.buffer.read(cx).is_singleton() {
17341 return;
17342 }
17343
17344 let fold_at_level = fold_at.0;
17345 let snapshot = self.buffer.read(cx).snapshot(cx);
17346 let mut to_fold = Vec::new();
17347 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17348
17349 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17350 while start_row < end_row {
17351 match self
17352 .snapshot(window, cx)
17353 .crease_for_buffer_row(MultiBufferRow(start_row))
17354 {
17355 Some(crease) => {
17356 let nested_start_row = crease.range().start.row + 1;
17357 let nested_end_row = crease.range().end.row;
17358
17359 if current_level < fold_at_level {
17360 stack.push((nested_start_row, nested_end_row, current_level + 1));
17361 } else if current_level == fold_at_level {
17362 to_fold.push(crease);
17363 }
17364
17365 start_row = nested_end_row + 1;
17366 }
17367 None => start_row += 1,
17368 }
17369 }
17370 }
17371
17372 self.fold_creases(to_fold, true, window, cx);
17373 }
17374
17375 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17376 if self.buffer.read(cx).is_singleton() {
17377 let mut fold_ranges = Vec::new();
17378 let snapshot = self.buffer.read(cx).snapshot(cx);
17379
17380 for row in 0..snapshot.max_row().0 {
17381 if let Some(foldable_range) = self
17382 .snapshot(window, cx)
17383 .crease_for_buffer_row(MultiBufferRow(row))
17384 {
17385 fold_ranges.push(foldable_range);
17386 }
17387 }
17388
17389 self.fold_creases(fold_ranges, true, window, cx);
17390 } else {
17391 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17392 editor
17393 .update_in(cx, |editor, _, cx| {
17394 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17395 editor.fold_buffer(buffer_id, cx);
17396 }
17397 })
17398 .ok();
17399 });
17400 }
17401 }
17402
17403 pub fn fold_function_bodies(
17404 &mut self,
17405 _: &actions::FoldFunctionBodies,
17406 window: &mut Window,
17407 cx: &mut Context<Self>,
17408 ) {
17409 let snapshot = self.buffer.read(cx).snapshot(cx);
17410
17411 let ranges = snapshot
17412 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17413 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17414 .collect::<Vec<_>>();
17415
17416 let creases = ranges
17417 .into_iter()
17418 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17419 .collect();
17420
17421 self.fold_creases(creases, true, window, cx);
17422 }
17423
17424 pub fn fold_recursive(
17425 &mut self,
17426 _: &actions::FoldRecursive,
17427 window: &mut Window,
17428 cx: &mut Context<Self>,
17429 ) {
17430 let mut to_fold = Vec::new();
17431 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17432 let selections = self.selections.all_adjusted(cx);
17433
17434 for selection in selections {
17435 let range = selection.range().sorted();
17436 let buffer_start_row = range.start.row;
17437
17438 if range.start.row != range.end.row {
17439 let mut found = false;
17440 for row in range.start.row..=range.end.row {
17441 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17442 found = true;
17443 to_fold.push(crease);
17444 }
17445 }
17446 if found {
17447 continue;
17448 }
17449 }
17450
17451 for row in (0..=range.start.row).rev() {
17452 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17453 if crease.range().end.row >= buffer_start_row {
17454 to_fold.push(crease);
17455 } else {
17456 break;
17457 }
17458 }
17459 }
17460 }
17461
17462 self.fold_creases(to_fold, true, window, cx);
17463 }
17464
17465 pub fn fold_at(
17466 &mut self,
17467 buffer_row: MultiBufferRow,
17468 window: &mut Window,
17469 cx: &mut Context<Self>,
17470 ) {
17471 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17472
17473 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17474 let autoscroll = self
17475 .selections
17476 .all::<Point>(cx)
17477 .iter()
17478 .any(|selection| crease.range().overlaps(&selection.range()));
17479
17480 self.fold_creases(vec![crease], autoscroll, window, cx);
17481 }
17482 }
17483
17484 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17485 if self.is_singleton(cx) {
17486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17487 let buffer = &display_map.buffer_snapshot;
17488 let selections = self.selections.all::<Point>(cx);
17489 let ranges = selections
17490 .iter()
17491 .map(|s| {
17492 let range = s.display_range(&display_map).sorted();
17493 let mut start = range.start.to_point(&display_map);
17494 let mut end = range.end.to_point(&display_map);
17495 start.column = 0;
17496 end.column = buffer.line_len(MultiBufferRow(end.row));
17497 start..end
17498 })
17499 .collect::<Vec<_>>();
17500
17501 self.unfold_ranges(&ranges, true, true, cx);
17502 } else {
17503 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17504 let buffer_ids = self
17505 .selections
17506 .disjoint_anchor_ranges()
17507 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17508 .collect::<HashSet<_>>();
17509 for buffer_id in buffer_ids {
17510 self.unfold_buffer(buffer_id, cx);
17511 }
17512 }
17513 }
17514
17515 pub fn unfold_recursive(
17516 &mut self,
17517 _: &UnfoldRecursive,
17518 _window: &mut Window,
17519 cx: &mut Context<Self>,
17520 ) {
17521 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17522 let selections = self.selections.all::<Point>(cx);
17523 let ranges = selections
17524 .iter()
17525 .map(|s| {
17526 let mut range = s.display_range(&display_map).sorted();
17527 *range.start.column_mut() = 0;
17528 *range.end.column_mut() = display_map.line_len(range.end.row());
17529 let start = range.start.to_point(&display_map);
17530 let end = range.end.to_point(&display_map);
17531 start..end
17532 })
17533 .collect::<Vec<_>>();
17534
17535 self.unfold_ranges(&ranges, true, true, cx);
17536 }
17537
17538 pub fn unfold_at(
17539 &mut self,
17540 buffer_row: MultiBufferRow,
17541 _window: &mut Window,
17542 cx: &mut Context<Self>,
17543 ) {
17544 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17545
17546 let intersection_range = Point::new(buffer_row.0, 0)
17547 ..Point::new(
17548 buffer_row.0,
17549 display_map.buffer_snapshot.line_len(buffer_row),
17550 );
17551
17552 let autoscroll = self
17553 .selections
17554 .all::<Point>(cx)
17555 .iter()
17556 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17557
17558 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17559 }
17560
17561 pub fn unfold_all(
17562 &mut self,
17563 _: &actions::UnfoldAll,
17564 _window: &mut Window,
17565 cx: &mut Context<Self>,
17566 ) {
17567 if self.buffer.read(cx).is_singleton() {
17568 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17569 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17570 } else {
17571 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17572 editor
17573 .update(cx, |editor, cx| {
17574 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17575 editor.unfold_buffer(buffer_id, cx);
17576 }
17577 })
17578 .ok();
17579 });
17580 }
17581 }
17582
17583 pub fn fold_selected_ranges(
17584 &mut self,
17585 _: &FoldSelectedRanges,
17586 window: &mut Window,
17587 cx: &mut Context<Self>,
17588 ) {
17589 let selections = self.selections.all_adjusted(cx);
17590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17591 let ranges = selections
17592 .into_iter()
17593 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17594 .collect::<Vec<_>>();
17595 self.fold_creases(ranges, true, window, cx);
17596 }
17597
17598 pub fn fold_ranges<T: ToOffset + Clone>(
17599 &mut self,
17600 ranges: Vec<Range<T>>,
17601 auto_scroll: bool,
17602 window: &mut Window,
17603 cx: &mut Context<Self>,
17604 ) {
17605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17606 let ranges = ranges
17607 .into_iter()
17608 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17609 .collect::<Vec<_>>();
17610 self.fold_creases(ranges, auto_scroll, window, cx);
17611 }
17612
17613 pub fn fold_creases<T: ToOffset + Clone>(
17614 &mut self,
17615 creases: Vec<Crease<T>>,
17616 auto_scroll: bool,
17617 _window: &mut Window,
17618 cx: &mut Context<Self>,
17619 ) {
17620 if creases.is_empty() {
17621 return;
17622 }
17623
17624 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17625
17626 if auto_scroll {
17627 self.request_autoscroll(Autoscroll::fit(), cx);
17628 }
17629
17630 cx.notify();
17631
17632 self.scrollbar_marker_state.dirty = true;
17633 self.folds_did_change(cx);
17634 }
17635
17636 /// Removes any folds whose ranges intersect any of the given ranges.
17637 pub fn unfold_ranges<T: ToOffset + Clone>(
17638 &mut self,
17639 ranges: &[Range<T>],
17640 inclusive: bool,
17641 auto_scroll: bool,
17642 cx: &mut Context<Self>,
17643 ) {
17644 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17645 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17646 });
17647 self.folds_did_change(cx);
17648 }
17649
17650 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17651 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17652 return;
17653 }
17654 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17655 self.display_map.update(cx, |display_map, cx| {
17656 display_map.fold_buffers([buffer_id], cx)
17657 });
17658 cx.emit(EditorEvent::BufferFoldToggled {
17659 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17660 folded: true,
17661 });
17662 cx.notify();
17663 }
17664
17665 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17666 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17667 return;
17668 }
17669 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17670 self.display_map.update(cx, |display_map, cx| {
17671 display_map.unfold_buffers([buffer_id], cx);
17672 });
17673 cx.emit(EditorEvent::BufferFoldToggled {
17674 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17675 folded: false,
17676 });
17677 cx.notify();
17678 }
17679
17680 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17681 self.display_map.read(cx).is_buffer_folded(buffer)
17682 }
17683
17684 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17685 self.display_map.read(cx).folded_buffers()
17686 }
17687
17688 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17689 self.display_map.update(cx, |display_map, cx| {
17690 display_map.disable_header_for_buffer(buffer_id, cx);
17691 });
17692 cx.notify();
17693 }
17694
17695 /// Removes any folds with the given ranges.
17696 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17697 &mut self,
17698 ranges: &[Range<T>],
17699 type_id: TypeId,
17700 auto_scroll: bool,
17701 cx: &mut Context<Self>,
17702 ) {
17703 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17704 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17705 });
17706 self.folds_did_change(cx);
17707 }
17708
17709 fn remove_folds_with<T: ToOffset + Clone>(
17710 &mut self,
17711 ranges: &[Range<T>],
17712 auto_scroll: bool,
17713 cx: &mut Context<Self>,
17714 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17715 ) {
17716 if ranges.is_empty() {
17717 return;
17718 }
17719
17720 let mut buffers_affected = HashSet::default();
17721 let multi_buffer = self.buffer().read(cx);
17722 for range in ranges {
17723 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17724 buffers_affected.insert(buffer.read(cx).remote_id());
17725 };
17726 }
17727
17728 self.display_map.update(cx, update);
17729
17730 if auto_scroll {
17731 self.request_autoscroll(Autoscroll::fit(), cx);
17732 }
17733
17734 cx.notify();
17735 self.scrollbar_marker_state.dirty = true;
17736 self.active_indent_guides_state.dirty = true;
17737 }
17738
17739 pub fn update_renderer_widths(
17740 &mut self,
17741 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17742 cx: &mut Context<Self>,
17743 ) -> bool {
17744 self.display_map
17745 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17746 }
17747
17748 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17749 self.display_map.read(cx).fold_placeholder.clone()
17750 }
17751
17752 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17753 self.buffer.update(cx, |buffer, cx| {
17754 buffer.set_all_diff_hunks_expanded(cx);
17755 });
17756 }
17757
17758 pub fn expand_all_diff_hunks(
17759 &mut self,
17760 _: &ExpandAllDiffHunks,
17761 _window: &mut Window,
17762 cx: &mut Context<Self>,
17763 ) {
17764 self.buffer.update(cx, |buffer, cx| {
17765 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17766 });
17767 }
17768
17769 pub fn toggle_selected_diff_hunks(
17770 &mut self,
17771 _: &ToggleSelectedDiffHunks,
17772 _window: &mut Window,
17773 cx: &mut Context<Self>,
17774 ) {
17775 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17776 self.toggle_diff_hunks_in_ranges(ranges, cx);
17777 }
17778
17779 pub fn diff_hunks_in_ranges<'a>(
17780 &'a self,
17781 ranges: &'a [Range<Anchor>],
17782 buffer: &'a MultiBufferSnapshot,
17783 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17784 ranges.iter().flat_map(move |range| {
17785 let end_excerpt_id = range.end.excerpt_id;
17786 let range = range.to_point(buffer);
17787 let mut peek_end = range.end;
17788 if range.end.row < buffer.max_row().0 {
17789 peek_end = Point::new(range.end.row + 1, 0);
17790 }
17791 buffer
17792 .diff_hunks_in_range(range.start..peek_end)
17793 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17794 })
17795 }
17796
17797 pub fn has_stageable_diff_hunks_in_ranges(
17798 &self,
17799 ranges: &[Range<Anchor>],
17800 snapshot: &MultiBufferSnapshot,
17801 ) -> bool {
17802 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17803 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17804 }
17805
17806 pub fn toggle_staged_selected_diff_hunks(
17807 &mut self,
17808 _: &::git::ToggleStaged,
17809 _: &mut Window,
17810 cx: &mut Context<Self>,
17811 ) {
17812 let snapshot = self.buffer.read(cx).snapshot(cx);
17813 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17814 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17815 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17816 }
17817
17818 pub fn set_render_diff_hunk_controls(
17819 &mut self,
17820 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17821 cx: &mut Context<Self>,
17822 ) {
17823 self.render_diff_hunk_controls = render_diff_hunk_controls;
17824 cx.notify();
17825 }
17826
17827 pub fn stage_and_next(
17828 &mut self,
17829 _: &::git::StageAndNext,
17830 window: &mut Window,
17831 cx: &mut Context<Self>,
17832 ) {
17833 self.do_stage_or_unstage_and_next(true, window, cx);
17834 }
17835
17836 pub fn unstage_and_next(
17837 &mut self,
17838 _: &::git::UnstageAndNext,
17839 window: &mut Window,
17840 cx: &mut Context<Self>,
17841 ) {
17842 self.do_stage_or_unstage_and_next(false, window, cx);
17843 }
17844
17845 pub fn stage_or_unstage_diff_hunks(
17846 &mut self,
17847 stage: bool,
17848 ranges: Vec<Range<Anchor>>,
17849 cx: &mut Context<Self>,
17850 ) {
17851 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17852 cx.spawn(async move |this, cx| {
17853 task.await?;
17854 this.update(cx, |this, cx| {
17855 let snapshot = this.buffer.read(cx).snapshot(cx);
17856 let chunk_by = this
17857 .diff_hunks_in_ranges(&ranges, &snapshot)
17858 .chunk_by(|hunk| hunk.buffer_id);
17859 for (buffer_id, hunks) in &chunk_by {
17860 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17861 }
17862 })
17863 })
17864 .detach_and_log_err(cx);
17865 }
17866
17867 fn save_buffers_for_ranges_if_needed(
17868 &mut self,
17869 ranges: &[Range<Anchor>],
17870 cx: &mut Context<Editor>,
17871 ) -> Task<Result<()>> {
17872 let multibuffer = self.buffer.read(cx);
17873 let snapshot = multibuffer.read(cx);
17874 let buffer_ids: HashSet<_> = ranges
17875 .iter()
17876 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17877 .collect();
17878 drop(snapshot);
17879
17880 let mut buffers = HashSet::default();
17881 for buffer_id in buffer_ids {
17882 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17883 let buffer = buffer_entity.read(cx);
17884 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17885 {
17886 buffers.insert(buffer_entity);
17887 }
17888 }
17889 }
17890
17891 if let Some(project) = &self.project {
17892 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17893 } else {
17894 Task::ready(Ok(()))
17895 }
17896 }
17897
17898 fn do_stage_or_unstage_and_next(
17899 &mut self,
17900 stage: bool,
17901 window: &mut Window,
17902 cx: &mut Context<Self>,
17903 ) {
17904 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17905
17906 if ranges.iter().any(|range| range.start != range.end) {
17907 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17908 return;
17909 }
17910
17911 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17912 let snapshot = self.snapshot(window, cx);
17913 let position = self.selections.newest::<Point>(cx).head();
17914 let mut row = snapshot
17915 .buffer_snapshot
17916 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17917 .find(|hunk| hunk.row_range.start.0 > position.row)
17918 .map(|hunk| hunk.row_range.start);
17919
17920 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17921 // Outside of the project diff editor, wrap around to the beginning.
17922 if !all_diff_hunks_expanded {
17923 row = row.or_else(|| {
17924 snapshot
17925 .buffer_snapshot
17926 .diff_hunks_in_range(Point::zero()..position)
17927 .find(|hunk| hunk.row_range.end.0 < position.row)
17928 .map(|hunk| hunk.row_range.start)
17929 });
17930 }
17931
17932 if let Some(row) = row {
17933 let destination = Point::new(row.0, 0);
17934 let autoscroll = Autoscroll::center();
17935
17936 self.unfold_ranges(&[destination..destination], false, false, cx);
17937 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17938 s.select_ranges([destination..destination]);
17939 });
17940 }
17941 }
17942
17943 fn do_stage_or_unstage(
17944 &self,
17945 stage: bool,
17946 buffer_id: BufferId,
17947 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17948 cx: &mut App,
17949 ) -> Option<()> {
17950 let project = self.project()?;
17951 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17952 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17953 let buffer_snapshot = buffer.read(cx).snapshot();
17954 let file_exists = buffer_snapshot
17955 .file()
17956 .is_some_and(|file| file.disk_state().exists());
17957 diff.update(cx, |diff, cx| {
17958 diff.stage_or_unstage_hunks(
17959 stage,
17960 &hunks
17961 .map(|hunk| buffer_diff::DiffHunk {
17962 buffer_range: hunk.buffer_range,
17963 diff_base_byte_range: hunk.diff_base_byte_range,
17964 secondary_status: hunk.secondary_status,
17965 range: Point::zero()..Point::zero(), // unused
17966 })
17967 .collect::<Vec<_>>(),
17968 &buffer_snapshot,
17969 file_exists,
17970 cx,
17971 )
17972 });
17973 None
17974 }
17975
17976 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17977 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17978 self.buffer
17979 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17980 }
17981
17982 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17983 self.buffer.update(cx, |buffer, cx| {
17984 let ranges = vec![Anchor::min()..Anchor::max()];
17985 if !buffer.all_diff_hunks_expanded()
17986 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17987 {
17988 buffer.collapse_diff_hunks(ranges, cx);
17989 true
17990 } else {
17991 false
17992 }
17993 })
17994 }
17995
17996 fn toggle_diff_hunks_in_ranges(
17997 &mut self,
17998 ranges: Vec<Range<Anchor>>,
17999 cx: &mut Context<Editor>,
18000 ) {
18001 self.buffer.update(cx, |buffer, cx| {
18002 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18003 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18004 })
18005 }
18006
18007 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18008 self.buffer.update(cx, |buffer, cx| {
18009 let snapshot = buffer.snapshot(cx);
18010 let excerpt_id = range.end.excerpt_id;
18011 let point_range = range.to_point(&snapshot);
18012 let expand = !buffer.single_hunk_is_expanded(range, cx);
18013 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18014 })
18015 }
18016
18017 pub(crate) fn apply_all_diff_hunks(
18018 &mut self,
18019 _: &ApplyAllDiffHunks,
18020 window: &mut Window,
18021 cx: &mut Context<Self>,
18022 ) {
18023 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18024
18025 let buffers = self.buffer.read(cx).all_buffers();
18026 for branch_buffer in buffers {
18027 branch_buffer.update(cx, |branch_buffer, cx| {
18028 branch_buffer.merge_into_base(Vec::new(), cx);
18029 });
18030 }
18031
18032 if let Some(project) = self.project.clone() {
18033 self.save(
18034 SaveOptions {
18035 format: true,
18036 autosave: false,
18037 },
18038 project,
18039 window,
18040 cx,
18041 )
18042 .detach_and_log_err(cx);
18043 }
18044 }
18045
18046 pub(crate) fn apply_selected_diff_hunks(
18047 &mut self,
18048 _: &ApplyDiffHunk,
18049 window: &mut Window,
18050 cx: &mut Context<Self>,
18051 ) {
18052 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18053 let snapshot = self.snapshot(window, cx);
18054 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18055 let mut ranges_by_buffer = HashMap::default();
18056 self.transact(window, cx, |editor, _window, cx| {
18057 for hunk in hunks {
18058 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18059 ranges_by_buffer
18060 .entry(buffer.clone())
18061 .or_insert_with(Vec::new)
18062 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18063 }
18064 }
18065
18066 for (buffer, ranges) in ranges_by_buffer {
18067 buffer.update(cx, |buffer, cx| {
18068 buffer.merge_into_base(ranges, cx);
18069 });
18070 }
18071 });
18072
18073 if let Some(project) = self.project.clone() {
18074 self.save(
18075 SaveOptions {
18076 format: true,
18077 autosave: false,
18078 },
18079 project,
18080 window,
18081 cx,
18082 )
18083 .detach_and_log_err(cx);
18084 }
18085 }
18086
18087 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18088 if hovered != self.gutter_hovered {
18089 self.gutter_hovered = hovered;
18090 cx.notify();
18091 }
18092 }
18093
18094 pub fn insert_blocks(
18095 &mut self,
18096 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18097 autoscroll: Option<Autoscroll>,
18098 cx: &mut Context<Self>,
18099 ) -> Vec<CustomBlockId> {
18100 let blocks = self
18101 .display_map
18102 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18103 if let Some(autoscroll) = autoscroll {
18104 self.request_autoscroll(autoscroll, cx);
18105 }
18106 cx.notify();
18107 blocks
18108 }
18109
18110 pub fn resize_blocks(
18111 &mut self,
18112 heights: HashMap<CustomBlockId, u32>,
18113 autoscroll: Option<Autoscroll>,
18114 cx: &mut Context<Self>,
18115 ) {
18116 self.display_map
18117 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18118 if let Some(autoscroll) = autoscroll {
18119 self.request_autoscroll(autoscroll, cx);
18120 }
18121 cx.notify();
18122 }
18123
18124 pub fn replace_blocks(
18125 &mut self,
18126 renderers: HashMap<CustomBlockId, RenderBlock>,
18127 autoscroll: Option<Autoscroll>,
18128 cx: &mut Context<Self>,
18129 ) {
18130 self.display_map
18131 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18132 if let Some(autoscroll) = autoscroll {
18133 self.request_autoscroll(autoscroll, cx);
18134 }
18135 cx.notify();
18136 }
18137
18138 pub fn remove_blocks(
18139 &mut self,
18140 block_ids: HashSet<CustomBlockId>,
18141 autoscroll: Option<Autoscroll>,
18142 cx: &mut Context<Self>,
18143 ) {
18144 self.display_map.update(cx, |display_map, cx| {
18145 display_map.remove_blocks(block_ids, cx)
18146 });
18147 if let Some(autoscroll) = autoscroll {
18148 self.request_autoscroll(autoscroll, cx);
18149 }
18150 cx.notify();
18151 }
18152
18153 pub fn row_for_block(
18154 &self,
18155 block_id: CustomBlockId,
18156 cx: &mut Context<Self>,
18157 ) -> Option<DisplayRow> {
18158 self.display_map
18159 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18160 }
18161
18162 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18163 self.focused_block = Some(focused_block);
18164 }
18165
18166 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18167 self.focused_block.take()
18168 }
18169
18170 pub fn insert_creases(
18171 &mut self,
18172 creases: impl IntoIterator<Item = Crease<Anchor>>,
18173 cx: &mut Context<Self>,
18174 ) -> Vec<CreaseId> {
18175 self.display_map
18176 .update(cx, |map, cx| map.insert_creases(creases, cx))
18177 }
18178
18179 pub fn remove_creases(
18180 &mut self,
18181 ids: impl IntoIterator<Item = CreaseId>,
18182 cx: &mut Context<Self>,
18183 ) -> Vec<(CreaseId, Range<Anchor>)> {
18184 self.display_map
18185 .update(cx, |map, cx| map.remove_creases(ids, cx))
18186 }
18187
18188 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18189 self.display_map
18190 .update(cx, |map, cx| map.snapshot(cx))
18191 .longest_row()
18192 }
18193
18194 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18195 self.display_map
18196 .update(cx, |map, cx| map.snapshot(cx))
18197 .max_point()
18198 }
18199
18200 pub fn text(&self, cx: &App) -> String {
18201 self.buffer.read(cx).read(cx).text()
18202 }
18203
18204 pub fn is_empty(&self, cx: &App) -> bool {
18205 self.buffer.read(cx).read(cx).is_empty()
18206 }
18207
18208 pub fn text_option(&self, cx: &App) -> Option<String> {
18209 let text = self.text(cx);
18210 let text = text.trim();
18211
18212 if text.is_empty() {
18213 return None;
18214 }
18215
18216 Some(text.to_string())
18217 }
18218
18219 pub fn set_text(
18220 &mut self,
18221 text: impl Into<Arc<str>>,
18222 window: &mut Window,
18223 cx: &mut Context<Self>,
18224 ) {
18225 self.transact(window, cx, |this, _, cx| {
18226 this.buffer
18227 .read(cx)
18228 .as_singleton()
18229 .expect("you can only call set_text on editors for singleton buffers")
18230 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18231 });
18232 }
18233
18234 pub fn display_text(&self, cx: &mut App) -> String {
18235 self.display_map
18236 .update(cx, |map, cx| map.snapshot(cx))
18237 .text()
18238 }
18239
18240 fn create_minimap(
18241 &self,
18242 minimap_settings: MinimapSettings,
18243 window: &mut Window,
18244 cx: &mut Context<Self>,
18245 ) -> Option<Entity<Self>> {
18246 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18247 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18248 }
18249
18250 fn initialize_new_minimap(
18251 &self,
18252 minimap_settings: MinimapSettings,
18253 window: &mut Window,
18254 cx: &mut Context<Self>,
18255 ) -> Entity<Self> {
18256 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18257
18258 let mut minimap = Editor::new_internal(
18259 EditorMode::Minimap {
18260 parent: cx.weak_entity(),
18261 },
18262 self.buffer.clone(),
18263 None,
18264 Some(self.display_map.clone()),
18265 window,
18266 cx,
18267 );
18268 minimap.scroll_manager.clone_state(&self.scroll_manager);
18269 minimap.set_text_style_refinement(TextStyleRefinement {
18270 font_size: Some(MINIMAP_FONT_SIZE),
18271 font_weight: Some(MINIMAP_FONT_WEIGHT),
18272 ..Default::default()
18273 });
18274 minimap.update_minimap_configuration(minimap_settings, cx);
18275 cx.new(|_| minimap)
18276 }
18277
18278 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18279 let current_line_highlight = minimap_settings
18280 .current_line_highlight
18281 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18282 self.set_current_line_highlight(Some(current_line_highlight));
18283 }
18284
18285 pub fn minimap(&self) -> Option<&Entity<Self>> {
18286 self.minimap
18287 .as_ref()
18288 .filter(|_| self.minimap_visibility.visible())
18289 }
18290
18291 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18292 let mut wrap_guides = smallvec![];
18293
18294 if self.show_wrap_guides == Some(false) {
18295 return wrap_guides;
18296 }
18297
18298 let settings = self.buffer.read(cx).language_settings(cx);
18299 if settings.show_wrap_guides {
18300 match self.soft_wrap_mode(cx) {
18301 SoftWrap::Column(soft_wrap) => {
18302 wrap_guides.push((soft_wrap as usize, true));
18303 }
18304 SoftWrap::Bounded(soft_wrap) => {
18305 wrap_guides.push((soft_wrap as usize, true));
18306 }
18307 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18308 }
18309 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18310 }
18311
18312 wrap_guides
18313 }
18314
18315 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18316 let settings = self.buffer.read(cx).language_settings(cx);
18317 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18318 match mode {
18319 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18320 SoftWrap::None
18321 }
18322 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18323 language_settings::SoftWrap::PreferredLineLength => {
18324 SoftWrap::Column(settings.preferred_line_length)
18325 }
18326 language_settings::SoftWrap::Bounded => {
18327 SoftWrap::Bounded(settings.preferred_line_length)
18328 }
18329 }
18330 }
18331
18332 pub fn set_soft_wrap_mode(
18333 &mut self,
18334 mode: language_settings::SoftWrap,
18335
18336 cx: &mut Context<Self>,
18337 ) {
18338 self.soft_wrap_mode_override = Some(mode);
18339 cx.notify();
18340 }
18341
18342 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18343 self.hard_wrap = hard_wrap;
18344 cx.notify();
18345 }
18346
18347 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18348 self.text_style_refinement = Some(style);
18349 }
18350
18351 /// called by the Element so we know what style we were most recently rendered with.
18352 pub(crate) fn set_style(
18353 &mut self,
18354 style: EditorStyle,
18355 window: &mut Window,
18356 cx: &mut Context<Self>,
18357 ) {
18358 // We intentionally do not inform the display map about the minimap style
18359 // so that wrapping is not recalculated and stays consistent for the editor
18360 // and its linked minimap.
18361 if !self.mode.is_minimap() {
18362 let rem_size = window.rem_size();
18363 self.display_map.update(cx, |map, cx| {
18364 map.set_font(
18365 style.text.font(),
18366 style.text.font_size.to_pixels(rem_size),
18367 cx,
18368 )
18369 });
18370 }
18371 self.style = Some(style);
18372 }
18373
18374 pub fn style(&self) -> Option<&EditorStyle> {
18375 self.style.as_ref()
18376 }
18377
18378 // Called by the element. This method is not designed to be called outside of the editor
18379 // element's layout code because it does not notify when rewrapping is computed synchronously.
18380 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18381 self.display_map
18382 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18383 }
18384
18385 pub fn set_soft_wrap(&mut self) {
18386 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18387 }
18388
18389 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18390 if self.soft_wrap_mode_override.is_some() {
18391 self.soft_wrap_mode_override.take();
18392 } else {
18393 let soft_wrap = match self.soft_wrap_mode(cx) {
18394 SoftWrap::GitDiff => return,
18395 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18396 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18397 language_settings::SoftWrap::None
18398 }
18399 };
18400 self.soft_wrap_mode_override = Some(soft_wrap);
18401 }
18402 cx.notify();
18403 }
18404
18405 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18406 let Some(workspace) = self.workspace() else {
18407 return;
18408 };
18409 let fs = workspace.read(cx).app_state().fs.clone();
18410 let current_show = TabBarSettings::get_global(cx).show;
18411 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18412 setting.show = Some(!current_show);
18413 });
18414 }
18415
18416 pub fn toggle_indent_guides(
18417 &mut self,
18418 _: &ToggleIndentGuides,
18419 _: &mut Window,
18420 cx: &mut Context<Self>,
18421 ) {
18422 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18423 self.buffer
18424 .read(cx)
18425 .language_settings(cx)
18426 .indent_guides
18427 .enabled
18428 });
18429 self.show_indent_guides = Some(!currently_enabled);
18430 cx.notify();
18431 }
18432
18433 fn should_show_indent_guides(&self) -> Option<bool> {
18434 self.show_indent_guides
18435 }
18436
18437 pub fn toggle_line_numbers(
18438 &mut self,
18439 _: &ToggleLineNumbers,
18440 _: &mut Window,
18441 cx: &mut Context<Self>,
18442 ) {
18443 let mut editor_settings = EditorSettings::get_global(cx).clone();
18444 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18445 EditorSettings::override_global(editor_settings, cx);
18446 }
18447
18448 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18449 if let Some(show_line_numbers) = self.show_line_numbers {
18450 return show_line_numbers;
18451 }
18452 EditorSettings::get_global(cx).gutter.line_numbers
18453 }
18454
18455 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18456 self.use_relative_line_numbers
18457 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18458 }
18459
18460 pub fn toggle_relative_line_numbers(
18461 &mut self,
18462 _: &ToggleRelativeLineNumbers,
18463 _: &mut Window,
18464 cx: &mut Context<Self>,
18465 ) {
18466 let is_relative = self.should_use_relative_line_numbers(cx);
18467 self.set_relative_line_number(Some(!is_relative), cx)
18468 }
18469
18470 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18471 self.use_relative_line_numbers = is_relative;
18472 cx.notify();
18473 }
18474
18475 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18476 self.show_gutter = show_gutter;
18477 cx.notify();
18478 }
18479
18480 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18481 self.show_scrollbars = ScrollbarAxes {
18482 horizontal: show,
18483 vertical: show,
18484 };
18485 cx.notify();
18486 }
18487
18488 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18489 self.show_scrollbars.vertical = show;
18490 cx.notify();
18491 }
18492
18493 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18494 self.show_scrollbars.horizontal = show;
18495 cx.notify();
18496 }
18497
18498 pub fn set_minimap_visibility(
18499 &mut self,
18500 minimap_visibility: MinimapVisibility,
18501 window: &mut Window,
18502 cx: &mut Context<Self>,
18503 ) {
18504 if self.minimap_visibility != minimap_visibility {
18505 if minimap_visibility.visible() && self.minimap.is_none() {
18506 let minimap_settings = EditorSettings::get_global(cx).minimap;
18507 self.minimap =
18508 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18509 }
18510 self.minimap_visibility = minimap_visibility;
18511 cx.notify();
18512 }
18513 }
18514
18515 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18516 self.set_show_scrollbars(false, cx);
18517 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18518 }
18519
18520 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18521 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18522 }
18523
18524 /// Normally the text in full mode and auto height editors is padded on the
18525 /// left side by roughly half a character width for improved hit testing.
18526 ///
18527 /// Use this method to disable this for cases where this is not wanted (e.g.
18528 /// if you want to align the editor text with some other text above or below)
18529 /// or if you want to add this padding to single-line editors.
18530 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18531 self.offset_content = offset_content;
18532 cx.notify();
18533 }
18534
18535 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18536 self.show_line_numbers = Some(show_line_numbers);
18537 cx.notify();
18538 }
18539
18540 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18541 self.disable_expand_excerpt_buttons = true;
18542 cx.notify();
18543 }
18544
18545 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18546 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18547 cx.notify();
18548 }
18549
18550 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18551 self.show_code_actions = Some(show_code_actions);
18552 cx.notify();
18553 }
18554
18555 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18556 self.show_runnables = Some(show_runnables);
18557 cx.notify();
18558 }
18559
18560 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18561 self.show_breakpoints = Some(show_breakpoints);
18562 cx.notify();
18563 }
18564
18565 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18566 if self.display_map.read(cx).masked != masked {
18567 self.display_map.update(cx, |map, _| map.masked = masked);
18568 }
18569 cx.notify()
18570 }
18571
18572 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18573 self.show_wrap_guides = Some(show_wrap_guides);
18574 cx.notify();
18575 }
18576
18577 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18578 self.show_indent_guides = Some(show_indent_guides);
18579 cx.notify();
18580 }
18581
18582 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18583 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18584 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18585 && let Some(dir) = file.abs_path(cx).parent()
18586 {
18587 return Some(dir.to_owned());
18588 }
18589
18590 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18591 return Some(project_path.path.to_path_buf());
18592 }
18593 }
18594
18595 None
18596 }
18597
18598 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18599 self.active_excerpt(cx)?
18600 .1
18601 .read(cx)
18602 .file()
18603 .and_then(|f| f.as_local())
18604 }
18605
18606 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18607 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18608 let buffer = buffer.read(cx);
18609 if let Some(project_path) = buffer.project_path(cx) {
18610 let project = self.project()?.read(cx);
18611 project.absolute_path(&project_path, cx)
18612 } else {
18613 buffer
18614 .file()
18615 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18616 }
18617 })
18618 }
18619
18620 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18621 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18622 let project_path = buffer.read(cx).project_path(cx)?;
18623 let project = self.project()?.read(cx);
18624 let entry = project.entry_for_path(&project_path, cx)?;
18625 let path = entry.path.to_path_buf();
18626 Some(path)
18627 })
18628 }
18629
18630 pub fn reveal_in_finder(
18631 &mut self,
18632 _: &RevealInFileManager,
18633 _window: &mut Window,
18634 cx: &mut Context<Self>,
18635 ) {
18636 if let Some(target) = self.target_file(cx) {
18637 cx.reveal_path(&target.abs_path(cx));
18638 }
18639 }
18640
18641 pub fn copy_path(
18642 &mut self,
18643 _: &zed_actions::workspace::CopyPath,
18644 _window: &mut Window,
18645 cx: &mut Context<Self>,
18646 ) {
18647 if let Some(path) = self.target_file_abs_path(cx)
18648 && let Some(path) = path.to_str()
18649 {
18650 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18651 }
18652 }
18653
18654 pub fn copy_relative_path(
18655 &mut self,
18656 _: &zed_actions::workspace::CopyRelativePath,
18657 _window: &mut Window,
18658 cx: &mut Context<Self>,
18659 ) {
18660 if let Some(path) = self.target_file_path(cx)
18661 && let Some(path) = path.to_str()
18662 {
18663 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18664 }
18665 }
18666
18667 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18668 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18669 buffer.read(cx).project_path(cx)
18670 } else {
18671 None
18672 }
18673 }
18674
18675 // Returns true if the editor handled a go-to-line request
18676 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18677 maybe!({
18678 let breakpoint_store = self.breakpoint_store.as_ref()?;
18679
18680 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18681 else {
18682 self.clear_row_highlights::<ActiveDebugLine>();
18683 return None;
18684 };
18685
18686 let position = active_stack_frame.position;
18687 let buffer_id = position.buffer_id?;
18688 let snapshot = self
18689 .project
18690 .as_ref()?
18691 .read(cx)
18692 .buffer_for_id(buffer_id, cx)?
18693 .read(cx)
18694 .snapshot();
18695
18696 let mut handled = false;
18697 for (id, ExcerptRange { context, .. }) in
18698 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18699 {
18700 if context.start.cmp(&position, &snapshot).is_ge()
18701 || context.end.cmp(&position, &snapshot).is_lt()
18702 {
18703 continue;
18704 }
18705 let snapshot = self.buffer.read(cx).snapshot(cx);
18706 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18707
18708 handled = true;
18709 self.clear_row_highlights::<ActiveDebugLine>();
18710
18711 self.go_to_line::<ActiveDebugLine>(
18712 multibuffer_anchor,
18713 Some(cx.theme().colors().editor_debugger_active_line_background),
18714 window,
18715 cx,
18716 );
18717
18718 cx.notify();
18719 }
18720
18721 handled.then_some(())
18722 })
18723 .is_some()
18724 }
18725
18726 pub fn copy_file_name_without_extension(
18727 &mut self,
18728 _: &CopyFileNameWithoutExtension,
18729 _: &mut Window,
18730 cx: &mut Context<Self>,
18731 ) {
18732 if let Some(file) = self.target_file(cx)
18733 && let Some(file_stem) = file.path().file_stem()
18734 && let Some(name) = file_stem.to_str()
18735 {
18736 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18737 }
18738 }
18739
18740 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18741 if let Some(file) = self.target_file(cx)
18742 && let Some(file_name) = file.path().file_name()
18743 && let Some(name) = file_name.to_str()
18744 {
18745 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18746 }
18747 }
18748
18749 pub fn toggle_git_blame(
18750 &mut self,
18751 _: &::git::Blame,
18752 window: &mut Window,
18753 cx: &mut Context<Self>,
18754 ) {
18755 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18756
18757 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18758 self.start_git_blame(true, window, cx);
18759 }
18760
18761 cx.notify();
18762 }
18763
18764 pub fn toggle_git_blame_inline(
18765 &mut self,
18766 _: &ToggleGitBlameInline,
18767 window: &mut Window,
18768 cx: &mut Context<Self>,
18769 ) {
18770 self.toggle_git_blame_inline_internal(true, window, cx);
18771 cx.notify();
18772 }
18773
18774 pub fn open_git_blame_commit(
18775 &mut self,
18776 _: &OpenGitBlameCommit,
18777 window: &mut Window,
18778 cx: &mut Context<Self>,
18779 ) {
18780 self.open_git_blame_commit_internal(window, cx);
18781 }
18782
18783 fn open_git_blame_commit_internal(
18784 &mut self,
18785 window: &mut Window,
18786 cx: &mut Context<Self>,
18787 ) -> Option<()> {
18788 let blame = self.blame.as_ref()?;
18789 let snapshot = self.snapshot(window, cx);
18790 let cursor = self.selections.newest::<Point>(cx).head();
18791 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18792 let blame_entry = blame
18793 .update(cx, |blame, cx| {
18794 blame
18795 .blame_for_rows(
18796 &[RowInfo {
18797 buffer_id: Some(buffer.remote_id()),
18798 buffer_row: Some(point.row),
18799 ..Default::default()
18800 }],
18801 cx,
18802 )
18803 .next()
18804 })
18805 .flatten()?;
18806 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18807 let repo = blame.read(cx).repository(cx)?;
18808 let workspace = self.workspace()?.downgrade();
18809 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18810 None
18811 }
18812
18813 pub fn git_blame_inline_enabled(&self) -> bool {
18814 self.git_blame_inline_enabled
18815 }
18816
18817 pub fn toggle_selection_menu(
18818 &mut self,
18819 _: &ToggleSelectionMenu,
18820 _: &mut Window,
18821 cx: &mut Context<Self>,
18822 ) {
18823 self.show_selection_menu = self
18824 .show_selection_menu
18825 .map(|show_selections_menu| !show_selections_menu)
18826 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18827
18828 cx.notify();
18829 }
18830
18831 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18832 self.show_selection_menu
18833 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18834 }
18835
18836 fn start_git_blame(
18837 &mut self,
18838 user_triggered: bool,
18839 window: &mut Window,
18840 cx: &mut Context<Self>,
18841 ) {
18842 if let Some(project) = self.project() {
18843 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18844 return;
18845 };
18846
18847 if buffer.read(cx).file().is_none() {
18848 return;
18849 }
18850
18851 let focused = self.focus_handle(cx).contains_focused(window, cx);
18852
18853 let project = project.clone();
18854 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18855 self.blame_subscription =
18856 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18857 self.blame = Some(blame);
18858 }
18859 }
18860
18861 fn toggle_git_blame_inline_internal(
18862 &mut self,
18863 user_triggered: bool,
18864 window: &mut Window,
18865 cx: &mut Context<Self>,
18866 ) {
18867 if self.git_blame_inline_enabled {
18868 self.git_blame_inline_enabled = false;
18869 self.show_git_blame_inline = false;
18870 self.show_git_blame_inline_delay_task.take();
18871 } else {
18872 self.git_blame_inline_enabled = true;
18873 self.start_git_blame_inline(user_triggered, window, cx);
18874 }
18875
18876 cx.notify();
18877 }
18878
18879 fn start_git_blame_inline(
18880 &mut self,
18881 user_triggered: bool,
18882 window: &mut Window,
18883 cx: &mut Context<Self>,
18884 ) {
18885 self.start_git_blame(user_triggered, window, cx);
18886
18887 if ProjectSettings::get_global(cx)
18888 .git
18889 .inline_blame_delay()
18890 .is_some()
18891 {
18892 self.start_inline_blame_timer(window, cx);
18893 } else {
18894 self.show_git_blame_inline = true
18895 }
18896 }
18897
18898 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18899 self.blame.as_ref()
18900 }
18901
18902 pub fn show_git_blame_gutter(&self) -> bool {
18903 self.show_git_blame_gutter
18904 }
18905
18906 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18907 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18908 }
18909
18910 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18911 self.show_git_blame_inline
18912 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18913 && !self.newest_selection_head_on_empty_line(cx)
18914 && self.has_blame_entries(cx)
18915 }
18916
18917 fn has_blame_entries(&self, cx: &App) -> bool {
18918 self.blame()
18919 .is_some_and(|blame| blame.read(cx).has_generated_entries())
18920 }
18921
18922 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18923 let cursor_anchor = self.selections.newest_anchor().head();
18924
18925 let snapshot = self.buffer.read(cx).snapshot(cx);
18926 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18927
18928 snapshot.line_len(buffer_row) == 0
18929 }
18930
18931 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18932 let buffer_and_selection = maybe!({
18933 let selection = self.selections.newest::<Point>(cx);
18934 let selection_range = selection.range();
18935
18936 let multi_buffer = self.buffer().read(cx);
18937 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18938 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18939
18940 let (buffer, range, _) = if selection.reversed {
18941 buffer_ranges.first()
18942 } else {
18943 buffer_ranges.last()
18944 }?;
18945
18946 let selection = text::ToPoint::to_point(&range.start, buffer).row
18947 ..text::ToPoint::to_point(&range.end, buffer).row;
18948 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
18949 });
18950
18951 let Some((buffer, selection)) = buffer_and_selection else {
18952 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18953 };
18954
18955 let Some(project) = self.project() else {
18956 return Task::ready(Err(anyhow!("editor does not have project")));
18957 };
18958
18959 project.update(cx, |project, cx| {
18960 project.get_permalink_to_line(&buffer, selection, cx)
18961 })
18962 }
18963
18964 pub fn copy_permalink_to_line(
18965 &mut self,
18966 _: &CopyPermalinkToLine,
18967 window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) {
18970 let permalink_task = self.get_permalink_to_line(cx);
18971 let workspace = self.workspace();
18972
18973 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18974 Ok(permalink) => {
18975 cx.update(|_, cx| {
18976 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18977 })
18978 .ok();
18979 }
18980 Err(err) => {
18981 let message = format!("Failed to copy permalink: {err}");
18982
18983 anyhow::Result::<()>::Err(err).log_err();
18984
18985 if let Some(workspace) = workspace {
18986 workspace
18987 .update_in(cx, |workspace, _, cx| {
18988 struct CopyPermalinkToLine;
18989
18990 workspace.show_toast(
18991 Toast::new(
18992 NotificationId::unique::<CopyPermalinkToLine>(),
18993 message,
18994 ),
18995 cx,
18996 )
18997 })
18998 .ok();
18999 }
19000 }
19001 })
19002 .detach();
19003 }
19004
19005 pub fn copy_file_location(
19006 &mut self,
19007 _: &CopyFileLocation,
19008 _: &mut Window,
19009 cx: &mut Context<Self>,
19010 ) {
19011 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19012 if let Some(file) = self.target_file(cx)
19013 && let Some(path) = file.path().to_str()
19014 {
19015 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19016 }
19017 }
19018
19019 pub fn open_permalink_to_line(
19020 &mut self,
19021 _: &OpenPermalinkToLine,
19022 window: &mut Window,
19023 cx: &mut Context<Self>,
19024 ) {
19025 let permalink_task = self.get_permalink_to_line(cx);
19026 let workspace = self.workspace();
19027
19028 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19029 Ok(permalink) => {
19030 cx.update(|_, cx| {
19031 cx.open_url(permalink.as_ref());
19032 })
19033 .ok();
19034 }
19035 Err(err) => {
19036 let message = format!("Failed to open permalink: {err}");
19037
19038 anyhow::Result::<()>::Err(err).log_err();
19039
19040 if let Some(workspace) = workspace {
19041 workspace
19042 .update(cx, |workspace, cx| {
19043 struct OpenPermalinkToLine;
19044
19045 workspace.show_toast(
19046 Toast::new(
19047 NotificationId::unique::<OpenPermalinkToLine>(),
19048 message,
19049 ),
19050 cx,
19051 )
19052 })
19053 .ok();
19054 }
19055 }
19056 })
19057 .detach();
19058 }
19059
19060 pub fn insert_uuid_v4(
19061 &mut self,
19062 _: &InsertUuidV4,
19063 window: &mut Window,
19064 cx: &mut Context<Self>,
19065 ) {
19066 self.insert_uuid(UuidVersion::V4, window, cx);
19067 }
19068
19069 pub fn insert_uuid_v7(
19070 &mut self,
19071 _: &InsertUuidV7,
19072 window: &mut Window,
19073 cx: &mut Context<Self>,
19074 ) {
19075 self.insert_uuid(UuidVersion::V7, window, cx);
19076 }
19077
19078 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19079 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19080 self.transact(window, cx, |this, window, cx| {
19081 let edits = this
19082 .selections
19083 .all::<Point>(cx)
19084 .into_iter()
19085 .map(|selection| {
19086 let uuid = match version {
19087 UuidVersion::V4 => uuid::Uuid::new_v4(),
19088 UuidVersion::V7 => uuid::Uuid::now_v7(),
19089 };
19090
19091 (selection.range(), uuid.to_string())
19092 });
19093 this.edit(edits, cx);
19094 this.refresh_edit_prediction(true, false, window, cx);
19095 });
19096 }
19097
19098 pub fn open_selections_in_multibuffer(
19099 &mut self,
19100 _: &OpenSelectionsInMultibuffer,
19101 window: &mut Window,
19102 cx: &mut Context<Self>,
19103 ) {
19104 let multibuffer = self.buffer.read(cx);
19105
19106 let Some(buffer) = multibuffer.as_singleton() else {
19107 return;
19108 };
19109
19110 let Some(workspace) = self.workspace() else {
19111 return;
19112 };
19113
19114 let title = multibuffer.title(cx).to_string();
19115
19116 let locations = self
19117 .selections
19118 .all_anchors(cx)
19119 .iter()
19120 .map(|selection| Location {
19121 buffer: buffer.clone(),
19122 range: selection.start.text_anchor..selection.end.text_anchor,
19123 })
19124 .collect::<Vec<_>>();
19125
19126 cx.spawn_in(window, async move |_, cx| {
19127 workspace.update_in(cx, |workspace, window, cx| {
19128 Self::open_locations_in_multibuffer(
19129 workspace,
19130 locations,
19131 format!("Selections for '{title}'"),
19132 false,
19133 MultibufferSelectionMode::All,
19134 window,
19135 cx,
19136 );
19137 })
19138 })
19139 .detach();
19140 }
19141
19142 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19143 /// last highlight added will be used.
19144 ///
19145 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19146 pub fn highlight_rows<T: 'static>(
19147 &mut self,
19148 range: Range<Anchor>,
19149 color: Hsla,
19150 options: RowHighlightOptions,
19151 cx: &mut Context<Self>,
19152 ) {
19153 let snapshot = self.buffer().read(cx).snapshot(cx);
19154 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19155 let ix = row_highlights.binary_search_by(|highlight| {
19156 Ordering::Equal
19157 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19158 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19159 });
19160
19161 if let Err(mut ix) = ix {
19162 let index = post_inc(&mut self.highlight_order);
19163
19164 // If this range intersects with the preceding highlight, then merge it with
19165 // the preceding highlight. Otherwise insert a new highlight.
19166 let mut merged = false;
19167 if ix > 0 {
19168 let prev_highlight = &mut row_highlights[ix - 1];
19169 if prev_highlight
19170 .range
19171 .end
19172 .cmp(&range.start, &snapshot)
19173 .is_ge()
19174 {
19175 ix -= 1;
19176 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19177 prev_highlight.range.end = range.end;
19178 }
19179 merged = true;
19180 prev_highlight.index = index;
19181 prev_highlight.color = color;
19182 prev_highlight.options = options;
19183 }
19184 }
19185
19186 if !merged {
19187 row_highlights.insert(
19188 ix,
19189 RowHighlight {
19190 range,
19191 index,
19192 color,
19193 options,
19194 type_id: TypeId::of::<T>(),
19195 },
19196 );
19197 }
19198
19199 // If any of the following highlights intersect with this one, merge them.
19200 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19201 let highlight = &row_highlights[ix];
19202 if next_highlight
19203 .range
19204 .start
19205 .cmp(&highlight.range.end, &snapshot)
19206 .is_le()
19207 {
19208 if next_highlight
19209 .range
19210 .end
19211 .cmp(&highlight.range.end, &snapshot)
19212 .is_gt()
19213 {
19214 row_highlights[ix].range.end = next_highlight.range.end;
19215 }
19216 row_highlights.remove(ix + 1);
19217 } else {
19218 break;
19219 }
19220 }
19221 }
19222 }
19223
19224 /// Remove any highlighted row ranges of the given type that intersect the
19225 /// given ranges.
19226 pub fn remove_highlighted_rows<T: 'static>(
19227 &mut self,
19228 ranges_to_remove: Vec<Range<Anchor>>,
19229 cx: &mut Context<Self>,
19230 ) {
19231 let snapshot = self.buffer().read(cx).snapshot(cx);
19232 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19233 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19234 row_highlights.retain(|highlight| {
19235 while let Some(range_to_remove) = ranges_to_remove.peek() {
19236 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19237 Ordering::Less | Ordering::Equal => {
19238 ranges_to_remove.next();
19239 }
19240 Ordering::Greater => {
19241 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19242 Ordering::Less | Ordering::Equal => {
19243 return false;
19244 }
19245 Ordering::Greater => break,
19246 }
19247 }
19248 }
19249 }
19250
19251 true
19252 })
19253 }
19254
19255 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19256 pub fn clear_row_highlights<T: 'static>(&mut self) {
19257 self.highlighted_rows.remove(&TypeId::of::<T>());
19258 }
19259
19260 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19261 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19262 self.highlighted_rows
19263 .get(&TypeId::of::<T>())
19264 .map_or(&[] as &[_], |vec| vec.as_slice())
19265 .iter()
19266 .map(|highlight| (highlight.range.clone(), highlight.color))
19267 }
19268
19269 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19270 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19271 /// Allows to ignore certain kinds of highlights.
19272 pub fn highlighted_display_rows(
19273 &self,
19274 window: &mut Window,
19275 cx: &mut App,
19276 ) -> BTreeMap<DisplayRow, LineHighlight> {
19277 let snapshot = self.snapshot(window, cx);
19278 let mut used_highlight_orders = HashMap::default();
19279 self.highlighted_rows
19280 .iter()
19281 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19282 .fold(
19283 BTreeMap::<DisplayRow, LineHighlight>::new(),
19284 |mut unique_rows, highlight| {
19285 let start = highlight.range.start.to_display_point(&snapshot);
19286 let end = highlight.range.end.to_display_point(&snapshot);
19287 let start_row = start.row().0;
19288 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19289 && end.column() == 0
19290 {
19291 end.row().0.saturating_sub(1)
19292 } else {
19293 end.row().0
19294 };
19295 for row in start_row..=end_row {
19296 let used_index =
19297 used_highlight_orders.entry(row).or_insert(highlight.index);
19298 if highlight.index >= *used_index {
19299 *used_index = highlight.index;
19300 unique_rows.insert(
19301 DisplayRow(row),
19302 LineHighlight {
19303 include_gutter: highlight.options.include_gutter,
19304 border: None,
19305 background: highlight.color.into(),
19306 type_id: Some(highlight.type_id),
19307 },
19308 );
19309 }
19310 }
19311 unique_rows
19312 },
19313 )
19314 }
19315
19316 pub fn highlighted_display_row_for_autoscroll(
19317 &self,
19318 snapshot: &DisplaySnapshot,
19319 ) -> Option<DisplayRow> {
19320 self.highlighted_rows
19321 .values()
19322 .flat_map(|highlighted_rows| highlighted_rows.iter())
19323 .filter_map(|highlight| {
19324 if highlight.options.autoscroll {
19325 Some(highlight.range.start.to_display_point(snapshot).row())
19326 } else {
19327 None
19328 }
19329 })
19330 .min()
19331 }
19332
19333 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19334 self.highlight_background::<SearchWithinRange>(
19335 ranges,
19336 |colors| colors.colors().editor_document_highlight_read_background,
19337 cx,
19338 )
19339 }
19340
19341 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19342 self.breadcrumb_header = Some(new_header);
19343 }
19344
19345 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19346 self.clear_background_highlights::<SearchWithinRange>(cx);
19347 }
19348
19349 pub fn highlight_background<T: 'static>(
19350 &mut self,
19351 ranges: &[Range<Anchor>],
19352 color_fetcher: fn(&Theme) -> Hsla,
19353 cx: &mut Context<Self>,
19354 ) {
19355 self.background_highlights.insert(
19356 HighlightKey::Type(TypeId::of::<T>()),
19357 (color_fetcher, Arc::from(ranges)),
19358 );
19359 self.scrollbar_marker_state.dirty = true;
19360 cx.notify();
19361 }
19362
19363 pub fn highlight_background_key<T: 'static>(
19364 &mut self,
19365 key: usize,
19366 ranges: &[Range<Anchor>],
19367 color_fetcher: fn(&Theme) -> Hsla,
19368 cx: &mut Context<Self>,
19369 ) {
19370 self.background_highlights.insert(
19371 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19372 (color_fetcher, Arc::from(ranges)),
19373 );
19374 self.scrollbar_marker_state.dirty = true;
19375 cx.notify();
19376 }
19377
19378 pub fn clear_background_highlights<T: 'static>(
19379 &mut self,
19380 cx: &mut Context<Self>,
19381 ) -> Option<BackgroundHighlight> {
19382 let text_highlights = self
19383 .background_highlights
19384 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19385 if !text_highlights.1.is_empty() {
19386 self.scrollbar_marker_state.dirty = true;
19387 cx.notify();
19388 }
19389 Some(text_highlights)
19390 }
19391
19392 pub fn highlight_gutter<T: 'static>(
19393 &mut self,
19394 ranges: impl Into<Vec<Range<Anchor>>>,
19395 color_fetcher: fn(&App) -> Hsla,
19396 cx: &mut Context<Self>,
19397 ) {
19398 self.gutter_highlights
19399 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19400 cx.notify();
19401 }
19402
19403 pub fn clear_gutter_highlights<T: 'static>(
19404 &mut self,
19405 cx: &mut Context<Self>,
19406 ) -> Option<GutterHighlight> {
19407 cx.notify();
19408 self.gutter_highlights.remove(&TypeId::of::<T>())
19409 }
19410
19411 pub fn insert_gutter_highlight<T: 'static>(
19412 &mut self,
19413 range: Range<Anchor>,
19414 color_fetcher: fn(&App) -> Hsla,
19415 cx: &mut Context<Self>,
19416 ) {
19417 let snapshot = self.buffer().read(cx).snapshot(cx);
19418 let mut highlights = self
19419 .gutter_highlights
19420 .remove(&TypeId::of::<T>())
19421 .map(|(_, highlights)| highlights)
19422 .unwrap_or_default();
19423 let ix = highlights.binary_search_by(|highlight| {
19424 Ordering::Equal
19425 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19426 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19427 });
19428 if let Err(ix) = ix {
19429 highlights.insert(ix, range);
19430 }
19431 self.gutter_highlights
19432 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19433 }
19434
19435 pub fn remove_gutter_highlights<T: 'static>(
19436 &mut self,
19437 ranges_to_remove: Vec<Range<Anchor>>,
19438 cx: &mut Context<Self>,
19439 ) {
19440 let snapshot = self.buffer().read(cx).snapshot(cx);
19441 let Some((color_fetcher, mut gutter_highlights)) =
19442 self.gutter_highlights.remove(&TypeId::of::<T>())
19443 else {
19444 return;
19445 };
19446 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19447 gutter_highlights.retain(|highlight| {
19448 while let Some(range_to_remove) = ranges_to_remove.peek() {
19449 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19450 Ordering::Less | Ordering::Equal => {
19451 ranges_to_remove.next();
19452 }
19453 Ordering::Greater => {
19454 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19455 Ordering::Less | Ordering::Equal => {
19456 return false;
19457 }
19458 Ordering::Greater => break,
19459 }
19460 }
19461 }
19462 }
19463
19464 true
19465 });
19466 self.gutter_highlights
19467 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19468 }
19469
19470 #[cfg(feature = "test-support")]
19471 pub fn all_text_highlights(
19472 &self,
19473 window: &mut Window,
19474 cx: &mut Context<Self>,
19475 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19476 let snapshot = self.snapshot(window, cx);
19477 self.display_map.update(cx, |display_map, _| {
19478 display_map
19479 .all_text_highlights()
19480 .map(|highlight| {
19481 let (style, ranges) = highlight.as_ref();
19482 (
19483 *style,
19484 ranges
19485 .iter()
19486 .map(|range| range.clone().to_display_points(&snapshot))
19487 .collect(),
19488 )
19489 })
19490 .collect()
19491 })
19492 }
19493
19494 #[cfg(feature = "test-support")]
19495 pub fn all_text_background_highlights(
19496 &self,
19497 window: &mut Window,
19498 cx: &mut Context<Self>,
19499 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19500 let snapshot = self.snapshot(window, cx);
19501 let buffer = &snapshot.buffer_snapshot;
19502 let start = buffer.anchor_before(0);
19503 let end = buffer.anchor_after(buffer.len());
19504 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19505 }
19506
19507 #[cfg(feature = "test-support")]
19508 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19509 let snapshot = self.buffer().read(cx).snapshot(cx);
19510
19511 let highlights = self
19512 .background_highlights
19513 .get(&HighlightKey::Type(TypeId::of::<
19514 items::BufferSearchHighlights,
19515 >()));
19516
19517 if let Some((_color, ranges)) = highlights {
19518 ranges
19519 .iter()
19520 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19521 .collect_vec()
19522 } else {
19523 vec![]
19524 }
19525 }
19526
19527 fn document_highlights_for_position<'a>(
19528 &'a self,
19529 position: Anchor,
19530 buffer: &'a MultiBufferSnapshot,
19531 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19532 let read_highlights = self
19533 .background_highlights
19534 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19535 .map(|h| &h.1);
19536 let write_highlights = self
19537 .background_highlights
19538 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19539 .map(|h| &h.1);
19540 let left_position = position.bias_left(buffer);
19541 let right_position = position.bias_right(buffer);
19542 read_highlights
19543 .into_iter()
19544 .chain(write_highlights)
19545 .flat_map(move |ranges| {
19546 let start_ix = match ranges.binary_search_by(|probe| {
19547 let cmp = probe.end.cmp(&left_position, buffer);
19548 if cmp.is_ge() {
19549 Ordering::Greater
19550 } else {
19551 Ordering::Less
19552 }
19553 }) {
19554 Ok(i) | Err(i) => i,
19555 };
19556
19557 ranges[start_ix..]
19558 .iter()
19559 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19560 })
19561 }
19562
19563 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19564 self.background_highlights
19565 .get(&HighlightKey::Type(TypeId::of::<T>()))
19566 .is_some_and(|(_, highlights)| !highlights.is_empty())
19567 }
19568
19569 pub fn background_highlights_in_range(
19570 &self,
19571 search_range: Range<Anchor>,
19572 display_snapshot: &DisplaySnapshot,
19573 theme: &Theme,
19574 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19575 let mut results = Vec::new();
19576 for (color_fetcher, ranges) in self.background_highlights.values() {
19577 let color = color_fetcher(theme);
19578 let start_ix = match ranges.binary_search_by(|probe| {
19579 let cmp = probe
19580 .end
19581 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19582 if cmp.is_gt() {
19583 Ordering::Greater
19584 } else {
19585 Ordering::Less
19586 }
19587 }) {
19588 Ok(i) | Err(i) => i,
19589 };
19590 for range in &ranges[start_ix..] {
19591 if range
19592 .start
19593 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19594 .is_ge()
19595 {
19596 break;
19597 }
19598
19599 let start = range.start.to_display_point(display_snapshot);
19600 let end = range.end.to_display_point(display_snapshot);
19601 results.push((start..end, color))
19602 }
19603 }
19604 results
19605 }
19606
19607 pub fn background_highlight_row_ranges<T: 'static>(
19608 &self,
19609 search_range: Range<Anchor>,
19610 display_snapshot: &DisplaySnapshot,
19611 count: usize,
19612 ) -> Vec<RangeInclusive<DisplayPoint>> {
19613 let mut results = Vec::new();
19614 let Some((_, ranges)) = self
19615 .background_highlights
19616 .get(&HighlightKey::Type(TypeId::of::<T>()))
19617 else {
19618 return vec![];
19619 };
19620
19621 let start_ix = match ranges.binary_search_by(|probe| {
19622 let cmp = probe
19623 .end
19624 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19625 if cmp.is_gt() {
19626 Ordering::Greater
19627 } else {
19628 Ordering::Less
19629 }
19630 }) {
19631 Ok(i) | Err(i) => i,
19632 };
19633 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19634 if let (Some(start_display), Some(end_display)) = (start, end) {
19635 results.push(
19636 start_display.to_display_point(display_snapshot)
19637 ..=end_display.to_display_point(display_snapshot),
19638 );
19639 }
19640 };
19641 let mut start_row: Option<Point> = None;
19642 let mut end_row: Option<Point> = None;
19643 if ranges.len() > count {
19644 return Vec::new();
19645 }
19646 for range in &ranges[start_ix..] {
19647 if range
19648 .start
19649 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19650 .is_ge()
19651 {
19652 break;
19653 }
19654 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19655 if let Some(current_row) = &end_row
19656 && end.row == current_row.row
19657 {
19658 continue;
19659 }
19660 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19661 if start_row.is_none() {
19662 assert_eq!(end_row, None);
19663 start_row = Some(start);
19664 end_row = Some(end);
19665 continue;
19666 }
19667 if let Some(current_end) = end_row.as_mut() {
19668 if start.row > current_end.row + 1 {
19669 push_region(start_row, end_row);
19670 start_row = Some(start);
19671 end_row = Some(end);
19672 } else {
19673 // Merge two hunks.
19674 *current_end = end;
19675 }
19676 } else {
19677 unreachable!();
19678 }
19679 }
19680 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19681 push_region(start_row, end_row);
19682 results
19683 }
19684
19685 pub fn gutter_highlights_in_range(
19686 &self,
19687 search_range: Range<Anchor>,
19688 display_snapshot: &DisplaySnapshot,
19689 cx: &App,
19690 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19691 let mut results = Vec::new();
19692 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19693 let color = color_fetcher(cx);
19694 let start_ix = match ranges.binary_search_by(|probe| {
19695 let cmp = probe
19696 .end
19697 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19698 if cmp.is_gt() {
19699 Ordering::Greater
19700 } else {
19701 Ordering::Less
19702 }
19703 }) {
19704 Ok(i) | Err(i) => i,
19705 };
19706 for range in &ranges[start_ix..] {
19707 if range
19708 .start
19709 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19710 .is_ge()
19711 {
19712 break;
19713 }
19714
19715 let start = range.start.to_display_point(display_snapshot);
19716 let end = range.end.to_display_point(display_snapshot);
19717 results.push((start..end, color))
19718 }
19719 }
19720 results
19721 }
19722
19723 /// Get the text ranges corresponding to the redaction query
19724 pub fn redacted_ranges(
19725 &self,
19726 search_range: Range<Anchor>,
19727 display_snapshot: &DisplaySnapshot,
19728 cx: &App,
19729 ) -> Vec<Range<DisplayPoint>> {
19730 display_snapshot
19731 .buffer_snapshot
19732 .redacted_ranges(search_range, |file| {
19733 if let Some(file) = file {
19734 file.is_private()
19735 && EditorSettings::get(
19736 Some(SettingsLocation {
19737 worktree_id: file.worktree_id(cx),
19738 path: file.path().as_ref(),
19739 }),
19740 cx,
19741 )
19742 .redact_private_values
19743 } else {
19744 false
19745 }
19746 })
19747 .map(|range| {
19748 range.start.to_display_point(display_snapshot)
19749 ..range.end.to_display_point(display_snapshot)
19750 })
19751 .collect()
19752 }
19753
19754 pub fn highlight_text_key<T: 'static>(
19755 &mut self,
19756 key: usize,
19757 ranges: Vec<Range<Anchor>>,
19758 style: HighlightStyle,
19759 cx: &mut Context<Self>,
19760 ) {
19761 self.display_map.update(cx, |map, _| {
19762 map.highlight_text(
19763 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19764 ranges,
19765 style,
19766 );
19767 });
19768 cx.notify();
19769 }
19770
19771 pub fn highlight_text<T: 'static>(
19772 &mut self,
19773 ranges: Vec<Range<Anchor>>,
19774 style: HighlightStyle,
19775 cx: &mut Context<Self>,
19776 ) {
19777 self.display_map.update(cx, |map, _| {
19778 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19779 });
19780 cx.notify();
19781 }
19782
19783 pub(crate) fn highlight_inlays<T: 'static>(
19784 &mut self,
19785 highlights: Vec<InlayHighlight>,
19786 style: HighlightStyle,
19787 cx: &mut Context<Self>,
19788 ) {
19789 self.display_map.update(cx, |map, _| {
19790 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19791 });
19792 cx.notify();
19793 }
19794
19795 pub fn text_highlights<'a, T: 'static>(
19796 &'a self,
19797 cx: &'a App,
19798 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19799 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19800 }
19801
19802 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19803 let cleared = self
19804 .display_map
19805 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19806 if cleared {
19807 cx.notify();
19808 }
19809 }
19810
19811 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19812 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19813 && self.focus_handle.is_focused(window)
19814 }
19815
19816 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19817 self.show_cursor_when_unfocused = is_enabled;
19818 cx.notify();
19819 }
19820
19821 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19822 cx.notify();
19823 }
19824
19825 fn on_debug_session_event(
19826 &mut self,
19827 _session: Entity<Session>,
19828 event: &SessionEvent,
19829 cx: &mut Context<Self>,
19830 ) {
19831 if let SessionEvent::InvalidateInlineValue = event {
19832 self.refresh_inline_values(cx);
19833 }
19834 }
19835
19836 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19837 let Some(project) = self.project.clone() else {
19838 return;
19839 };
19840
19841 if !self.inline_value_cache.enabled {
19842 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19843 self.splice_inlays(&inlays, Vec::new(), cx);
19844 return;
19845 }
19846
19847 let current_execution_position = self
19848 .highlighted_rows
19849 .get(&TypeId::of::<ActiveDebugLine>())
19850 .and_then(|lines| lines.last().map(|line| line.range.end));
19851
19852 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19853 let inline_values = editor
19854 .update(cx, |editor, cx| {
19855 let Some(current_execution_position) = current_execution_position else {
19856 return Some(Task::ready(Ok(Vec::new())));
19857 };
19858
19859 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19860 let snapshot = buffer.snapshot(cx);
19861
19862 let excerpt = snapshot.excerpt_containing(
19863 current_execution_position..current_execution_position,
19864 )?;
19865
19866 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19867 })?;
19868
19869 let range =
19870 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19871
19872 project.inline_values(buffer, range, cx)
19873 })
19874 .ok()
19875 .flatten()?
19876 .await
19877 .context("refreshing debugger inlays")
19878 .log_err()?;
19879
19880 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19881
19882 for (buffer_id, inline_value) in inline_values
19883 .into_iter()
19884 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19885 {
19886 buffer_inline_values
19887 .entry(buffer_id)
19888 .or_default()
19889 .push(inline_value);
19890 }
19891
19892 editor
19893 .update(cx, |editor, cx| {
19894 let snapshot = editor.buffer.read(cx).snapshot(cx);
19895 let mut new_inlays = Vec::default();
19896
19897 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19898 let buffer_id = buffer_snapshot.remote_id();
19899 buffer_inline_values
19900 .get(&buffer_id)
19901 .into_iter()
19902 .flatten()
19903 .for_each(|hint| {
19904 let inlay = Inlay::debugger(
19905 post_inc(&mut editor.next_inlay_id),
19906 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19907 hint.text(),
19908 );
19909 if !inlay.text.chars().contains(&'\n') {
19910 new_inlays.push(inlay);
19911 }
19912 });
19913 }
19914
19915 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19916 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19917
19918 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19919 })
19920 .ok()?;
19921 Some(())
19922 });
19923 }
19924
19925 fn on_buffer_event(
19926 &mut self,
19927 multibuffer: &Entity<MultiBuffer>,
19928 event: &multi_buffer::Event,
19929 window: &mut Window,
19930 cx: &mut Context<Self>,
19931 ) {
19932 match event {
19933 multi_buffer::Event::Edited {
19934 singleton_buffer_edited,
19935 edited_buffer,
19936 } => {
19937 self.scrollbar_marker_state.dirty = true;
19938 self.active_indent_guides_state.dirty = true;
19939 self.refresh_active_diagnostics(cx);
19940 self.refresh_code_actions(window, cx);
19941 self.refresh_selected_text_highlights(true, window, cx);
19942 self.refresh_single_line_folds(window, cx);
19943 refresh_matching_bracket_highlights(self, window, cx);
19944 if self.has_active_edit_prediction() {
19945 self.update_visible_edit_prediction(window, cx);
19946 }
19947 if let Some(project) = self.project.as_ref()
19948 && let Some(edited_buffer) = edited_buffer
19949 {
19950 project.update(cx, |project, cx| {
19951 self.registered_buffers
19952 .entry(edited_buffer.read(cx).remote_id())
19953 .or_insert_with(|| {
19954 project.register_buffer_with_language_servers(edited_buffer, cx)
19955 });
19956 });
19957 }
19958 cx.emit(EditorEvent::BufferEdited);
19959 cx.emit(SearchEvent::MatchesInvalidated);
19960
19961 if let Some(buffer) = edited_buffer {
19962 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19963 }
19964
19965 if *singleton_buffer_edited {
19966 if let Some(buffer) = edited_buffer
19967 && buffer.read(cx).file().is_none()
19968 {
19969 cx.emit(EditorEvent::TitleChanged);
19970 }
19971 if let Some(project) = &self.project {
19972 #[allow(clippy::mutable_key_type)]
19973 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19974 multibuffer
19975 .all_buffers()
19976 .into_iter()
19977 .filter_map(|buffer| {
19978 buffer.update(cx, |buffer, cx| {
19979 let language = buffer.language()?;
19980 let should_discard = project.update(cx, |project, cx| {
19981 project.is_local()
19982 && !project.has_language_servers_for(buffer, cx)
19983 });
19984 should_discard.not().then_some(language.clone())
19985 })
19986 })
19987 .collect::<HashSet<_>>()
19988 });
19989 if !languages_affected.is_empty() {
19990 self.refresh_inlay_hints(
19991 InlayHintRefreshReason::BufferEdited(languages_affected),
19992 cx,
19993 );
19994 }
19995 }
19996 }
19997
19998 let Some(project) = &self.project else { return };
19999 let (telemetry, is_via_ssh) = {
20000 let project = project.read(cx);
20001 let telemetry = project.client().telemetry().clone();
20002 let is_via_ssh = project.is_via_ssh();
20003 (telemetry, is_via_ssh)
20004 };
20005 refresh_linked_ranges(self, window, cx);
20006 telemetry.log_edit_event("editor", is_via_ssh);
20007 }
20008 multi_buffer::Event::ExcerptsAdded {
20009 buffer,
20010 predecessor,
20011 excerpts,
20012 } => {
20013 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20014 let buffer_id = buffer.read(cx).remote_id();
20015 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20016 && let Some(project) = &self.project
20017 {
20018 update_uncommitted_diff_for_buffer(
20019 cx.entity(),
20020 project,
20021 [buffer.clone()],
20022 self.buffer.clone(),
20023 cx,
20024 )
20025 .detach();
20026 }
20027 self.update_lsp_data(false, Some(buffer_id), window, cx);
20028 cx.emit(EditorEvent::ExcerptsAdded {
20029 buffer: buffer.clone(),
20030 predecessor: *predecessor,
20031 excerpts: excerpts.clone(),
20032 });
20033 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20034 }
20035 multi_buffer::Event::ExcerptsRemoved {
20036 ids,
20037 removed_buffer_ids,
20038 } => {
20039 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20040 let buffer = self.buffer.read(cx);
20041 self.registered_buffers
20042 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20043 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20044 cx.emit(EditorEvent::ExcerptsRemoved {
20045 ids: ids.clone(),
20046 removed_buffer_ids: removed_buffer_ids.clone(),
20047 });
20048 }
20049 multi_buffer::Event::ExcerptsEdited {
20050 excerpt_ids,
20051 buffer_ids,
20052 } => {
20053 self.display_map.update(cx, |map, cx| {
20054 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20055 });
20056 cx.emit(EditorEvent::ExcerptsEdited {
20057 ids: excerpt_ids.clone(),
20058 });
20059 }
20060 multi_buffer::Event::ExcerptsExpanded { ids } => {
20061 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20062 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20063 }
20064 multi_buffer::Event::Reparsed(buffer_id) => {
20065 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20066 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20067
20068 cx.emit(EditorEvent::Reparsed(*buffer_id));
20069 }
20070 multi_buffer::Event::DiffHunksToggled => {
20071 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20072 }
20073 multi_buffer::Event::LanguageChanged(buffer_id) => {
20074 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20075 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20076 cx.emit(EditorEvent::Reparsed(*buffer_id));
20077 cx.notify();
20078 }
20079 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20080 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20081 multi_buffer::Event::FileHandleChanged
20082 | multi_buffer::Event::Reloaded
20083 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20084 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20085 multi_buffer::Event::DiagnosticsUpdated => {
20086 self.update_diagnostics_state(window, cx);
20087 }
20088 _ => {}
20089 };
20090 }
20091
20092 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20093 if !self.diagnostics_enabled() {
20094 return;
20095 }
20096 self.refresh_active_diagnostics(cx);
20097 self.refresh_inline_diagnostics(true, window, cx);
20098 self.scrollbar_marker_state.dirty = true;
20099 cx.notify();
20100 }
20101
20102 pub fn start_temporary_diff_override(&mut self) {
20103 self.load_diff_task.take();
20104 self.temporary_diff_override = true;
20105 }
20106
20107 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20108 self.temporary_diff_override = false;
20109 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20110 self.buffer.update(cx, |buffer, cx| {
20111 buffer.set_all_diff_hunks_collapsed(cx);
20112 });
20113
20114 if let Some(project) = self.project.clone() {
20115 self.load_diff_task = Some(
20116 update_uncommitted_diff_for_buffer(
20117 cx.entity(),
20118 &project,
20119 self.buffer.read(cx).all_buffers(),
20120 self.buffer.clone(),
20121 cx,
20122 )
20123 .shared(),
20124 );
20125 }
20126 }
20127
20128 fn on_display_map_changed(
20129 &mut self,
20130 _: Entity<DisplayMap>,
20131 _: &mut Window,
20132 cx: &mut Context<Self>,
20133 ) {
20134 cx.notify();
20135 }
20136
20137 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20138 if self.diagnostics_enabled() {
20139 let new_severity = EditorSettings::get_global(cx)
20140 .diagnostics_max_severity
20141 .unwrap_or(DiagnosticSeverity::Hint);
20142 self.set_max_diagnostics_severity(new_severity, cx);
20143 }
20144 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20145 self.update_edit_prediction_settings(cx);
20146 self.refresh_edit_prediction(true, false, window, cx);
20147 self.refresh_inline_values(cx);
20148 self.refresh_inlay_hints(
20149 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20150 self.selections.newest_anchor().head(),
20151 &self.buffer.read(cx).snapshot(cx),
20152 cx,
20153 )),
20154 cx,
20155 );
20156
20157 let old_cursor_shape = self.cursor_shape;
20158 let old_show_breadcrumbs = self.show_breadcrumbs;
20159
20160 {
20161 let editor_settings = EditorSettings::get_global(cx);
20162 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20163 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20164 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20165 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20166 }
20167
20168 if old_cursor_shape != self.cursor_shape {
20169 cx.emit(EditorEvent::CursorShapeChanged);
20170 }
20171
20172 if old_show_breadcrumbs != self.show_breadcrumbs {
20173 cx.emit(EditorEvent::BreadcrumbsChanged);
20174 }
20175
20176 let project_settings = ProjectSettings::get_global(cx);
20177 self.serialize_dirty_buffers =
20178 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20179
20180 if self.mode.is_full() {
20181 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20182 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20183 if self.show_inline_diagnostics != show_inline_diagnostics {
20184 self.show_inline_diagnostics = show_inline_diagnostics;
20185 self.refresh_inline_diagnostics(false, window, cx);
20186 }
20187
20188 if self.git_blame_inline_enabled != inline_blame_enabled {
20189 self.toggle_git_blame_inline_internal(false, window, cx);
20190 }
20191
20192 let minimap_settings = EditorSettings::get_global(cx).minimap;
20193 if self.minimap_visibility != MinimapVisibility::Disabled {
20194 if self.minimap_visibility.settings_visibility()
20195 != minimap_settings.minimap_enabled()
20196 {
20197 self.set_minimap_visibility(
20198 MinimapVisibility::for_mode(self.mode(), cx),
20199 window,
20200 cx,
20201 );
20202 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20203 minimap_entity.update(cx, |minimap_editor, cx| {
20204 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20205 })
20206 }
20207 }
20208 }
20209
20210 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20211 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20212 }) {
20213 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20214 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20215 }
20216 self.refresh_colors(false, None, window, cx);
20217 }
20218
20219 cx.notify();
20220 }
20221
20222 pub fn set_searchable(&mut self, searchable: bool) {
20223 self.searchable = searchable;
20224 }
20225
20226 pub fn searchable(&self) -> bool {
20227 self.searchable
20228 }
20229
20230 fn open_proposed_changes_editor(
20231 &mut self,
20232 _: &OpenProposedChangesEditor,
20233 window: &mut Window,
20234 cx: &mut Context<Self>,
20235 ) {
20236 let Some(workspace) = self.workspace() else {
20237 cx.propagate();
20238 return;
20239 };
20240
20241 let selections = self.selections.all::<usize>(cx);
20242 let multi_buffer = self.buffer.read(cx);
20243 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20244 let mut new_selections_by_buffer = HashMap::default();
20245 for selection in selections {
20246 for (buffer, range, _) in
20247 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20248 {
20249 let mut range = range.to_point(buffer);
20250 range.start.column = 0;
20251 range.end.column = buffer.line_len(range.end.row);
20252 new_selections_by_buffer
20253 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20254 .or_insert(Vec::new())
20255 .push(range)
20256 }
20257 }
20258
20259 let proposed_changes_buffers = new_selections_by_buffer
20260 .into_iter()
20261 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20262 .collect::<Vec<_>>();
20263 let proposed_changes_editor = cx.new(|cx| {
20264 ProposedChangesEditor::new(
20265 "Proposed changes",
20266 proposed_changes_buffers,
20267 self.project.clone(),
20268 window,
20269 cx,
20270 )
20271 });
20272
20273 window.defer(cx, move |window, cx| {
20274 workspace.update(cx, |workspace, cx| {
20275 workspace.active_pane().update(cx, |pane, cx| {
20276 pane.add_item(
20277 Box::new(proposed_changes_editor),
20278 true,
20279 true,
20280 None,
20281 window,
20282 cx,
20283 );
20284 });
20285 });
20286 });
20287 }
20288
20289 pub fn open_excerpts_in_split(
20290 &mut self,
20291 _: &OpenExcerptsSplit,
20292 window: &mut Window,
20293 cx: &mut Context<Self>,
20294 ) {
20295 self.open_excerpts_common(None, true, window, cx)
20296 }
20297
20298 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20299 self.open_excerpts_common(None, false, window, cx)
20300 }
20301
20302 fn open_excerpts_common(
20303 &mut self,
20304 jump_data: Option<JumpData>,
20305 split: bool,
20306 window: &mut Window,
20307 cx: &mut Context<Self>,
20308 ) {
20309 let Some(workspace) = self.workspace() else {
20310 cx.propagate();
20311 return;
20312 };
20313
20314 if self.buffer.read(cx).is_singleton() {
20315 cx.propagate();
20316 return;
20317 }
20318
20319 let mut new_selections_by_buffer = HashMap::default();
20320 match &jump_data {
20321 Some(JumpData::MultiBufferPoint {
20322 excerpt_id,
20323 position,
20324 anchor,
20325 line_offset_from_top,
20326 }) => {
20327 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20328 if let Some(buffer) = multi_buffer_snapshot
20329 .buffer_id_for_excerpt(*excerpt_id)
20330 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20331 {
20332 let buffer_snapshot = buffer.read(cx).snapshot();
20333 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20334 language::ToPoint::to_point(anchor, &buffer_snapshot)
20335 } else {
20336 buffer_snapshot.clip_point(*position, Bias::Left)
20337 };
20338 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20339 new_selections_by_buffer.insert(
20340 buffer,
20341 (
20342 vec![jump_to_offset..jump_to_offset],
20343 Some(*line_offset_from_top),
20344 ),
20345 );
20346 }
20347 }
20348 Some(JumpData::MultiBufferRow {
20349 row,
20350 line_offset_from_top,
20351 }) => {
20352 let point = MultiBufferPoint::new(row.0, 0);
20353 if let Some((buffer, buffer_point, _)) =
20354 self.buffer.read(cx).point_to_buffer_point(point, cx)
20355 {
20356 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20357 new_selections_by_buffer
20358 .entry(buffer)
20359 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20360 .0
20361 .push(buffer_offset..buffer_offset)
20362 }
20363 }
20364 None => {
20365 let selections = self.selections.all::<usize>(cx);
20366 let multi_buffer = self.buffer.read(cx);
20367 for selection in selections {
20368 for (snapshot, range, _, anchor) in multi_buffer
20369 .snapshot(cx)
20370 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20371 {
20372 if let Some(anchor) = anchor {
20373 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20374 else {
20375 continue;
20376 };
20377 let offset = text::ToOffset::to_offset(
20378 &anchor.text_anchor,
20379 &buffer_handle.read(cx).snapshot(),
20380 );
20381 let range = offset..offset;
20382 new_selections_by_buffer
20383 .entry(buffer_handle)
20384 .or_insert((Vec::new(), None))
20385 .0
20386 .push(range)
20387 } else {
20388 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20389 else {
20390 continue;
20391 };
20392 new_selections_by_buffer
20393 .entry(buffer_handle)
20394 .or_insert((Vec::new(), None))
20395 .0
20396 .push(range)
20397 }
20398 }
20399 }
20400 }
20401 }
20402
20403 new_selections_by_buffer
20404 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20405
20406 if new_selections_by_buffer.is_empty() {
20407 return;
20408 }
20409
20410 // We defer the pane interaction because we ourselves are a workspace item
20411 // and activating a new item causes the pane to call a method on us reentrantly,
20412 // which panics if we're on the stack.
20413 window.defer(cx, move |window, cx| {
20414 workspace.update(cx, |workspace, cx| {
20415 let pane = if split {
20416 workspace.adjacent_pane(window, cx)
20417 } else {
20418 workspace.active_pane().clone()
20419 };
20420
20421 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20422 let editor = buffer
20423 .read(cx)
20424 .file()
20425 .is_none()
20426 .then(|| {
20427 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20428 // so `workspace.open_project_item` will never find them, always opening a new editor.
20429 // Instead, we try to activate the existing editor in the pane first.
20430 let (editor, pane_item_index) =
20431 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20432 let editor = item.downcast::<Editor>()?;
20433 let singleton_buffer =
20434 editor.read(cx).buffer().read(cx).as_singleton()?;
20435 if singleton_buffer == buffer {
20436 Some((editor, i))
20437 } else {
20438 None
20439 }
20440 })?;
20441 pane.update(cx, |pane, cx| {
20442 pane.activate_item(pane_item_index, true, true, window, cx)
20443 });
20444 Some(editor)
20445 })
20446 .flatten()
20447 .unwrap_or_else(|| {
20448 workspace.open_project_item::<Self>(
20449 pane.clone(),
20450 buffer,
20451 true,
20452 true,
20453 window,
20454 cx,
20455 )
20456 });
20457
20458 editor.update(cx, |editor, cx| {
20459 let autoscroll = match scroll_offset {
20460 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20461 None => Autoscroll::newest(),
20462 };
20463 let nav_history = editor.nav_history.take();
20464 editor.change_selections(
20465 SelectionEffects::scroll(autoscroll),
20466 window,
20467 cx,
20468 |s| {
20469 s.select_ranges(ranges);
20470 },
20471 );
20472 editor.nav_history = nav_history;
20473 });
20474 }
20475 })
20476 });
20477 }
20478
20479 // For now, don't allow opening excerpts in buffers that aren't backed by
20480 // regular project files.
20481 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20482 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20483 }
20484
20485 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20486 let snapshot = self.buffer.read(cx).read(cx);
20487 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20488 Some(
20489 ranges
20490 .iter()
20491 .map(move |range| {
20492 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20493 })
20494 .collect(),
20495 )
20496 }
20497
20498 fn selection_replacement_ranges(
20499 &self,
20500 range: Range<OffsetUtf16>,
20501 cx: &mut App,
20502 ) -> Vec<Range<OffsetUtf16>> {
20503 let selections = self.selections.all::<OffsetUtf16>(cx);
20504 let newest_selection = selections
20505 .iter()
20506 .max_by_key(|selection| selection.id)
20507 .unwrap();
20508 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20509 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20510 let snapshot = self.buffer.read(cx).read(cx);
20511 selections
20512 .into_iter()
20513 .map(|mut selection| {
20514 selection.start.0 =
20515 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20516 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20517 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20518 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20519 })
20520 .collect()
20521 }
20522
20523 fn report_editor_event(
20524 &self,
20525 reported_event: ReportEditorEvent,
20526 file_extension: Option<String>,
20527 cx: &App,
20528 ) {
20529 if cfg!(any(test, feature = "test-support")) {
20530 return;
20531 }
20532
20533 let Some(project) = &self.project else { return };
20534
20535 // If None, we are in a file without an extension
20536 let file = self
20537 .buffer
20538 .read(cx)
20539 .as_singleton()
20540 .and_then(|b| b.read(cx).file());
20541 let file_extension = file_extension.or(file
20542 .as_ref()
20543 .and_then(|file| Path::new(file.file_name(cx)).extension())
20544 .and_then(|e| e.to_str())
20545 .map(|a| a.to_string()));
20546
20547 let vim_mode = vim_enabled(cx);
20548
20549 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20550 let copilot_enabled = edit_predictions_provider
20551 == language::language_settings::EditPredictionProvider::Copilot;
20552 let copilot_enabled_for_language = self
20553 .buffer
20554 .read(cx)
20555 .language_settings(cx)
20556 .show_edit_predictions;
20557
20558 let project = project.read(cx);
20559 let event_type = reported_event.event_type();
20560
20561 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20562 telemetry::event!(
20563 event_type,
20564 type = if auto_saved {"autosave"} else {"manual"},
20565 file_extension,
20566 vim_mode,
20567 copilot_enabled,
20568 copilot_enabled_for_language,
20569 edit_predictions_provider,
20570 is_via_ssh = project.is_via_ssh(),
20571 );
20572 } else {
20573 telemetry::event!(
20574 event_type,
20575 file_extension,
20576 vim_mode,
20577 copilot_enabled,
20578 copilot_enabled_for_language,
20579 edit_predictions_provider,
20580 is_via_ssh = project.is_via_ssh(),
20581 );
20582 };
20583 }
20584
20585 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20586 /// with each line being an array of {text, highlight} objects.
20587 fn copy_highlight_json(
20588 &mut self,
20589 _: &CopyHighlightJson,
20590 window: &mut Window,
20591 cx: &mut Context<Self>,
20592 ) {
20593 #[derive(Serialize)]
20594 struct Chunk<'a> {
20595 text: String,
20596 highlight: Option<&'a str>,
20597 }
20598
20599 let snapshot = self.buffer.read(cx).snapshot(cx);
20600 let range = self
20601 .selected_text_range(false, window, cx)
20602 .and_then(|selection| {
20603 if selection.range.is_empty() {
20604 None
20605 } else {
20606 Some(selection.range)
20607 }
20608 })
20609 .unwrap_or_else(|| 0..snapshot.len());
20610
20611 let chunks = snapshot.chunks(range, true);
20612 let mut lines = Vec::new();
20613 let mut line: VecDeque<Chunk> = VecDeque::new();
20614
20615 let Some(style) = self.style.as_ref() else {
20616 return;
20617 };
20618
20619 for chunk in chunks {
20620 let highlight = chunk
20621 .syntax_highlight_id
20622 .and_then(|id| id.name(&style.syntax));
20623 let mut chunk_lines = chunk.text.split('\n').peekable();
20624 while let Some(text) = chunk_lines.next() {
20625 let mut merged_with_last_token = false;
20626 if let Some(last_token) = line.back_mut()
20627 && last_token.highlight == highlight
20628 {
20629 last_token.text.push_str(text);
20630 merged_with_last_token = true;
20631 }
20632
20633 if !merged_with_last_token {
20634 line.push_back(Chunk {
20635 text: text.into(),
20636 highlight,
20637 });
20638 }
20639
20640 if chunk_lines.peek().is_some() {
20641 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20642 line.pop_front();
20643 }
20644 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20645 line.pop_back();
20646 }
20647
20648 lines.push(mem::take(&mut line));
20649 }
20650 }
20651 }
20652
20653 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20654 return;
20655 };
20656 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20657 }
20658
20659 pub fn open_context_menu(
20660 &mut self,
20661 _: &OpenContextMenu,
20662 window: &mut Window,
20663 cx: &mut Context<Self>,
20664 ) {
20665 self.request_autoscroll(Autoscroll::newest(), cx);
20666 let position = self.selections.newest_display(cx).start;
20667 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20668 }
20669
20670 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20671 &self.inlay_hint_cache
20672 }
20673
20674 pub fn replay_insert_event(
20675 &mut self,
20676 text: &str,
20677 relative_utf16_range: Option<Range<isize>>,
20678 window: &mut Window,
20679 cx: &mut Context<Self>,
20680 ) {
20681 if !self.input_enabled {
20682 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20683 return;
20684 }
20685 if let Some(relative_utf16_range) = relative_utf16_range {
20686 let selections = self.selections.all::<OffsetUtf16>(cx);
20687 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20688 let new_ranges = selections.into_iter().map(|range| {
20689 let start = OffsetUtf16(
20690 range
20691 .head()
20692 .0
20693 .saturating_add_signed(relative_utf16_range.start),
20694 );
20695 let end = OffsetUtf16(
20696 range
20697 .head()
20698 .0
20699 .saturating_add_signed(relative_utf16_range.end),
20700 );
20701 start..end
20702 });
20703 s.select_ranges(new_ranges);
20704 });
20705 }
20706
20707 self.handle_input(text, window, cx);
20708 }
20709
20710 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20711 let Some(provider) = self.semantics_provider.as_ref() else {
20712 return false;
20713 };
20714
20715 let mut supports = false;
20716 self.buffer().update(cx, |this, cx| {
20717 this.for_each_buffer(|buffer| {
20718 supports |= provider.supports_inlay_hints(buffer, cx);
20719 });
20720 });
20721
20722 supports
20723 }
20724
20725 pub fn is_focused(&self, window: &Window) -> bool {
20726 self.focus_handle.is_focused(window)
20727 }
20728
20729 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20730 cx.emit(EditorEvent::Focused);
20731
20732 if let Some(descendant) = self
20733 .last_focused_descendant
20734 .take()
20735 .and_then(|descendant| descendant.upgrade())
20736 {
20737 window.focus(&descendant);
20738 } else {
20739 if let Some(blame) = self.blame.as_ref() {
20740 blame.update(cx, GitBlame::focus)
20741 }
20742
20743 self.blink_manager.update(cx, BlinkManager::enable);
20744 self.show_cursor_names(window, cx);
20745 self.buffer.update(cx, |buffer, cx| {
20746 buffer.finalize_last_transaction(cx);
20747 if self.leader_id.is_none() {
20748 buffer.set_active_selections(
20749 &self.selections.disjoint_anchors(),
20750 self.selections.line_mode,
20751 self.cursor_shape,
20752 cx,
20753 );
20754 }
20755 });
20756 }
20757 }
20758
20759 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20760 cx.emit(EditorEvent::FocusedIn)
20761 }
20762
20763 fn handle_focus_out(
20764 &mut self,
20765 event: FocusOutEvent,
20766 _window: &mut Window,
20767 cx: &mut Context<Self>,
20768 ) {
20769 if event.blurred != self.focus_handle {
20770 self.last_focused_descendant = Some(event.blurred);
20771 }
20772 self.selection_drag_state = SelectionDragState::None;
20773 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20774 }
20775
20776 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20777 self.blink_manager.update(cx, BlinkManager::disable);
20778 self.buffer
20779 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20780
20781 if let Some(blame) = self.blame.as_ref() {
20782 blame.update(cx, GitBlame::blur)
20783 }
20784 if !self.hover_state.focused(window, cx) {
20785 hide_hover(self, cx);
20786 }
20787 if !self
20788 .context_menu
20789 .borrow()
20790 .as_ref()
20791 .is_some_and(|context_menu| context_menu.focused(window, cx))
20792 {
20793 self.hide_context_menu(window, cx);
20794 }
20795 self.discard_edit_prediction(false, cx);
20796 cx.emit(EditorEvent::Blurred);
20797 cx.notify();
20798 }
20799
20800 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20801 let mut pending: String = window
20802 .pending_input_keystrokes()
20803 .into_iter()
20804 .flatten()
20805 .filter_map(|keystroke| {
20806 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20807 keystroke.key_char.clone()
20808 } else {
20809 None
20810 }
20811 })
20812 .collect();
20813
20814 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20815 pending = "".to_string();
20816 }
20817
20818 let existing_pending = self
20819 .text_highlights::<PendingInput>(cx)
20820 .map(|(_, ranges)| ranges.to_vec());
20821 if existing_pending.is_none() && pending.is_empty() {
20822 return;
20823 }
20824 let transaction =
20825 self.transact(window, cx, |this, window, cx| {
20826 let selections = this.selections.all::<usize>(cx);
20827 let edits = selections
20828 .iter()
20829 .map(|selection| (selection.end..selection.end, pending.clone()));
20830 this.edit(edits, cx);
20831 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20832 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20833 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20834 }));
20835 });
20836 if let Some(existing_ranges) = existing_pending {
20837 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20838 this.edit(edits, cx);
20839 }
20840 });
20841
20842 let snapshot = self.snapshot(window, cx);
20843 let ranges = self
20844 .selections
20845 .all::<usize>(cx)
20846 .into_iter()
20847 .map(|selection| {
20848 snapshot.buffer_snapshot.anchor_after(selection.end)
20849 ..snapshot
20850 .buffer_snapshot
20851 .anchor_before(selection.end + pending.len())
20852 })
20853 .collect();
20854
20855 if pending.is_empty() {
20856 self.clear_highlights::<PendingInput>(cx);
20857 } else {
20858 self.highlight_text::<PendingInput>(
20859 ranges,
20860 HighlightStyle {
20861 underline: Some(UnderlineStyle {
20862 thickness: px(1.),
20863 color: None,
20864 wavy: false,
20865 }),
20866 ..Default::default()
20867 },
20868 cx,
20869 );
20870 }
20871
20872 self.ime_transaction = self.ime_transaction.or(transaction);
20873 if let Some(transaction) = self.ime_transaction {
20874 self.buffer.update(cx, |buffer, cx| {
20875 buffer.group_until_transaction(transaction, cx);
20876 });
20877 }
20878
20879 if self.text_highlights::<PendingInput>(cx).is_none() {
20880 self.ime_transaction.take();
20881 }
20882 }
20883
20884 pub fn register_action_renderer(
20885 &mut self,
20886 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20887 ) -> Subscription {
20888 let id = self.next_editor_action_id.post_inc();
20889 self.editor_actions
20890 .borrow_mut()
20891 .insert(id, Box::new(listener));
20892
20893 let editor_actions = self.editor_actions.clone();
20894 Subscription::new(move || {
20895 editor_actions.borrow_mut().remove(&id);
20896 })
20897 }
20898
20899 pub fn register_action<A: Action>(
20900 &mut self,
20901 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20902 ) -> Subscription {
20903 let id = self.next_editor_action_id.post_inc();
20904 let listener = Arc::new(listener);
20905 self.editor_actions.borrow_mut().insert(
20906 id,
20907 Box::new(move |_, window, _| {
20908 let listener = listener.clone();
20909 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20910 let action = action.downcast_ref().unwrap();
20911 if phase == DispatchPhase::Bubble {
20912 listener(action, window, cx)
20913 }
20914 })
20915 }),
20916 );
20917
20918 let editor_actions = self.editor_actions.clone();
20919 Subscription::new(move || {
20920 editor_actions.borrow_mut().remove(&id);
20921 })
20922 }
20923
20924 pub fn file_header_size(&self) -> u32 {
20925 FILE_HEADER_HEIGHT
20926 }
20927
20928 pub fn restore(
20929 &mut self,
20930 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20931 window: &mut Window,
20932 cx: &mut Context<Self>,
20933 ) {
20934 let workspace = self.workspace();
20935 let project = self.project();
20936 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20937 let mut tasks = Vec::new();
20938 for (buffer_id, changes) in revert_changes {
20939 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20940 buffer.update(cx, |buffer, cx| {
20941 buffer.edit(
20942 changes
20943 .into_iter()
20944 .map(|(range, text)| (range, text.to_string())),
20945 None,
20946 cx,
20947 );
20948 });
20949
20950 if let Some(project) =
20951 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20952 {
20953 project.update(cx, |project, cx| {
20954 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20955 })
20956 }
20957 }
20958 }
20959 tasks
20960 });
20961 cx.spawn_in(window, async move |_, cx| {
20962 for (buffer, task) in save_tasks {
20963 let result = task.await;
20964 if result.is_err() {
20965 let Some(path) = buffer
20966 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20967 .ok()
20968 else {
20969 continue;
20970 };
20971 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20972 let Some(task) = cx
20973 .update_window_entity(workspace, |workspace, window, cx| {
20974 workspace
20975 .open_path_preview(path, None, false, false, false, window, cx)
20976 })
20977 .ok()
20978 else {
20979 continue;
20980 };
20981 task.await.log_err();
20982 }
20983 }
20984 }
20985 })
20986 .detach();
20987 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20988 selections.refresh()
20989 });
20990 }
20991
20992 pub fn to_pixel_point(
20993 &self,
20994 source: multi_buffer::Anchor,
20995 editor_snapshot: &EditorSnapshot,
20996 window: &mut Window,
20997 ) -> Option<gpui::Point<Pixels>> {
20998 let source_point = source.to_display_point(editor_snapshot);
20999 self.display_to_pixel_point(source_point, editor_snapshot, window)
21000 }
21001
21002 pub fn display_to_pixel_point(
21003 &self,
21004 source: DisplayPoint,
21005 editor_snapshot: &EditorSnapshot,
21006 window: &mut Window,
21007 ) -> Option<gpui::Point<Pixels>> {
21008 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21009 let text_layout_details = self.text_layout_details(window);
21010 let scroll_top = text_layout_details
21011 .scroll_anchor
21012 .scroll_position(editor_snapshot)
21013 .y;
21014
21015 if source.row().as_f32() < scroll_top.floor() {
21016 return None;
21017 }
21018 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21019 let source_y = line_height * (source.row().as_f32() - scroll_top);
21020 Some(gpui::Point::new(source_x, source_y))
21021 }
21022
21023 pub fn has_visible_completions_menu(&self) -> bool {
21024 !self.edit_prediction_preview_is_active()
21025 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21026 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21027 })
21028 }
21029
21030 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21031 if self.mode.is_minimap() {
21032 return;
21033 }
21034 self.addons
21035 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21036 }
21037
21038 pub fn unregister_addon<T: Addon>(&mut self) {
21039 self.addons.remove(&std::any::TypeId::of::<T>());
21040 }
21041
21042 pub fn addon<T: Addon>(&self) -> Option<&T> {
21043 let type_id = std::any::TypeId::of::<T>();
21044 self.addons
21045 .get(&type_id)
21046 .and_then(|item| item.to_any().downcast_ref::<T>())
21047 }
21048
21049 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21050 let type_id = std::any::TypeId::of::<T>();
21051 self.addons
21052 .get_mut(&type_id)
21053 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21054 }
21055
21056 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21057 let text_layout_details = self.text_layout_details(window);
21058 let style = &text_layout_details.editor_style;
21059 let font_id = window.text_system().resolve_font(&style.text.font());
21060 let font_size = style.text.font_size.to_pixels(window.rem_size());
21061 let line_height = style.text.line_height_in_pixels(window.rem_size());
21062 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21063 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21064
21065 CharacterDimensions {
21066 em_width,
21067 em_advance,
21068 line_height,
21069 }
21070 }
21071
21072 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21073 self.load_diff_task.clone()
21074 }
21075
21076 fn read_metadata_from_db(
21077 &mut self,
21078 item_id: u64,
21079 workspace_id: WorkspaceId,
21080 window: &mut Window,
21081 cx: &mut Context<Editor>,
21082 ) {
21083 if self.is_singleton(cx)
21084 && !self.mode.is_minimap()
21085 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21086 {
21087 let buffer_snapshot = OnceCell::new();
21088
21089 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21090 && !folds.is_empty()
21091 {
21092 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21093 self.fold_ranges(
21094 folds
21095 .into_iter()
21096 .map(|(start, end)| {
21097 snapshot.clip_offset(start, Bias::Left)
21098 ..snapshot.clip_offset(end, Bias::Right)
21099 })
21100 .collect(),
21101 false,
21102 window,
21103 cx,
21104 );
21105 }
21106
21107 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21108 && !selections.is_empty()
21109 {
21110 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21111 // skip adding the initial selection to selection history
21112 self.selection_history.mode = SelectionHistoryMode::Skipping;
21113 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21114 s.select_ranges(selections.into_iter().map(|(start, end)| {
21115 snapshot.clip_offset(start, Bias::Left)
21116 ..snapshot.clip_offset(end, Bias::Right)
21117 }));
21118 });
21119 self.selection_history.mode = SelectionHistoryMode::Normal;
21120 };
21121 }
21122
21123 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21124 }
21125
21126 fn update_lsp_data(
21127 &mut self,
21128 ignore_cache: bool,
21129 for_buffer: Option<BufferId>,
21130 window: &mut Window,
21131 cx: &mut Context<'_, Self>,
21132 ) {
21133 self.pull_diagnostics(for_buffer, window, cx);
21134 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21135 }
21136}
21137
21138fn vim_enabled(cx: &App) -> bool {
21139 cx.global::<SettingsStore>()
21140 .raw_user_settings()
21141 .get("vim_mode")
21142 == Some(&serde_json::Value::Bool(true))
21143}
21144
21145fn process_completion_for_edit(
21146 completion: &Completion,
21147 intent: CompletionIntent,
21148 buffer: &Entity<Buffer>,
21149 cursor_position: &text::Anchor,
21150 cx: &mut Context<Editor>,
21151) -> CompletionEdit {
21152 let buffer = buffer.read(cx);
21153 let buffer_snapshot = buffer.snapshot();
21154 let (snippet, new_text) = if completion.is_snippet() {
21155 // Workaround for typescript language server issues so that methods don't expand within
21156 // strings and functions with type expressions. The previous point is used because the query
21157 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21158 let mut snippet_source = completion.new_text.clone();
21159 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21160 previous_point.column = previous_point.column.saturating_sub(1);
21161 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21162 && scope.prefers_label_for_snippet_in_completion()
21163 && let Some(label) = completion.label()
21164 && matches!(
21165 completion.kind(),
21166 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21167 )
21168 {
21169 snippet_source = label;
21170 }
21171 match Snippet::parse(&snippet_source).log_err() {
21172 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21173 None => (None, completion.new_text.clone()),
21174 }
21175 } else {
21176 (None, completion.new_text.clone())
21177 };
21178
21179 let mut range_to_replace = {
21180 let replace_range = &completion.replace_range;
21181 if let CompletionSource::Lsp {
21182 insert_range: Some(insert_range),
21183 ..
21184 } = &completion.source
21185 {
21186 debug_assert_eq!(
21187 insert_range.start, replace_range.start,
21188 "insert_range and replace_range should start at the same position"
21189 );
21190 debug_assert!(
21191 insert_range
21192 .start
21193 .cmp(cursor_position, &buffer_snapshot)
21194 .is_le(),
21195 "insert_range should start before or at cursor position"
21196 );
21197 debug_assert!(
21198 replace_range
21199 .start
21200 .cmp(cursor_position, &buffer_snapshot)
21201 .is_le(),
21202 "replace_range should start before or at cursor position"
21203 );
21204
21205 let should_replace = match intent {
21206 CompletionIntent::CompleteWithInsert => false,
21207 CompletionIntent::CompleteWithReplace => true,
21208 CompletionIntent::Complete | CompletionIntent::Compose => {
21209 let insert_mode =
21210 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21211 .completions
21212 .lsp_insert_mode;
21213 match insert_mode {
21214 LspInsertMode::Insert => false,
21215 LspInsertMode::Replace => true,
21216 LspInsertMode::ReplaceSubsequence => {
21217 let mut text_to_replace = buffer.chars_for_range(
21218 buffer.anchor_before(replace_range.start)
21219 ..buffer.anchor_after(replace_range.end),
21220 );
21221 let mut current_needle = text_to_replace.next();
21222 for haystack_ch in completion.label.text.chars() {
21223 if let Some(needle_ch) = current_needle
21224 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21225 {
21226 current_needle = text_to_replace.next();
21227 }
21228 }
21229 current_needle.is_none()
21230 }
21231 LspInsertMode::ReplaceSuffix => {
21232 if replace_range
21233 .end
21234 .cmp(cursor_position, &buffer_snapshot)
21235 .is_gt()
21236 {
21237 let range_after_cursor = *cursor_position..replace_range.end;
21238 let text_after_cursor = buffer
21239 .text_for_range(
21240 buffer.anchor_before(range_after_cursor.start)
21241 ..buffer.anchor_after(range_after_cursor.end),
21242 )
21243 .collect::<String>()
21244 .to_ascii_lowercase();
21245 completion
21246 .label
21247 .text
21248 .to_ascii_lowercase()
21249 .ends_with(&text_after_cursor)
21250 } else {
21251 true
21252 }
21253 }
21254 }
21255 }
21256 };
21257
21258 if should_replace {
21259 replace_range.clone()
21260 } else {
21261 insert_range.clone()
21262 }
21263 } else {
21264 replace_range.clone()
21265 }
21266 };
21267
21268 if range_to_replace
21269 .end
21270 .cmp(cursor_position, &buffer_snapshot)
21271 .is_lt()
21272 {
21273 range_to_replace.end = *cursor_position;
21274 }
21275
21276 CompletionEdit {
21277 new_text,
21278 replace_range: range_to_replace.to_offset(buffer),
21279 snippet,
21280 }
21281}
21282
21283struct CompletionEdit {
21284 new_text: String,
21285 replace_range: Range<usize>,
21286 snippet: Option<Snippet>,
21287}
21288
21289fn insert_extra_newline_brackets(
21290 buffer: &MultiBufferSnapshot,
21291 range: Range<usize>,
21292 language: &language::LanguageScope,
21293) -> bool {
21294 let leading_whitespace_len = buffer
21295 .reversed_chars_at(range.start)
21296 .take_while(|c| c.is_whitespace() && *c != '\n')
21297 .map(|c| c.len_utf8())
21298 .sum::<usize>();
21299 let trailing_whitespace_len = buffer
21300 .chars_at(range.end)
21301 .take_while(|c| c.is_whitespace() && *c != '\n')
21302 .map(|c| c.len_utf8())
21303 .sum::<usize>();
21304 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21305
21306 language.brackets().any(|(pair, enabled)| {
21307 let pair_start = pair.start.trim_end();
21308 let pair_end = pair.end.trim_start();
21309
21310 enabled
21311 && pair.newline
21312 && buffer.contains_str_at(range.end, pair_end)
21313 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21314 })
21315}
21316
21317fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21318 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21319 [(buffer, range, _)] => (*buffer, range.clone()),
21320 _ => return false,
21321 };
21322 let pair = {
21323 let mut result: Option<BracketMatch> = None;
21324
21325 for pair in buffer
21326 .all_bracket_ranges(range.clone())
21327 .filter(move |pair| {
21328 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21329 })
21330 {
21331 let len = pair.close_range.end - pair.open_range.start;
21332
21333 if let Some(existing) = &result {
21334 let existing_len = existing.close_range.end - existing.open_range.start;
21335 if len > existing_len {
21336 continue;
21337 }
21338 }
21339
21340 result = Some(pair);
21341 }
21342
21343 result
21344 };
21345 let Some(pair) = pair else {
21346 return false;
21347 };
21348 pair.newline_only
21349 && buffer
21350 .chars_for_range(pair.open_range.end..range.start)
21351 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21352 .all(|c| c.is_whitespace() && c != '\n')
21353}
21354
21355fn update_uncommitted_diff_for_buffer(
21356 editor: Entity<Editor>,
21357 project: &Entity<Project>,
21358 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21359 buffer: Entity<MultiBuffer>,
21360 cx: &mut App,
21361) -> Task<()> {
21362 let mut tasks = Vec::new();
21363 project.update(cx, |project, cx| {
21364 for buffer in buffers {
21365 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21366 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21367 }
21368 }
21369 });
21370 cx.spawn(async move |cx| {
21371 let diffs = future::join_all(tasks).await;
21372 if editor
21373 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21374 .unwrap_or(false)
21375 {
21376 return;
21377 }
21378
21379 buffer
21380 .update(cx, |buffer, cx| {
21381 for diff in diffs.into_iter().flatten() {
21382 buffer.add_diff(diff, cx);
21383 }
21384 })
21385 .ok();
21386 })
21387}
21388
21389fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21390 let tab_size = tab_size.get() as usize;
21391 let mut width = offset;
21392
21393 for ch in text.chars() {
21394 width += if ch == '\t' {
21395 tab_size - (width % tab_size)
21396 } else {
21397 1
21398 };
21399 }
21400
21401 width - offset
21402}
21403
21404#[cfg(test)]
21405mod tests {
21406 use super::*;
21407
21408 #[test]
21409 fn test_string_size_with_expanded_tabs() {
21410 let nz = |val| NonZeroU32::new(val).unwrap();
21411 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21412 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21413 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21414 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21415 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21416 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21417 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21418 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21419 }
21420}
21421
21422/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21423struct WordBreakingTokenizer<'a> {
21424 input: &'a str,
21425}
21426
21427impl<'a> WordBreakingTokenizer<'a> {
21428 fn new(input: &'a str) -> Self {
21429 Self { input }
21430 }
21431}
21432
21433fn is_char_ideographic(ch: char) -> bool {
21434 use unicode_script::Script::*;
21435 use unicode_script::UnicodeScript;
21436 matches!(ch.script(), Han | Tangut | Yi)
21437}
21438
21439fn is_grapheme_ideographic(text: &str) -> bool {
21440 text.chars().any(is_char_ideographic)
21441}
21442
21443fn is_grapheme_whitespace(text: &str) -> bool {
21444 text.chars().any(|x| x.is_whitespace())
21445}
21446
21447fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21448 text.chars()
21449 .next()
21450 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21451}
21452
21453#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21454enum WordBreakToken<'a> {
21455 Word { token: &'a str, grapheme_len: usize },
21456 InlineWhitespace { token: &'a str, grapheme_len: usize },
21457 Newline,
21458}
21459
21460impl<'a> Iterator for WordBreakingTokenizer<'a> {
21461 /// Yields a span, the count of graphemes in the token, and whether it was
21462 /// whitespace. Note that it also breaks at word boundaries.
21463 type Item = WordBreakToken<'a>;
21464
21465 fn next(&mut self) -> Option<Self::Item> {
21466 use unicode_segmentation::UnicodeSegmentation;
21467 if self.input.is_empty() {
21468 return None;
21469 }
21470
21471 let mut iter = self.input.graphemes(true).peekable();
21472 let mut offset = 0;
21473 let mut grapheme_len = 0;
21474 if let Some(first_grapheme) = iter.next() {
21475 let is_newline = first_grapheme == "\n";
21476 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21477 offset += first_grapheme.len();
21478 grapheme_len += 1;
21479 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21480 if let Some(grapheme) = iter.peek().copied()
21481 && should_stay_with_preceding_ideograph(grapheme)
21482 {
21483 offset += grapheme.len();
21484 grapheme_len += 1;
21485 }
21486 } else {
21487 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21488 let mut next_word_bound = words.peek().copied();
21489 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21490 next_word_bound = words.next();
21491 }
21492 while let Some(grapheme) = iter.peek().copied() {
21493 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21494 break;
21495 };
21496 if is_grapheme_whitespace(grapheme) != is_whitespace
21497 || (grapheme == "\n") != is_newline
21498 {
21499 break;
21500 };
21501 offset += grapheme.len();
21502 grapheme_len += 1;
21503 iter.next();
21504 }
21505 }
21506 let token = &self.input[..offset];
21507 self.input = &self.input[offset..];
21508 if token == "\n" {
21509 Some(WordBreakToken::Newline)
21510 } else if is_whitespace {
21511 Some(WordBreakToken::InlineWhitespace {
21512 token,
21513 grapheme_len,
21514 })
21515 } else {
21516 Some(WordBreakToken::Word {
21517 token,
21518 grapheme_len,
21519 })
21520 }
21521 } else {
21522 None
21523 }
21524 }
21525}
21526
21527#[test]
21528fn test_word_breaking_tokenizer() {
21529 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21530 ("", &[]),
21531 (" ", &[whitespace(" ", 2)]),
21532 ("Ʒ", &[word("Ʒ", 1)]),
21533 ("Ǽ", &[word("Ǽ", 1)]),
21534 ("⋑", &[word("⋑", 1)]),
21535 ("⋑⋑", &[word("⋑⋑", 2)]),
21536 (
21537 "原理,进而",
21538 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21539 ),
21540 (
21541 "hello world",
21542 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21543 ),
21544 (
21545 "hello, world",
21546 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21547 ),
21548 (
21549 " hello world",
21550 &[
21551 whitespace(" ", 2),
21552 word("hello", 5),
21553 whitespace(" ", 1),
21554 word("world", 5),
21555 ],
21556 ),
21557 (
21558 "这是什么 \n 钢笔",
21559 &[
21560 word("这", 1),
21561 word("是", 1),
21562 word("什", 1),
21563 word("么", 1),
21564 whitespace(" ", 1),
21565 newline(),
21566 whitespace(" ", 1),
21567 word("钢", 1),
21568 word("笔", 1),
21569 ],
21570 ),
21571 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21572 ];
21573
21574 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21575 WordBreakToken::Word {
21576 token,
21577 grapheme_len,
21578 }
21579 }
21580
21581 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21582 WordBreakToken::InlineWhitespace {
21583 token,
21584 grapheme_len,
21585 }
21586 }
21587
21588 fn newline() -> WordBreakToken<'static> {
21589 WordBreakToken::Newline
21590 }
21591
21592 for (input, result) in tests {
21593 assert_eq!(
21594 WordBreakingTokenizer::new(input)
21595 .collect::<Vec<_>>()
21596 .as_slice(),
21597 *result,
21598 );
21599 }
21600}
21601
21602fn wrap_with_prefix(
21603 first_line_prefix: String,
21604 subsequent_lines_prefix: String,
21605 unwrapped_text: String,
21606 wrap_column: usize,
21607 tab_size: NonZeroU32,
21608 preserve_existing_whitespace: bool,
21609) -> String {
21610 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21611 let subsequent_lines_prefix_len =
21612 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21613 let mut wrapped_text = String::new();
21614 let mut current_line = first_line_prefix;
21615 let mut is_first_line = true;
21616
21617 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21618 let mut current_line_len = first_line_prefix_len;
21619 let mut in_whitespace = false;
21620 for token in tokenizer {
21621 let have_preceding_whitespace = in_whitespace;
21622 match token {
21623 WordBreakToken::Word {
21624 token,
21625 grapheme_len,
21626 } => {
21627 in_whitespace = false;
21628 let current_prefix_len = if is_first_line {
21629 first_line_prefix_len
21630 } else {
21631 subsequent_lines_prefix_len
21632 };
21633 if current_line_len + grapheme_len > wrap_column
21634 && current_line_len != current_prefix_len
21635 {
21636 wrapped_text.push_str(current_line.trim_end());
21637 wrapped_text.push('\n');
21638 is_first_line = false;
21639 current_line = subsequent_lines_prefix.clone();
21640 current_line_len = subsequent_lines_prefix_len;
21641 }
21642 current_line.push_str(token);
21643 current_line_len += grapheme_len;
21644 }
21645 WordBreakToken::InlineWhitespace {
21646 mut token,
21647 mut grapheme_len,
21648 } => {
21649 in_whitespace = true;
21650 if have_preceding_whitespace && !preserve_existing_whitespace {
21651 continue;
21652 }
21653 if !preserve_existing_whitespace {
21654 token = " ";
21655 grapheme_len = 1;
21656 }
21657 let current_prefix_len = if is_first_line {
21658 first_line_prefix_len
21659 } else {
21660 subsequent_lines_prefix_len
21661 };
21662 if current_line_len + grapheme_len > wrap_column {
21663 wrapped_text.push_str(current_line.trim_end());
21664 wrapped_text.push('\n');
21665 is_first_line = false;
21666 current_line = subsequent_lines_prefix.clone();
21667 current_line_len = subsequent_lines_prefix_len;
21668 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21669 current_line.push_str(token);
21670 current_line_len += grapheme_len;
21671 }
21672 }
21673 WordBreakToken::Newline => {
21674 in_whitespace = true;
21675 let current_prefix_len = if is_first_line {
21676 first_line_prefix_len
21677 } else {
21678 subsequent_lines_prefix_len
21679 };
21680 if preserve_existing_whitespace {
21681 wrapped_text.push_str(current_line.trim_end());
21682 wrapped_text.push('\n');
21683 is_first_line = false;
21684 current_line = subsequent_lines_prefix.clone();
21685 current_line_len = subsequent_lines_prefix_len;
21686 } else if have_preceding_whitespace {
21687 continue;
21688 } else if current_line_len + 1 > wrap_column
21689 && current_line_len != current_prefix_len
21690 {
21691 wrapped_text.push_str(current_line.trim_end());
21692 wrapped_text.push('\n');
21693 is_first_line = false;
21694 current_line = subsequent_lines_prefix.clone();
21695 current_line_len = subsequent_lines_prefix_len;
21696 } else if current_line_len != current_prefix_len {
21697 current_line.push(' ');
21698 current_line_len += 1;
21699 }
21700 }
21701 }
21702 }
21703
21704 if !current_line.is_empty() {
21705 wrapped_text.push_str(¤t_line);
21706 }
21707 wrapped_text
21708}
21709
21710#[test]
21711fn test_wrap_with_prefix() {
21712 assert_eq!(
21713 wrap_with_prefix(
21714 "# ".to_string(),
21715 "# ".to_string(),
21716 "abcdefg".to_string(),
21717 4,
21718 NonZeroU32::new(4).unwrap(),
21719 false,
21720 ),
21721 "# abcdefg"
21722 );
21723 assert_eq!(
21724 wrap_with_prefix(
21725 "".to_string(),
21726 "".to_string(),
21727 "\thello world".to_string(),
21728 8,
21729 NonZeroU32::new(4).unwrap(),
21730 false,
21731 ),
21732 "hello\nworld"
21733 );
21734 assert_eq!(
21735 wrap_with_prefix(
21736 "// ".to_string(),
21737 "// ".to_string(),
21738 "xx \nyy zz aa bb cc".to_string(),
21739 12,
21740 NonZeroU32::new(4).unwrap(),
21741 false,
21742 ),
21743 "// xx yy zz\n// aa bb cc"
21744 );
21745 assert_eq!(
21746 wrap_with_prefix(
21747 String::new(),
21748 String::new(),
21749 "这是什么 \n 钢笔".to_string(),
21750 3,
21751 NonZeroU32::new(4).unwrap(),
21752 false,
21753 ),
21754 "这是什\n么 钢\n笔"
21755 );
21756}
21757
21758pub trait CollaborationHub {
21759 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21760 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21761 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21762}
21763
21764impl CollaborationHub for Entity<Project> {
21765 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21766 self.read(cx).collaborators()
21767 }
21768
21769 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21770 self.read(cx).user_store().read(cx).participant_indices()
21771 }
21772
21773 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21774 let this = self.read(cx);
21775 let user_ids = this.collaborators().values().map(|c| c.user_id);
21776 this.user_store().read(cx).participant_names(user_ids, cx)
21777 }
21778}
21779
21780pub trait SemanticsProvider {
21781 fn hover(
21782 &self,
21783 buffer: &Entity<Buffer>,
21784 position: text::Anchor,
21785 cx: &mut App,
21786 ) -> Option<Task<Vec<project::Hover>>>;
21787
21788 fn inline_values(
21789 &self,
21790 buffer_handle: Entity<Buffer>,
21791 range: Range<text::Anchor>,
21792 cx: &mut App,
21793 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21794
21795 fn inlay_hints(
21796 &self,
21797 buffer_handle: Entity<Buffer>,
21798 range: Range<text::Anchor>,
21799 cx: &mut App,
21800 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21801
21802 fn resolve_inlay_hint(
21803 &self,
21804 hint: InlayHint,
21805 buffer_handle: Entity<Buffer>,
21806 server_id: LanguageServerId,
21807 cx: &mut App,
21808 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21809
21810 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21811
21812 fn document_highlights(
21813 &self,
21814 buffer: &Entity<Buffer>,
21815 position: text::Anchor,
21816 cx: &mut App,
21817 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21818
21819 fn definitions(
21820 &self,
21821 buffer: &Entity<Buffer>,
21822 position: text::Anchor,
21823 kind: GotoDefinitionKind,
21824 cx: &mut App,
21825 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21826
21827 fn range_for_rename(
21828 &self,
21829 buffer: &Entity<Buffer>,
21830 position: text::Anchor,
21831 cx: &mut App,
21832 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21833
21834 fn perform_rename(
21835 &self,
21836 buffer: &Entity<Buffer>,
21837 position: text::Anchor,
21838 new_name: String,
21839 cx: &mut App,
21840 ) -> Option<Task<Result<ProjectTransaction>>>;
21841}
21842
21843pub trait CompletionProvider {
21844 fn completions(
21845 &self,
21846 excerpt_id: ExcerptId,
21847 buffer: &Entity<Buffer>,
21848 buffer_position: text::Anchor,
21849 trigger: CompletionContext,
21850 window: &mut Window,
21851 cx: &mut Context<Editor>,
21852 ) -> Task<Result<Vec<CompletionResponse>>>;
21853
21854 fn resolve_completions(
21855 &self,
21856 _buffer: Entity<Buffer>,
21857 _completion_indices: Vec<usize>,
21858 _completions: Rc<RefCell<Box<[Completion]>>>,
21859 _cx: &mut Context<Editor>,
21860 ) -> Task<Result<bool>> {
21861 Task::ready(Ok(false))
21862 }
21863
21864 fn apply_additional_edits_for_completion(
21865 &self,
21866 _buffer: Entity<Buffer>,
21867 _completions: Rc<RefCell<Box<[Completion]>>>,
21868 _completion_index: usize,
21869 _push_to_history: bool,
21870 _cx: &mut Context<Editor>,
21871 ) -> Task<Result<Option<language::Transaction>>> {
21872 Task::ready(Ok(None))
21873 }
21874
21875 fn is_completion_trigger(
21876 &self,
21877 buffer: &Entity<Buffer>,
21878 position: language::Anchor,
21879 text: &str,
21880 trigger_in_words: bool,
21881 menu_is_open: bool,
21882 cx: &mut Context<Editor>,
21883 ) -> bool;
21884
21885 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21886
21887 fn sort_completions(&self) -> bool {
21888 true
21889 }
21890
21891 fn filter_completions(&self) -> bool {
21892 true
21893 }
21894}
21895
21896pub trait CodeActionProvider {
21897 fn id(&self) -> Arc<str>;
21898
21899 fn code_actions(
21900 &self,
21901 buffer: &Entity<Buffer>,
21902 range: Range<text::Anchor>,
21903 window: &mut Window,
21904 cx: &mut App,
21905 ) -> Task<Result<Vec<CodeAction>>>;
21906
21907 fn apply_code_action(
21908 &self,
21909 buffer_handle: Entity<Buffer>,
21910 action: CodeAction,
21911 excerpt_id: ExcerptId,
21912 push_to_history: bool,
21913 window: &mut Window,
21914 cx: &mut App,
21915 ) -> Task<Result<ProjectTransaction>>;
21916}
21917
21918impl CodeActionProvider for Entity<Project> {
21919 fn id(&self) -> Arc<str> {
21920 "project".into()
21921 }
21922
21923 fn code_actions(
21924 &self,
21925 buffer: &Entity<Buffer>,
21926 range: Range<text::Anchor>,
21927 _window: &mut Window,
21928 cx: &mut App,
21929 ) -> Task<Result<Vec<CodeAction>>> {
21930 self.update(cx, |project, cx| {
21931 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21932 let code_actions = project.code_actions(buffer, range, None, cx);
21933 cx.background_spawn(async move {
21934 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21935 Ok(code_lens_actions
21936 .context("code lens fetch")?
21937 .into_iter()
21938 .chain(code_actions.context("code action fetch")?)
21939 .collect())
21940 })
21941 })
21942 }
21943
21944 fn apply_code_action(
21945 &self,
21946 buffer_handle: Entity<Buffer>,
21947 action: CodeAction,
21948 _excerpt_id: ExcerptId,
21949 push_to_history: bool,
21950 _window: &mut Window,
21951 cx: &mut App,
21952 ) -> Task<Result<ProjectTransaction>> {
21953 self.update(cx, |project, cx| {
21954 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21955 })
21956 }
21957}
21958
21959fn snippet_completions(
21960 project: &Project,
21961 buffer: &Entity<Buffer>,
21962 buffer_position: text::Anchor,
21963 cx: &mut App,
21964) -> Task<Result<CompletionResponse>> {
21965 let languages = buffer.read(cx).languages_at(buffer_position);
21966 let snippet_store = project.snippets().read(cx);
21967
21968 let scopes: Vec<_> = languages
21969 .iter()
21970 .filter_map(|language| {
21971 let language_name = language.lsp_id();
21972 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21973
21974 if snippets.is_empty() {
21975 None
21976 } else {
21977 Some((language.default_scope(), snippets))
21978 }
21979 })
21980 .collect();
21981
21982 if scopes.is_empty() {
21983 return Task::ready(Ok(CompletionResponse {
21984 completions: vec![],
21985 is_incomplete: false,
21986 }));
21987 }
21988
21989 let snapshot = buffer.read(cx).text_snapshot();
21990 let chars: String = snapshot
21991 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21992 .collect();
21993 let executor = cx.background_executor().clone();
21994
21995 cx.background_spawn(async move {
21996 let mut is_incomplete = false;
21997 let mut completions: Vec<Completion> = Vec::new();
21998 for (scope, snippets) in scopes.into_iter() {
21999 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22000 let mut last_word = chars
22001 .chars()
22002 .take_while(|c| classifier.is_word(*c))
22003 .collect::<String>();
22004 last_word = last_word.chars().rev().collect();
22005
22006 if last_word.is_empty() {
22007 return Ok(CompletionResponse {
22008 completions: vec![],
22009 is_incomplete: true,
22010 });
22011 }
22012
22013 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22014 let to_lsp = |point: &text::Anchor| {
22015 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22016 point_to_lsp(end)
22017 };
22018 let lsp_end = to_lsp(&buffer_position);
22019
22020 let candidates = snippets
22021 .iter()
22022 .enumerate()
22023 .flat_map(|(ix, snippet)| {
22024 snippet
22025 .prefix
22026 .iter()
22027 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22028 })
22029 .collect::<Vec<StringMatchCandidate>>();
22030
22031 const MAX_RESULTS: usize = 100;
22032 let mut matches = fuzzy::match_strings(
22033 &candidates,
22034 &last_word,
22035 last_word.chars().any(|c| c.is_uppercase()),
22036 true,
22037 MAX_RESULTS,
22038 &Default::default(),
22039 executor.clone(),
22040 )
22041 .await;
22042
22043 if matches.len() >= MAX_RESULTS {
22044 is_incomplete = true;
22045 }
22046
22047 // Remove all candidates where the query's start does not match the start of any word in the candidate
22048 if let Some(query_start) = last_word.chars().next() {
22049 matches.retain(|string_match| {
22050 split_words(&string_match.string).any(|word| {
22051 // Check that the first codepoint of the word as lowercase matches the first
22052 // codepoint of the query as lowercase
22053 word.chars()
22054 .flat_map(|codepoint| codepoint.to_lowercase())
22055 .zip(query_start.to_lowercase())
22056 .all(|(word_cp, query_cp)| word_cp == query_cp)
22057 })
22058 });
22059 }
22060
22061 let matched_strings = matches
22062 .into_iter()
22063 .map(|m| m.string)
22064 .collect::<HashSet<_>>();
22065
22066 completions.extend(snippets.iter().filter_map(|snippet| {
22067 let matching_prefix = snippet
22068 .prefix
22069 .iter()
22070 .find(|prefix| matched_strings.contains(*prefix))?;
22071 let start = as_offset - last_word.len();
22072 let start = snapshot.anchor_before(start);
22073 let range = start..buffer_position;
22074 let lsp_start = to_lsp(&start);
22075 let lsp_range = lsp::Range {
22076 start: lsp_start,
22077 end: lsp_end,
22078 };
22079 Some(Completion {
22080 replace_range: range,
22081 new_text: snippet.body.clone(),
22082 source: CompletionSource::Lsp {
22083 insert_range: None,
22084 server_id: LanguageServerId(usize::MAX),
22085 resolved: true,
22086 lsp_completion: Box::new(lsp::CompletionItem {
22087 label: snippet.prefix.first().unwrap().clone(),
22088 kind: Some(CompletionItemKind::SNIPPET),
22089 label_details: snippet.description.as_ref().map(|description| {
22090 lsp::CompletionItemLabelDetails {
22091 detail: Some(description.clone()),
22092 description: None,
22093 }
22094 }),
22095 insert_text_format: Some(InsertTextFormat::SNIPPET),
22096 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22097 lsp::InsertReplaceEdit {
22098 new_text: snippet.body.clone(),
22099 insert: lsp_range,
22100 replace: lsp_range,
22101 },
22102 )),
22103 filter_text: Some(snippet.body.clone()),
22104 sort_text: Some(char::MAX.to_string()),
22105 ..lsp::CompletionItem::default()
22106 }),
22107 lsp_defaults: None,
22108 },
22109 label: CodeLabel {
22110 text: matching_prefix.clone(),
22111 runs: Vec::new(),
22112 filter_range: 0..matching_prefix.len(),
22113 },
22114 icon_path: None,
22115 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22116 single_line: snippet.name.clone().into(),
22117 plain_text: snippet
22118 .description
22119 .clone()
22120 .map(|description| description.into()),
22121 }),
22122 insert_text_mode: None,
22123 confirm: None,
22124 })
22125 }))
22126 }
22127
22128 Ok(CompletionResponse {
22129 completions,
22130 is_incomplete,
22131 })
22132 })
22133}
22134
22135impl CompletionProvider for Entity<Project> {
22136 fn completions(
22137 &self,
22138 _excerpt_id: ExcerptId,
22139 buffer: &Entity<Buffer>,
22140 buffer_position: text::Anchor,
22141 options: CompletionContext,
22142 _window: &mut Window,
22143 cx: &mut Context<Editor>,
22144 ) -> Task<Result<Vec<CompletionResponse>>> {
22145 self.update(cx, |project, cx| {
22146 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22147 let project_completions = project.completions(buffer, buffer_position, options, cx);
22148 cx.background_spawn(async move {
22149 let mut responses = project_completions.await?;
22150 let snippets = snippets.await?;
22151 if !snippets.completions.is_empty() {
22152 responses.push(snippets);
22153 }
22154 Ok(responses)
22155 })
22156 })
22157 }
22158
22159 fn resolve_completions(
22160 &self,
22161 buffer: Entity<Buffer>,
22162 completion_indices: Vec<usize>,
22163 completions: Rc<RefCell<Box<[Completion]>>>,
22164 cx: &mut Context<Editor>,
22165 ) -> Task<Result<bool>> {
22166 self.update(cx, |project, cx| {
22167 project.lsp_store().update(cx, |lsp_store, cx| {
22168 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22169 })
22170 })
22171 }
22172
22173 fn apply_additional_edits_for_completion(
22174 &self,
22175 buffer: Entity<Buffer>,
22176 completions: Rc<RefCell<Box<[Completion]>>>,
22177 completion_index: usize,
22178 push_to_history: bool,
22179 cx: &mut Context<Editor>,
22180 ) -> Task<Result<Option<language::Transaction>>> {
22181 self.update(cx, |project, cx| {
22182 project.lsp_store().update(cx, |lsp_store, cx| {
22183 lsp_store.apply_additional_edits_for_completion(
22184 buffer,
22185 completions,
22186 completion_index,
22187 push_to_history,
22188 cx,
22189 )
22190 })
22191 })
22192 }
22193
22194 fn is_completion_trigger(
22195 &self,
22196 buffer: &Entity<Buffer>,
22197 position: language::Anchor,
22198 text: &str,
22199 trigger_in_words: bool,
22200 menu_is_open: bool,
22201 cx: &mut Context<Editor>,
22202 ) -> bool {
22203 let mut chars = text.chars();
22204 let char = if let Some(char) = chars.next() {
22205 char
22206 } else {
22207 return false;
22208 };
22209 if chars.next().is_some() {
22210 return false;
22211 }
22212
22213 let buffer = buffer.read(cx);
22214 let snapshot = buffer.snapshot();
22215 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22216 return false;
22217 }
22218 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22219 if trigger_in_words && classifier.is_word(char) {
22220 return true;
22221 }
22222
22223 buffer.completion_triggers().contains(text)
22224 }
22225}
22226
22227impl SemanticsProvider for Entity<Project> {
22228 fn hover(
22229 &self,
22230 buffer: &Entity<Buffer>,
22231 position: text::Anchor,
22232 cx: &mut App,
22233 ) -> Option<Task<Vec<project::Hover>>> {
22234 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22235 }
22236
22237 fn document_highlights(
22238 &self,
22239 buffer: &Entity<Buffer>,
22240 position: text::Anchor,
22241 cx: &mut App,
22242 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22243 Some(self.update(cx, |project, cx| {
22244 project.document_highlights(buffer, position, cx)
22245 }))
22246 }
22247
22248 fn definitions(
22249 &self,
22250 buffer: &Entity<Buffer>,
22251 position: text::Anchor,
22252 kind: GotoDefinitionKind,
22253 cx: &mut App,
22254 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22255 Some(self.update(cx, |project, cx| match kind {
22256 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22257 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22258 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22259 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22260 }))
22261 }
22262
22263 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22264 self.update(cx, |project, cx| {
22265 if project
22266 .active_debug_session(cx)
22267 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22268 {
22269 return true;
22270 }
22271
22272 buffer.update(cx, |buffer, cx| {
22273 project.any_language_server_supports_inlay_hints(buffer, cx)
22274 })
22275 })
22276 }
22277
22278 fn inline_values(
22279 &self,
22280 buffer_handle: Entity<Buffer>,
22281 range: Range<text::Anchor>,
22282 cx: &mut App,
22283 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22284 self.update(cx, |project, cx| {
22285 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22286
22287 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22288 })
22289 }
22290
22291 fn inlay_hints(
22292 &self,
22293 buffer_handle: Entity<Buffer>,
22294 range: Range<text::Anchor>,
22295 cx: &mut App,
22296 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22297 Some(self.update(cx, |project, cx| {
22298 project.inlay_hints(buffer_handle, range, cx)
22299 }))
22300 }
22301
22302 fn resolve_inlay_hint(
22303 &self,
22304 hint: InlayHint,
22305 buffer_handle: Entity<Buffer>,
22306 server_id: LanguageServerId,
22307 cx: &mut App,
22308 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22309 Some(self.update(cx, |project, cx| {
22310 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22311 }))
22312 }
22313
22314 fn range_for_rename(
22315 &self,
22316 buffer: &Entity<Buffer>,
22317 position: text::Anchor,
22318 cx: &mut App,
22319 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22320 Some(self.update(cx, |project, cx| {
22321 let buffer = buffer.clone();
22322 let task = project.prepare_rename(buffer.clone(), position, cx);
22323 cx.spawn(async move |_, cx| {
22324 Ok(match task.await? {
22325 PrepareRenameResponse::Success(range) => Some(range),
22326 PrepareRenameResponse::InvalidPosition => None,
22327 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22328 // Fallback on using TreeSitter info to determine identifier range
22329 buffer.read_with(cx, |buffer, _| {
22330 let snapshot = buffer.snapshot();
22331 let (range, kind) = snapshot.surrounding_word(position, false);
22332 if kind != Some(CharKind::Word) {
22333 return None;
22334 }
22335 Some(
22336 snapshot.anchor_before(range.start)
22337 ..snapshot.anchor_after(range.end),
22338 )
22339 })?
22340 }
22341 })
22342 })
22343 }))
22344 }
22345
22346 fn perform_rename(
22347 &self,
22348 buffer: &Entity<Buffer>,
22349 position: text::Anchor,
22350 new_name: String,
22351 cx: &mut App,
22352 ) -> Option<Task<Result<ProjectTransaction>>> {
22353 Some(self.update(cx, |project, cx| {
22354 project.perform_rename(buffer.clone(), position, new_name, cx)
22355 }))
22356 }
22357}
22358
22359fn inlay_hint_settings(
22360 location: Anchor,
22361 snapshot: &MultiBufferSnapshot,
22362 cx: &mut Context<Editor>,
22363) -> InlayHintSettings {
22364 let file = snapshot.file_at(location);
22365 let language = snapshot.language_at(location).map(|l| l.name());
22366 language_settings(language, file, cx).inlay_hints
22367}
22368
22369fn consume_contiguous_rows(
22370 contiguous_row_selections: &mut Vec<Selection<Point>>,
22371 selection: &Selection<Point>,
22372 display_map: &DisplaySnapshot,
22373 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22374) -> (MultiBufferRow, MultiBufferRow) {
22375 contiguous_row_selections.push(selection.clone());
22376 let start_row = starting_row(selection, display_map);
22377 let mut end_row = ending_row(selection, display_map);
22378
22379 while let Some(next_selection) = selections.peek() {
22380 if next_selection.start.row <= end_row.0 {
22381 end_row = ending_row(next_selection, display_map);
22382 contiguous_row_selections.push(selections.next().unwrap().clone());
22383 } else {
22384 break;
22385 }
22386 }
22387 (start_row, end_row)
22388}
22389
22390fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22391 if selection.start.column > 0 {
22392 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22393 } else {
22394 MultiBufferRow(selection.start.row)
22395 }
22396}
22397
22398fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22399 if next_selection.end.column > 0 || next_selection.is_empty() {
22400 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22401 } else {
22402 MultiBufferRow(next_selection.end.row)
22403 }
22404}
22405
22406impl EditorSnapshot {
22407 pub fn remote_selections_in_range<'a>(
22408 &'a self,
22409 range: &'a Range<Anchor>,
22410 collaboration_hub: &dyn CollaborationHub,
22411 cx: &'a App,
22412 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22413 let participant_names = collaboration_hub.user_names(cx);
22414 let participant_indices = collaboration_hub.user_participant_indices(cx);
22415 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22416 let collaborators_by_replica_id = collaborators_by_peer_id
22417 .values()
22418 .map(|collaborator| (collaborator.replica_id, collaborator))
22419 .collect::<HashMap<_, _>>();
22420 self.buffer_snapshot
22421 .selections_in_range(range, false)
22422 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22423 if replica_id == AGENT_REPLICA_ID {
22424 Some(RemoteSelection {
22425 replica_id,
22426 selection,
22427 cursor_shape,
22428 line_mode,
22429 collaborator_id: CollaboratorId::Agent,
22430 user_name: Some("Agent".into()),
22431 color: cx.theme().players().agent(),
22432 })
22433 } else {
22434 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22435 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22436 let user_name = participant_names.get(&collaborator.user_id).cloned();
22437 Some(RemoteSelection {
22438 replica_id,
22439 selection,
22440 cursor_shape,
22441 line_mode,
22442 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22443 user_name,
22444 color: if let Some(index) = participant_index {
22445 cx.theme().players().color_for_participant(index.0)
22446 } else {
22447 cx.theme().players().absent()
22448 },
22449 })
22450 }
22451 })
22452 }
22453
22454 pub fn hunks_for_ranges(
22455 &self,
22456 ranges: impl IntoIterator<Item = Range<Point>>,
22457 ) -> Vec<MultiBufferDiffHunk> {
22458 let mut hunks = Vec::new();
22459 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22460 HashMap::default();
22461 for query_range in ranges {
22462 let query_rows =
22463 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22464 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22465 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22466 ) {
22467 // Include deleted hunks that are adjacent to the query range, because
22468 // otherwise they would be missed.
22469 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22470 if hunk.status().is_deleted() {
22471 intersects_range |= hunk.row_range.start == query_rows.end;
22472 intersects_range |= hunk.row_range.end == query_rows.start;
22473 }
22474 if intersects_range {
22475 if !processed_buffer_rows
22476 .entry(hunk.buffer_id)
22477 .or_default()
22478 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22479 {
22480 continue;
22481 }
22482 hunks.push(hunk);
22483 }
22484 }
22485 }
22486
22487 hunks
22488 }
22489
22490 fn display_diff_hunks_for_rows<'a>(
22491 &'a self,
22492 display_rows: Range<DisplayRow>,
22493 folded_buffers: &'a HashSet<BufferId>,
22494 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22495 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22496 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22497
22498 self.buffer_snapshot
22499 .diff_hunks_in_range(buffer_start..buffer_end)
22500 .filter_map(|hunk| {
22501 if folded_buffers.contains(&hunk.buffer_id) {
22502 return None;
22503 }
22504
22505 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22506 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22507
22508 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22509 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22510
22511 let display_hunk = if hunk_display_start.column() != 0 {
22512 DisplayDiffHunk::Folded {
22513 display_row: hunk_display_start.row(),
22514 }
22515 } else {
22516 let mut end_row = hunk_display_end.row();
22517 if hunk_display_end.column() > 0 {
22518 end_row.0 += 1;
22519 }
22520 let is_created_file = hunk.is_created_file();
22521 DisplayDiffHunk::Unfolded {
22522 status: hunk.status(),
22523 diff_base_byte_range: hunk.diff_base_byte_range,
22524 display_row_range: hunk_display_start.row()..end_row,
22525 multi_buffer_range: Anchor::range_in_buffer(
22526 hunk.excerpt_id,
22527 hunk.buffer_id,
22528 hunk.buffer_range,
22529 ),
22530 is_created_file,
22531 }
22532 };
22533
22534 Some(display_hunk)
22535 })
22536 }
22537
22538 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22539 self.display_snapshot.buffer_snapshot.language_at(position)
22540 }
22541
22542 pub fn is_focused(&self) -> bool {
22543 self.is_focused
22544 }
22545
22546 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22547 self.placeholder_text.as_ref()
22548 }
22549
22550 pub fn scroll_position(&self) -> gpui::Point<f32> {
22551 self.scroll_anchor.scroll_position(&self.display_snapshot)
22552 }
22553
22554 fn gutter_dimensions(
22555 &self,
22556 font_id: FontId,
22557 font_size: Pixels,
22558 max_line_number_width: Pixels,
22559 cx: &App,
22560 ) -> Option<GutterDimensions> {
22561 if !self.show_gutter {
22562 return None;
22563 }
22564
22565 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22566 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22567
22568 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22569 matches!(
22570 ProjectSettings::get_global(cx).git.git_gutter,
22571 Some(GitGutterSetting::TrackedFiles)
22572 )
22573 });
22574 let gutter_settings = EditorSettings::get_global(cx).gutter;
22575 let show_line_numbers = self
22576 .show_line_numbers
22577 .unwrap_or(gutter_settings.line_numbers);
22578 let line_gutter_width = if show_line_numbers {
22579 // Avoid flicker-like gutter resizes when the line number gains another digit by
22580 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22581 let min_width_for_number_on_gutter =
22582 ch_advance * gutter_settings.min_line_number_digits as f32;
22583 max_line_number_width.max(min_width_for_number_on_gutter)
22584 } else {
22585 0.0.into()
22586 };
22587
22588 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22589 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22590
22591 let git_blame_entries_width =
22592 self.git_blame_gutter_max_author_length
22593 .map(|max_author_length| {
22594 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22595 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22596
22597 /// The number of characters to dedicate to gaps and margins.
22598 const SPACING_WIDTH: usize = 4;
22599
22600 let max_char_count = max_author_length.min(renderer.max_author_length())
22601 + ::git::SHORT_SHA_LENGTH
22602 + MAX_RELATIVE_TIMESTAMP.len()
22603 + SPACING_WIDTH;
22604
22605 ch_advance * max_char_count
22606 });
22607
22608 let is_singleton = self.buffer_snapshot.is_singleton();
22609
22610 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22611 left_padding += if !is_singleton {
22612 ch_width * 4.0
22613 } else if show_runnables || show_breakpoints {
22614 ch_width * 3.0
22615 } else if show_git_gutter && show_line_numbers {
22616 ch_width * 2.0
22617 } else if show_git_gutter || show_line_numbers {
22618 ch_width
22619 } else {
22620 px(0.)
22621 };
22622
22623 let shows_folds = is_singleton && gutter_settings.folds;
22624
22625 let right_padding = if shows_folds && show_line_numbers {
22626 ch_width * 4.0
22627 } else if shows_folds || (!is_singleton && show_line_numbers) {
22628 ch_width * 3.0
22629 } else if show_line_numbers {
22630 ch_width
22631 } else {
22632 px(0.)
22633 };
22634
22635 Some(GutterDimensions {
22636 left_padding,
22637 right_padding,
22638 width: line_gutter_width + left_padding + right_padding,
22639 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22640 git_blame_entries_width,
22641 })
22642 }
22643
22644 pub fn render_crease_toggle(
22645 &self,
22646 buffer_row: MultiBufferRow,
22647 row_contains_cursor: bool,
22648 editor: Entity<Editor>,
22649 window: &mut Window,
22650 cx: &mut App,
22651 ) -> Option<AnyElement> {
22652 let folded = self.is_line_folded(buffer_row);
22653 let mut is_foldable = false;
22654
22655 if let Some(crease) = self
22656 .crease_snapshot
22657 .query_row(buffer_row, &self.buffer_snapshot)
22658 {
22659 is_foldable = true;
22660 match crease {
22661 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22662 if let Some(render_toggle) = render_toggle {
22663 let toggle_callback =
22664 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22665 if folded {
22666 editor.update(cx, |editor, cx| {
22667 editor.fold_at(buffer_row, window, cx)
22668 });
22669 } else {
22670 editor.update(cx, |editor, cx| {
22671 editor.unfold_at(buffer_row, window, cx)
22672 });
22673 }
22674 });
22675 return Some((render_toggle)(
22676 buffer_row,
22677 folded,
22678 toggle_callback,
22679 window,
22680 cx,
22681 ));
22682 }
22683 }
22684 }
22685 }
22686
22687 is_foldable |= self.starts_indent(buffer_row);
22688
22689 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22690 Some(
22691 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22692 .toggle_state(folded)
22693 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22694 if folded {
22695 this.unfold_at(buffer_row, window, cx);
22696 } else {
22697 this.fold_at(buffer_row, window, cx);
22698 }
22699 }))
22700 .into_any_element(),
22701 )
22702 } else {
22703 None
22704 }
22705 }
22706
22707 pub fn render_crease_trailer(
22708 &self,
22709 buffer_row: MultiBufferRow,
22710 window: &mut Window,
22711 cx: &mut App,
22712 ) -> Option<AnyElement> {
22713 let folded = self.is_line_folded(buffer_row);
22714 if let Crease::Inline { render_trailer, .. } = self
22715 .crease_snapshot
22716 .query_row(buffer_row, &self.buffer_snapshot)?
22717 {
22718 let render_trailer = render_trailer.as_ref()?;
22719 Some(render_trailer(buffer_row, folded, window, cx))
22720 } else {
22721 None
22722 }
22723 }
22724}
22725
22726impl Deref for EditorSnapshot {
22727 type Target = DisplaySnapshot;
22728
22729 fn deref(&self) -> &Self::Target {
22730 &self.display_snapshot
22731 }
22732}
22733
22734#[derive(Clone, Debug, PartialEq, Eq)]
22735pub enum EditorEvent {
22736 InputIgnored {
22737 text: Arc<str>,
22738 },
22739 InputHandled {
22740 utf16_range_to_replace: Option<Range<isize>>,
22741 text: Arc<str>,
22742 },
22743 ExcerptsAdded {
22744 buffer: Entity<Buffer>,
22745 predecessor: ExcerptId,
22746 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22747 },
22748 ExcerptsRemoved {
22749 ids: Vec<ExcerptId>,
22750 removed_buffer_ids: Vec<BufferId>,
22751 },
22752 BufferFoldToggled {
22753 ids: Vec<ExcerptId>,
22754 folded: bool,
22755 },
22756 ExcerptsEdited {
22757 ids: Vec<ExcerptId>,
22758 },
22759 ExcerptsExpanded {
22760 ids: Vec<ExcerptId>,
22761 },
22762 BufferEdited,
22763 Edited {
22764 transaction_id: clock::Lamport,
22765 },
22766 Reparsed(BufferId),
22767 Focused,
22768 FocusedIn,
22769 Blurred,
22770 DirtyChanged,
22771 Saved,
22772 TitleChanged,
22773 DiffBaseChanged,
22774 SelectionsChanged {
22775 local: bool,
22776 },
22777 ScrollPositionChanged {
22778 local: bool,
22779 autoscroll: bool,
22780 },
22781 Closed,
22782 TransactionUndone {
22783 transaction_id: clock::Lamport,
22784 },
22785 TransactionBegun {
22786 transaction_id: clock::Lamport,
22787 },
22788 Reloaded,
22789 CursorShapeChanged,
22790 BreadcrumbsChanged,
22791 PushedToNavHistory {
22792 anchor: Anchor,
22793 is_deactivate: bool,
22794 },
22795}
22796
22797impl EventEmitter<EditorEvent> for Editor {}
22798
22799impl Focusable for Editor {
22800 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22801 self.focus_handle.clone()
22802 }
22803}
22804
22805impl Render for Editor {
22806 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22807 let settings = ThemeSettings::get_global(cx);
22808
22809 let mut text_style = match self.mode {
22810 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
22811 color: cx.theme().colors().editor_foreground,
22812 font_family: settings.ui_font.family.clone(),
22813 font_features: settings.ui_font.features.clone(),
22814 font_fallbacks: settings.ui_font.fallbacks.clone(),
22815 font_size: rems(0.875).into(),
22816 font_weight: settings.ui_font.weight,
22817 line_height: relative(settings.buffer_line_height.value()),
22818 ..Default::default()
22819 },
22820 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22821 color: cx.theme().colors().editor_foreground,
22822 font_family: settings.buffer_font.family.clone(),
22823 font_features: settings.buffer_font.features.clone(),
22824 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22825 font_size: settings.buffer_font_size(cx).into(),
22826 font_weight: settings.buffer_font.weight,
22827 line_height: relative(settings.buffer_line_height.value()),
22828 ..Default::default()
22829 },
22830 };
22831 if let Some(text_style_refinement) = &self.text_style_refinement {
22832 text_style.refine(text_style_refinement)
22833 }
22834
22835 let background = match self.mode {
22836 EditorMode::SingleLine => cx.theme().system().transparent,
22837 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22838 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22839 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22840 };
22841
22842 EditorElement::new(
22843 &cx.entity(),
22844 EditorStyle {
22845 background,
22846 border: cx.theme().colors().border,
22847 local_player: cx.theme().players().local(),
22848 text: text_style,
22849 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22850 syntax: cx.theme().syntax().clone(),
22851 status: cx.theme().status().clone(),
22852 inlay_hints_style: make_inlay_hints_style(cx),
22853 edit_prediction_styles: make_suggestion_styles(cx),
22854 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22855 show_underlines: self.diagnostics_enabled(),
22856 },
22857 )
22858 }
22859}
22860
22861impl EntityInputHandler for Editor {
22862 fn text_for_range(
22863 &mut self,
22864 range_utf16: Range<usize>,
22865 adjusted_range: &mut Option<Range<usize>>,
22866 _: &mut Window,
22867 cx: &mut Context<Self>,
22868 ) -> Option<String> {
22869 let snapshot = self.buffer.read(cx).read(cx);
22870 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22871 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22872 if (start.0..end.0) != range_utf16 {
22873 adjusted_range.replace(start.0..end.0);
22874 }
22875 Some(snapshot.text_for_range(start..end).collect())
22876 }
22877
22878 fn selected_text_range(
22879 &mut self,
22880 ignore_disabled_input: bool,
22881 _: &mut Window,
22882 cx: &mut Context<Self>,
22883 ) -> Option<UTF16Selection> {
22884 // Prevent the IME menu from appearing when holding down an alphabetic key
22885 // while input is disabled.
22886 if !ignore_disabled_input && !self.input_enabled {
22887 return None;
22888 }
22889
22890 let selection = self.selections.newest::<OffsetUtf16>(cx);
22891 let range = selection.range();
22892
22893 Some(UTF16Selection {
22894 range: range.start.0..range.end.0,
22895 reversed: selection.reversed,
22896 })
22897 }
22898
22899 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22900 let snapshot = self.buffer.read(cx).read(cx);
22901 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22902 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22903 }
22904
22905 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22906 self.clear_highlights::<InputComposition>(cx);
22907 self.ime_transaction.take();
22908 }
22909
22910 fn replace_text_in_range(
22911 &mut self,
22912 range_utf16: Option<Range<usize>>,
22913 text: &str,
22914 window: &mut Window,
22915 cx: &mut Context<Self>,
22916 ) {
22917 if !self.input_enabled {
22918 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22919 return;
22920 }
22921
22922 self.transact(window, cx, |this, window, cx| {
22923 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22924 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22925 Some(this.selection_replacement_ranges(range_utf16, cx))
22926 } else {
22927 this.marked_text_ranges(cx)
22928 };
22929
22930 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22931 let newest_selection_id = this.selections.newest_anchor().id;
22932 this.selections
22933 .all::<OffsetUtf16>(cx)
22934 .iter()
22935 .zip(ranges_to_replace.iter())
22936 .find_map(|(selection, range)| {
22937 if selection.id == newest_selection_id {
22938 Some(
22939 (range.start.0 as isize - selection.head().0 as isize)
22940 ..(range.end.0 as isize - selection.head().0 as isize),
22941 )
22942 } else {
22943 None
22944 }
22945 })
22946 });
22947
22948 cx.emit(EditorEvent::InputHandled {
22949 utf16_range_to_replace: range_to_replace,
22950 text: text.into(),
22951 });
22952
22953 if let Some(new_selected_ranges) = new_selected_ranges {
22954 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22955 selections.select_ranges(new_selected_ranges)
22956 });
22957 this.backspace(&Default::default(), window, cx);
22958 }
22959
22960 this.handle_input(text, window, cx);
22961 });
22962
22963 if let Some(transaction) = self.ime_transaction {
22964 self.buffer.update(cx, |buffer, cx| {
22965 buffer.group_until_transaction(transaction, cx);
22966 });
22967 }
22968
22969 self.unmark_text(window, cx);
22970 }
22971
22972 fn replace_and_mark_text_in_range(
22973 &mut self,
22974 range_utf16: Option<Range<usize>>,
22975 text: &str,
22976 new_selected_range_utf16: Option<Range<usize>>,
22977 window: &mut Window,
22978 cx: &mut Context<Self>,
22979 ) {
22980 if !self.input_enabled {
22981 return;
22982 }
22983
22984 let transaction = self.transact(window, cx, |this, window, cx| {
22985 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22986 let snapshot = this.buffer.read(cx).read(cx);
22987 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22988 for marked_range in &mut marked_ranges {
22989 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22990 marked_range.start.0 += relative_range_utf16.start;
22991 marked_range.start =
22992 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22993 marked_range.end =
22994 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22995 }
22996 }
22997 Some(marked_ranges)
22998 } else if let Some(range_utf16) = range_utf16 {
22999 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23000 Some(this.selection_replacement_ranges(range_utf16, cx))
23001 } else {
23002 None
23003 };
23004
23005 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23006 let newest_selection_id = this.selections.newest_anchor().id;
23007 this.selections
23008 .all::<OffsetUtf16>(cx)
23009 .iter()
23010 .zip(ranges_to_replace.iter())
23011 .find_map(|(selection, range)| {
23012 if selection.id == newest_selection_id {
23013 Some(
23014 (range.start.0 as isize - selection.head().0 as isize)
23015 ..(range.end.0 as isize - selection.head().0 as isize),
23016 )
23017 } else {
23018 None
23019 }
23020 })
23021 });
23022
23023 cx.emit(EditorEvent::InputHandled {
23024 utf16_range_to_replace: range_to_replace,
23025 text: text.into(),
23026 });
23027
23028 if let Some(ranges) = ranges_to_replace {
23029 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23030 s.select_ranges(ranges)
23031 });
23032 }
23033
23034 let marked_ranges = {
23035 let snapshot = this.buffer.read(cx).read(cx);
23036 this.selections
23037 .disjoint_anchors()
23038 .iter()
23039 .map(|selection| {
23040 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23041 })
23042 .collect::<Vec<_>>()
23043 };
23044
23045 if text.is_empty() {
23046 this.unmark_text(window, cx);
23047 } else {
23048 this.highlight_text::<InputComposition>(
23049 marked_ranges.clone(),
23050 HighlightStyle {
23051 underline: Some(UnderlineStyle {
23052 thickness: px(1.),
23053 color: None,
23054 wavy: false,
23055 }),
23056 ..Default::default()
23057 },
23058 cx,
23059 );
23060 }
23061
23062 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23063 let use_autoclose = this.use_autoclose;
23064 let use_auto_surround = this.use_auto_surround;
23065 this.set_use_autoclose(false);
23066 this.set_use_auto_surround(false);
23067 this.handle_input(text, window, cx);
23068 this.set_use_autoclose(use_autoclose);
23069 this.set_use_auto_surround(use_auto_surround);
23070
23071 if let Some(new_selected_range) = new_selected_range_utf16 {
23072 let snapshot = this.buffer.read(cx).read(cx);
23073 let new_selected_ranges = marked_ranges
23074 .into_iter()
23075 .map(|marked_range| {
23076 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23077 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23078 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23079 snapshot.clip_offset_utf16(new_start, Bias::Left)
23080 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23081 })
23082 .collect::<Vec<_>>();
23083
23084 drop(snapshot);
23085 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23086 selections.select_ranges(new_selected_ranges)
23087 });
23088 }
23089 });
23090
23091 self.ime_transaction = self.ime_transaction.or(transaction);
23092 if let Some(transaction) = self.ime_transaction {
23093 self.buffer.update(cx, |buffer, cx| {
23094 buffer.group_until_transaction(transaction, cx);
23095 });
23096 }
23097
23098 if self.text_highlights::<InputComposition>(cx).is_none() {
23099 self.ime_transaction.take();
23100 }
23101 }
23102
23103 fn bounds_for_range(
23104 &mut self,
23105 range_utf16: Range<usize>,
23106 element_bounds: gpui::Bounds<Pixels>,
23107 window: &mut Window,
23108 cx: &mut Context<Self>,
23109 ) -> Option<gpui::Bounds<Pixels>> {
23110 let text_layout_details = self.text_layout_details(window);
23111 let CharacterDimensions {
23112 em_width,
23113 em_advance,
23114 line_height,
23115 } = self.character_dimensions(window);
23116
23117 let snapshot = self.snapshot(window, cx);
23118 let scroll_position = snapshot.scroll_position();
23119 let scroll_left = scroll_position.x * em_advance;
23120
23121 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23122 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23123 + self.gutter_dimensions.full_width();
23124 let y = line_height * (start.row().as_f32() - scroll_position.y);
23125
23126 Some(Bounds {
23127 origin: element_bounds.origin + point(x, y),
23128 size: size(em_width, line_height),
23129 })
23130 }
23131
23132 fn character_index_for_point(
23133 &mut self,
23134 point: gpui::Point<Pixels>,
23135 _window: &mut Window,
23136 _cx: &mut Context<Self>,
23137 ) -> Option<usize> {
23138 let position_map = self.last_position_map.as_ref()?;
23139 if !position_map.text_hitbox.contains(&point) {
23140 return None;
23141 }
23142 let display_point = position_map.point_for_position(point).previous_valid;
23143 let anchor = position_map
23144 .snapshot
23145 .display_point_to_anchor(display_point, Bias::Left);
23146 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23147 Some(utf16_offset.0)
23148 }
23149}
23150
23151trait SelectionExt {
23152 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23153 fn spanned_rows(
23154 &self,
23155 include_end_if_at_line_start: bool,
23156 map: &DisplaySnapshot,
23157 ) -> Range<MultiBufferRow>;
23158}
23159
23160impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23161 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23162 let start = self
23163 .start
23164 .to_point(&map.buffer_snapshot)
23165 .to_display_point(map);
23166 let end = self
23167 .end
23168 .to_point(&map.buffer_snapshot)
23169 .to_display_point(map);
23170 if self.reversed {
23171 end..start
23172 } else {
23173 start..end
23174 }
23175 }
23176
23177 fn spanned_rows(
23178 &self,
23179 include_end_if_at_line_start: bool,
23180 map: &DisplaySnapshot,
23181 ) -> Range<MultiBufferRow> {
23182 let start = self.start.to_point(&map.buffer_snapshot);
23183 let mut end = self.end.to_point(&map.buffer_snapshot);
23184 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23185 end.row -= 1;
23186 }
23187
23188 let buffer_start = map.prev_line_boundary(start).0;
23189 let buffer_end = map.next_line_boundary(end).0;
23190 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23191 }
23192}
23193
23194impl<T: InvalidationRegion> InvalidationStack<T> {
23195 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23196 where
23197 S: Clone + ToOffset,
23198 {
23199 while let Some(region) = self.last() {
23200 let all_selections_inside_invalidation_ranges =
23201 if selections.len() == region.ranges().len() {
23202 selections
23203 .iter()
23204 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23205 .all(|(selection, invalidation_range)| {
23206 let head = selection.head().to_offset(buffer);
23207 invalidation_range.start <= head && invalidation_range.end >= head
23208 })
23209 } else {
23210 false
23211 };
23212
23213 if all_selections_inside_invalidation_ranges {
23214 break;
23215 } else {
23216 self.pop();
23217 }
23218 }
23219 }
23220}
23221
23222impl<T> Default for InvalidationStack<T> {
23223 fn default() -> Self {
23224 Self(Default::default())
23225 }
23226}
23227
23228impl<T> Deref for InvalidationStack<T> {
23229 type Target = Vec<T>;
23230
23231 fn deref(&self) -> &Self::Target {
23232 &self.0
23233 }
23234}
23235
23236impl<T> DerefMut for InvalidationStack<T> {
23237 fn deref_mut(&mut self) -> &mut Self::Target {
23238 &mut self.0
23239 }
23240}
23241
23242impl InvalidationRegion for SnippetState {
23243 fn ranges(&self) -> &[Range<Anchor>] {
23244 &self.ranges[self.active_index]
23245 }
23246}
23247
23248fn edit_prediction_edit_text(
23249 current_snapshot: &BufferSnapshot,
23250 edits: &[(Range<Anchor>, String)],
23251 edit_preview: &EditPreview,
23252 include_deletions: bool,
23253 cx: &App,
23254) -> HighlightedText {
23255 let edits = edits
23256 .iter()
23257 .map(|(anchor, text)| {
23258 (
23259 anchor.start.text_anchor..anchor.end.text_anchor,
23260 text.clone(),
23261 )
23262 })
23263 .collect::<Vec<_>>();
23264
23265 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23266}
23267
23268fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23269 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23270 // Just show the raw edit text with basic styling
23271 let mut text = String::new();
23272 let mut highlights = Vec::new();
23273
23274 let insertion_highlight_style = HighlightStyle {
23275 color: Some(cx.theme().colors().text),
23276 ..Default::default()
23277 };
23278
23279 for (_, edit_text) in edits {
23280 let start_offset = text.len();
23281 text.push_str(edit_text);
23282 let end_offset = text.len();
23283
23284 if start_offset < end_offset {
23285 highlights.push((start_offset..end_offset, insertion_highlight_style));
23286 }
23287 }
23288
23289 HighlightedText {
23290 text: text.into(),
23291 highlights,
23292 }
23293}
23294
23295pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23296 match severity {
23297 lsp::DiagnosticSeverity::ERROR => colors.error,
23298 lsp::DiagnosticSeverity::WARNING => colors.warning,
23299 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23300 lsp::DiagnosticSeverity::HINT => colors.info,
23301 _ => colors.ignored,
23302 }
23303}
23304
23305pub fn styled_runs_for_code_label<'a>(
23306 label: &'a CodeLabel,
23307 syntax_theme: &'a theme::SyntaxTheme,
23308) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23309 let fade_out = HighlightStyle {
23310 fade_out: Some(0.35),
23311 ..Default::default()
23312 };
23313
23314 let mut prev_end = label.filter_range.end;
23315 label
23316 .runs
23317 .iter()
23318 .enumerate()
23319 .flat_map(move |(ix, (range, highlight_id))| {
23320 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23321 style
23322 } else {
23323 return Default::default();
23324 };
23325 let mut muted_style = style;
23326 muted_style.highlight(fade_out);
23327
23328 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23329 if range.start >= label.filter_range.end {
23330 if range.start > prev_end {
23331 runs.push((prev_end..range.start, fade_out));
23332 }
23333 runs.push((range.clone(), muted_style));
23334 } else if range.end <= label.filter_range.end {
23335 runs.push((range.clone(), style));
23336 } else {
23337 runs.push((range.start..label.filter_range.end, style));
23338 runs.push((label.filter_range.end..range.end, muted_style));
23339 }
23340 prev_end = cmp::max(prev_end, range.end);
23341
23342 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23343 runs.push((prev_end..label.text.len(), fade_out));
23344 }
23345
23346 runs
23347 })
23348}
23349
23350pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23351 let mut prev_index = 0;
23352 let mut prev_codepoint: Option<char> = None;
23353 text.char_indices()
23354 .chain([(text.len(), '\0')])
23355 .filter_map(move |(index, codepoint)| {
23356 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23357 let is_boundary = index == text.len()
23358 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23359 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23360 if is_boundary {
23361 let chunk = &text[prev_index..index];
23362 prev_index = index;
23363 Some(chunk)
23364 } else {
23365 None
23366 }
23367 })
23368}
23369
23370pub trait RangeToAnchorExt: Sized {
23371 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23372
23373 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23374 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23375 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23376 }
23377}
23378
23379impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23380 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23381 let start_offset = self.start.to_offset(snapshot);
23382 let end_offset = self.end.to_offset(snapshot);
23383 if start_offset == end_offset {
23384 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23385 } else {
23386 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23387 }
23388 }
23389}
23390
23391pub trait RowExt {
23392 fn as_f32(&self) -> f32;
23393
23394 fn next_row(&self) -> Self;
23395
23396 fn previous_row(&self) -> Self;
23397
23398 fn minus(&self, other: Self) -> u32;
23399}
23400
23401impl RowExt for DisplayRow {
23402 fn as_f32(&self) -> f32 {
23403 self.0 as f32
23404 }
23405
23406 fn next_row(&self) -> Self {
23407 Self(self.0 + 1)
23408 }
23409
23410 fn previous_row(&self) -> Self {
23411 Self(self.0.saturating_sub(1))
23412 }
23413
23414 fn minus(&self, other: Self) -> u32 {
23415 self.0 - other.0
23416 }
23417}
23418
23419impl RowExt for MultiBufferRow {
23420 fn as_f32(&self) -> f32 {
23421 self.0 as f32
23422 }
23423
23424 fn next_row(&self) -> Self {
23425 Self(self.0 + 1)
23426 }
23427
23428 fn previous_row(&self) -> Self {
23429 Self(self.0.saturating_sub(1))
23430 }
23431
23432 fn minus(&self, other: Self) -> u32 {
23433 self.0 - other.0
23434 }
23435}
23436
23437trait RowRangeExt {
23438 type Row;
23439
23440 fn len(&self) -> usize;
23441
23442 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23443}
23444
23445impl RowRangeExt for Range<MultiBufferRow> {
23446 type Row = MultiBufferRow;
23447
23448 fn len(&self) -> usize {
23449 (self.end.0 - self.start.0) as usize
23450 }
23451
23452 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23453 (self.start.0..self.end.0).map(MultiBufferRow)
23454 }
23455}
23456
23457impl RowRangeExt for Range<DisplayRow> {
23458 type Row = DisplayRow;
23459
23460 fn len(&self) -> usize {
23461 (self.end.0 - self.start.0) as usize
23462 }
23463
23464 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23465 (self.start.0..self.end.0).map(DisplayRow)
23466 }
23467}
23468
23469/// If select range has more than one line, we
23470/// just point the cursor to range.start.
23471fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23472 if range.start.row == range.end.row {
23473 range
23474 } else {
23475 range.start..range.start
23476 }
23477}
23478pub struct KillRing(ClipboardItem);
23479impl Global for KillRing {}
23480
23481const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23482
23483enum BreakpointPromptEditAction {
23484 Log,
23485 Condition,
23486 HitCondition,
23487}
23488
23489struct BreakpointPromptEditor {
23490 pub(crate) prompt: Entity<Editor>,
23491 editor: WeakEntity<Editor>,
23492 breakpoint_anchor: Anchor,
23493 breakpoint: Breakpoint,
23494 edit_action: BreakpointPromptEditAction,
23495 block_ids: HashSet<CustomBlockId>,
23496 editor_margins: Arc<Mutex<EditorMargins>>,
23497 _subscriptions: Vec<Subscription>,
23498}
23499
23500impl BreakpointPromptEditor {
23501 const MAX_LINES: u8 = 4;
23502
23503 fn new(
23504 editor: WeakEntity<Editor>,
23505 breakpoint_anchor: Anchor,
23506 breakpoint: Breakpoint,
23507 edit_action: BreakpointPromptEditAction,
23508 window: &mut Window,
23509 cx: &mut Context<Self>,
23510 ) -> Self {
23511 let base_text = match edit_action {
23512 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23513 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23514 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23515 }
23516 .map(|msg| msg.to_string())
23517 .unwrap_or_default();
23518
23519 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23520 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23521
23522 let prompt = cx.new(|cx| {
23523 let mut prompt = Editor::new(
23524 EditorMode::AutoHeight {
23525 min_lines: 1,
23526 max_lines: Some(Self::MAX_LINES as usize),
23527 },
23528 buffer,
23529 None,
23530 window,
23531 cx,
23532 );
23533 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23534 prompt.set_show_cursor_when_unfocused(false, cx);
23535 prompt.set_placeholder_text(
23536 match edit_action {
23537 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23538 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23539 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23540 },
23541 cx,
23542 );
23543
23544 prompt
23545 });
23546
23547 Self {
23548 prompt,
23549 editor,
23550 breakpoint_anchor,
23551 breakpoint,
23552 edit_action,
23553 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23554 block_ids: Default::default(),
23555 _subscriptions: vec![],
23556 }
23557 }
23558
23559 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23560 self.block_ids.extend(block_ids)
23561 }
23562
23563 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23564 if let Some(editor) = self.editor.upgrade() {
23565 let message = self
23566 .prompt
23567 .read(cx)
23568 .buffer
23569 .read(cx)
23570 .as_singleton()
23571 .expect("A multi buffer in breakpoint prompt isn't possible")
23572 .read(cx)
23573 .as_rope()
23574 .to_string();
23575
23576 editor.update(cx, |editor, cx| {
23577 editor.edit_breakpoint_at_anchor(
23578 self.breakpoint_anchor,
23579 self.breakpoint.clone(),
23580 match self.edit_action {
23581 BreakpointPromptEditAction::Log => {
23582 BreakpointEditAction::EditLogMessage(message.into())
23583 }
23584 BreakpointPromptEditAction::Condition => {
23585 BreakpointEditAction::EditCondition(message.into())
23586 }
23587 BreakpointPromptEditAction::HitCondition => {
23588 BreakpointEditAction::EditHitCondition(message.into())
23589 }
23590 },
23591 cx,
23592 );
23593
23594 editor.remove_blocks(self.block_ids.clone(), None, cx);
23595 cx.focus_self(window);
23596 });
23597 }
23598 }
23599
23600 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23601 self.editor
23602 .update(cx, |editor, cx| {
23603 editor.remove_blocks(self.block_ids.clone(), None, cx);
23604 window.focus(&editor.focus_handle);
23605 })
23606 .log_err();
23607 }
23608
23609 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23610 let settings = ThemeSettings::get_global(cx);
23611 let text_style = TextStyle {
23612 color: if self.prompt.read(cx).read_only(cx) {
23613 cx.theme().colors().text_disabled
23614 } else {
23615 cx.theme().colors().text
23616 },
23617 font_family: settings.buffer_font.family.clone(),
23618 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23619 font_size: settings.buffer_font_size(cx).into(),
23620 font_weight: settings.buffer_font.weight,
23621 line_height: relative(settings.buffer_line_height.value()),
23622 ..Default::default()
23623 };
23624 EditorElement::new(
23625 &self.prompt,
23626 EditorStyle {
23627 background: cx.theme().colors().editor_background,
23628 local_player: cx.theme().players().local(),
23629 text: text_style,
23630 ..Default::default()
23631 },
23632 )
23633 }
23634}
23635
23636impl Render for BreakpointPromptEditor {
23637 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23638 let editor_margins = *self.editor_margins.lock();
23639 let gutter_dimensions = editor_margins.gutter;
23640 h_flex()
23641 .key_context("Editor")
23642 .bg(cx.theme().colors().editor_background)
23643 .border_y_1()
23644 .border_color(cx.theme().status().info_border)
23645 .size_full()
23646 .py(window.line_height() / 2.5)
23647 .on_action(cx.listener(Self::confirm))
23648 .on_action(cx.listener(Self::cancel))
23649 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23650 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23651 }
23652}
23653
23654impl Focusable for BreakpointPromptEditor {
23655 fn focus_handle(&self, cx: &App) -> FocusHandle {
23656 self.prompt.focus_handle(cx)
23657 }
23658}
23659
23660fn all_edits_insertions_or_deletions(
23661 edits: &Vec<(Range<Anchor>, String)>,
23662 snapshot: &MultiBufferSnapshot,
23663) -> bool {
23664 let mut all_insertions = true;
23665 let mut all_deletions = true;
23666
23667 for (range, new_text) in edits.iter() {
23668 let range_is_empty = range.to_offset(snapshot).is_empty();
23669 let text_is_empty = new_text.is_empty();
23670
23671 if range_is_empty != text_is_empty {
23672 if range_is_empty {
23673 all_deletions = false;
23674 } else {
23675 all_insertions = false;
23676 }
23677 } else {
23678 return false;
23679 }
23680
23681 if !all_insertions && !all_deletions {
23682 return false;
23683 }
23684 }
23685 all_insertions || all_deletions
23686}
23687
23688struct MissingEditPredictionKeybindingTooltip;
23689
23690impl Render for MissingEditPredictionKeybindingTooltip {
23691 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23692 ui::tooltip_container(window, cx, |container, _, cx| {
23693 container
23694 .flex_shrink_0()
23695 .max_w_80()
23696 .min_h(rems_from_px(124.))
23697 .justify_between()
23698 .child(
23699 v_flex()
23700 .flex_1()
23701 .text_ui_sm(cx)
23702 .child(Label::new("Conflict with Accept Keybinding"))
23703 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23704 )
23705 .child(
23706 h_flex()
23707 .pb_1()
23708 .gap_1()
23709 .items_end()
23710 .w_full()
23711 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23712 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23713 }))
23714 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23715 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23716 })),
23717 )
23718 })
23719 }
23720}
23721
23722#[derive(Debug, Clone, Copy, PartialEq)]
23723pub struct LineHighlight {
23724 pub background: Background,
23725 pub border: Option<gpui::Hsla>,
23726 pub include_gutter: bool,
23727 pub type_id: Option<TypeId>,
23728}
23729
23730struct LineManipulationResult {
23731 pub new_text: String,
23732 pub line_count_before: usize,
23733 pub line_count_after: usize,
23734}
23735
23736fn render_diff_hunk_controls(
23737 row: u32,
23738 status: &DiffHunkStatus,
23739 hunk_range: Range<Anchor>,
23740 is_created_file: bool,
23741 line_height: Pixels,
23742 editor: &Entity<Editor>,
23743 _window: &mut Window,
23744 cx: &mut App,
23745) -> AnyElement {
23746 h_flex()
23747 .h(line_height)
23748 .mr_1()
23749 .gap_1()
23750 .px_0p5()
23751 .pb_1()
23752 .border_x_1()
23753 .border_b_1()
23754 .border_color(cx.theme().colors().border_variant)
23755 .rounded_b_lg()
23756 .bg(cx.theme().colors().editor_background)
23757 .gap_1()
23758 .block_mouse_except_scroll()
23759 .shadow_md()
23760 .child(if status.has_secondary_hunk() {
23761 Button::new(("stage", row as u64), "Stage")
23762 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23763 .tooltip({
23764 let focus_handle = editor.focus_handle(cx);
23765 move |window, cx| {
23766 Tooltip::for_action_in(
23767 "Stage Hunk",
23768 &::git::ToggleStaged,
23769 &focus_handle,
23770 window,
23771 cx,
23772 )
23773 }
23774 })
23775 .on_click({
23776 let editor = editor.clone();
23777 move |_event, _window, cx| {
23778 editor.update(cx, |editor, cx| {
23779 editor.stage_or_unstage_diff_hunks(
23780 true,
23781 vec![hunk_range.start..hunk_range.start],
23782 cx,
23783 );
23784 });
23785 }
23786 })
23787 } else {
23788 Button::new(("unstage", row as u64), "Unstage")
23789 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23790 .tooltip({
23791 let focus_handle = editor.focus_handle(cx);
23792 move |window, cx| {
23793 Tooltip::for_action_in(
23794 "Unstage Hunk",
23795 &::git::ToggleStaged,
23796 &focus_handle,
23797 window,
23798 cx,
23799 )
23800 }
23801 })
23802 .on_click({
23803 let editor = editor.clone();
23804 move |_event, _window, cx| {
23805 editor.update(cx, |editor, cx| {
23806 editor.stage_or_unstage_diff_hunks(
23807 false,
23808 vec![hunk_range.start..hunk_range.start],
23809 cx,
23810 );
23811 });
23812 }
23813 })
23814 })
23815 .child(
23816 Button::new(("restore", row as u64), "Restore")
23817 .tooltip({
23818 let focus_handle = editor.focus_handle(cx);
23819 move |window, cx| {
23820 Tooltip::for_action_in(
23821 "Restore Hunk",
23822 &::git::Restore,
23823 &focus_handle,
23824 window,
23825 cx,
23826 )
23827 }
23828 })
23829 .on_click({
23830 let editor = editor.clone();
23831 move |_event, window, cx| {
23832 editor.update(cx, |editor, cx| {
23833 let snapshot = editor.snapshot(window, cx);
23834 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23835 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23836 });
23837 }
23838 })
23839 .disabled(is_created_file),
23840 )
23841 .when(
23842 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23843 |el| {
23844 el.child(
23845 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23846 .shape(IconButtonShape::Square)
23847 .icon_size(IconSize::Small)
23848 // .disabled(!has_multiple_hunks)
23849 .tooltip({
23850 let focus_handle = editor.focus_handle(cx);
23851 move |window, cx| {
23852 Tooltip::for_action_in(
23853 "Next Hunk",
23854 &GoToHunk,
23855 &focus_handle,
23856 window,
23857 cx,
23858 )
23859 }
23860 })
23861 .on_click({
23862 let editor = editor.clone();
23863 move |_event, window, cx| {
23864 editor.update(cx, |editor, cx| {
23865 let snapshot = editor.snapshot(window, cx);
23866 let position =
23867 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23868 editor.go_to_hunk_before_or_after_position(
23869 &snapshot,
23870 position,
23871 Direction::Next,
23872 window,
23873 cx,
23874 );
23875 editor.expand_selected_diff_hunks(cx);
23876 });
23877 }
23878 }),
23879 )
23880 .child(
23881 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23882 .shape(IconButtonShape::Square)
23883 .icon_size(IconSize::Small)
23884 // .disabled(!has_multiple_hunks)
23885 .tooltip({
23886 let focus_handle = editor.focus_handle(cx);
23887 move |window, cx| {
23888 Tooltip::for_action_in(
23889 "Previous Hunk",
23890 &GoToPreviousHunk,
23891 &focus_handle,
23892 window,
23893 cx,
23894 )
23895 }
23896 })
23897 .on_click({
23898 let editor = editor.clone();
23899 move |_event, window, cx| {
23900 editor.update(cx, |editor, cx| {
23901 let snapshot = editor.snapshot(window, cx);
23902 let point =
23903 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23904 editor.go_to_hunk_before_or_after_position(
23905 &snapshot,
23906 point,
23907 Direction::Prev,
23908 window,
23909 cx,
23910 );
23911 editor.expand_selected_diff_hunks(cx);
23912 });
23913 }
23914 }),
23915 )
23916 },
23917 )
23918 .into_any_element()
23919}